From d1d64628998b2de853881f6cf1834e01cc22c2f5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 13 Jul 2018 09:26:42 +0100 Subject: [PATCH 001/569] Update RELEASENOTES.md --- RELEASENOTES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b22fd16a..a9a4cacb 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,7 +1,8 @@ Version 3.2d67 ==== **Bug Fix** -* Fix a bug causing a crash when `soxr` interpolation was enabled and a play session was stopped. This bug overwrote incorrect memory locations, so depending on what it overwrote, may have been responsible for other inexplicable crashes. +* Fix a bug causing a crash when `soxr` interpolation was enabled and a play session was stopped. This bug overwrote incorrect memory locations, so depending on what it overwrote, may have been responsible for other inexplicable crashes. Thanks are due to [hanaguro](https://github.com/hanaguro), [FnasBas](https://github.com/FnasBas), [priitohlo](https://github.com/priitohlo) and [David Krmpotić]https://github.com/davidhq. + Version 3.2.d66 ==== From 074914f60c43d461e04240f425bb8d1bbd874577 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 13 Jul 2018 09:27:17 +0100 Subject: [PATCH 002/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a9a4cacb..05436fe2 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,7 +1,7 @@ Version 3.2d67 ==== **Bug Fix** -* Fix a bug causing a crash when `soxr` interpolation was enabled and a play session was stopped. This bug overwrote incorrect memory locations, so depending on what it overwrote, may have been responsible for other inexplicable crashes. Thanks are due to [hanaguro](https://github.com/hanaguro), [FnasBas](https://github.com/FnasBas), [priitohlo](https://github.com/priitohlo) and [David Krmpotić]https://github.com/davidhq. +* Fix a bug causing a crash when `soxr` interpolation was enabled and a play session was stopped. This bug overwrote incorrect memory locations, so depending on what it overwrote, may have been responsible for other inexplicable crashes. Thanks are due to [hanaguro](https://github.com/hanaguro), [FnasBas](https://github.com/FnasBas), [priitohlo](https://github.com/priitohlo) and [David Krmpotić](https://github.com/davidhq). Version 3.2.d66 From a0ee75bd8428f98b66b5169693fa56bde735aeaf Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 13 Jul 2018 10:52:35 +0100 Subject: [PATCH 003/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 05436fe2..6170227b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,7 +1,7 @@ Version 3.2d67 ==== **Bug Fix** -* Fix a bug causing a crash when `soxr` interpolation was enabled and a play session was stopped. This bug overwrote incorrect memory locations, so depending on what it overwrote, may have been responsible for other inexplicable crashes. Thanks are due to [hanaguro](https://github.com/hanaguro), [FnasBas](https://github.com/FnasBas), [priitohlo](https://github.com/priitohlo) and [David Krmpotić](https://github.com/davidhq). +* Fix a bug causing a crash when `soxr` interpolation was enabled and a play session was stopped. This bug overwrote incorrect memory locations, so depending on what it overwrote, may have been responsible for other inexplicable crashes. Thanks are due to [hanaguro](https://github.com/hanaguro), [FnasBas](https://github.com/FnasBas), [priitohlo](https://github.com/priitohlo), [David Krmpotić](https://github.com/davidhq) and [artenverho](https://github.com/artenverho). Version 3.2.d66 From b19b14746a9ac77cfb19211a74f29f3c8f6216ad Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 13 Jul 2018 11:43:18 +0100 Subject: [PATCH 004/569] Update RELEASENOTES.md --- RELEASENOTES.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6170227b..a9c7a5ef 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,12 +1,14 @@ Version 3.2d67 ==== +This is equivalent to 3.2.1. + **Bug Fix** * Fix a bug causing a crash when `soxr` interpolation was enabled and a play session was stopped. This bug overwrote incorrect memory locations, so depending on what it overwrote, may have been responsible for other inexplicable crashes. Thanks are due to [hanaguro](https://github.com/hanaguro), [FnasBas](https://github.com/FnasBas), [priitohlo](https://github.com/priitohlo), [David Krmpotić](https://github.com/davidhq) and [artenverho](https://github.com/artenverho). Version 3.2.d66 ==== -* This is equivalent to 3.2. +This is equivalent to 3.2. Version 3.2.d65 ==== From c2e3fa5aa7ccb0d658869e4d70d9240c9f6153ec Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 14 Jul 2018 13:38:29 +0100 Subject: [PATCH 005/569] Move ab_mutex, flow control CV and flush mutex initialisation and teardown out of the player thread. Thus remove need for player theread lock. Format some of the surce files. --- configure.ac | 2 +- dbus-service.c | 20 +- mdns_avahi.c | 122 ++++++------- metadata_hub.h | 2 +- mpris-service.c | 5 +- mqtt.c | 282 ++++++++++++++--------------- mqtt.h | 11 +- player.c | 471 +++++++++++++++++++++--------------------------- player.h | 1 - rtp.c | 18 +- rtsp.c | 41 +++-- shairport.c | 143 +++++++-------- 12 files changed, 531 insertions(+), 587 deletions(-) diff --git a/configure.ac b/configure.ac index f047fc15..4ddb55f5 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.2d67], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.2.2p0], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) diff --git a/dbus-service.c b/dbus-service.c index ebcd3371..14ace35b 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -43,16 +43,15 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)) shairport_sync_advanced_remote_control_set_available(shairportSyncAdvancedRemoteControlSkeleton, FALSE); } - + if (argc->progress_string) { - // debug(1, "Check progress string"); - th = shairport_sync_remote_control_get_progress_string( - shairportSyncRemoteControlSkeleton); - if ((th == NULL) || (strcasecmp(th, argc->progress_string) != 0)) { - // debug(1, "Progress string should be changed"); - shairport_sync_remote_control_set_progress_string( - shairportSyncRemoteControlSkeleton, argc->progress_string); - } + // debug(1, "Check progress string"); + th = shairport_sync_remote_control_get_progress_string(shairportSyncRemoteControlSkeleton); + if ((th == NULL) || (strcasecmp(th, argc->progress_string) != 0)) { + // debug(1, "Progress string should be changed"); + shairport_sync_remote_control_set_progress_string(shairportSyncRemoteControlSkeleton, + argc->progress_string); + } } switch (argc->player_state) { @@ -162,7 +161,8 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)) if ((argc->track_metadata) && (argc->track_metadata->item_id)) { char trackidstring[128]; // debug(1, "Set ID using mper ID: \"%u\".",argc->item_id); - snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/mper_%u", argc->track_metadata->item_id); + snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/mper_%u", + argc->track_metadata->item_id); GVariant *trackid = g_variant_new("o", trackidstring); g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid); } diff --git a/mdns_avahi.c b/mdns_avahi.c index 28aa9eca..c54f2df6 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -84,33 +84,33 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn /* Called whenever a service has been resolved successfully or timed out */ switch (event) { - case AVAHI_RESOLVER_FAILURE: - debug(2, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s.", name, - type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r)))); - break; - case AVAHI_RESOLVER_FOUND: { - // char a[AVAHI_ADDRESS_STR_MAX], *t; - // debug(1, "Resolve callback: Service '%s' of type '%s' in domain '%s':", name, type, domain); - char *dacpid = strstr(name, "iTunes_Ctrl_"); - if (dacpid) { - dacpid += strlen("iTunes_Ctrl_"); - if (strcmp(dacpid, dbs->dacp_id) == 0) { - debug(3, "Client's DACP port: %u.", port); - #ifdef HAVE_DACP_CLIENT - dacp_monitor_port_update_callback(dacpid, port); - #endif - #ifdef CONFIG_METADATA - char portstring[20]; - memset(portstring, 0, sizeof(portstring)); - snprintf(portstring, sizeof(portstring), "%u", port); - send_ssnc_metadata('dapo', strdup(portstring), strlen(portstring), 0); - #endif - } - } else { - debug(1, "Resolve callback: Can't see a DACP string in a DACP Record!"); + case AVAHI_RESOLVER_FAILURE: + debug(2, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s.", name, + type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r)))); + break; + case AVAHI_RESOLVER_FOUND: { + // char a[AVAHI_ADDRESS_STR_MAX], *t; + // debug(1, "Resolve callback: Service '%s' of type '%s' in domain '%s':", name, type, domain); + char *dacpid = strstr(name, "iTunes_Ctrl_"); + if (dacpid) { + dacpid += strlen("iTunes_Ctrl_"); + if (strcmp(dacpid, dbs->dacp_id) == 0) { + debug(3, "Client's DACP port: %u.", port); +#ifdef HAVE_DACP_CLIENT + dacp_monitor_port_update_callback(dacpid, port); +#endif +#ifdef CONFIG_METADATA + char portstring[20]; + memset(portstring, 0, sizeof(portstring)); + snprintf(portstring, sizeof(portstring), "%u", port); + send_ssnc_metadata('dapo', strdup(portstring), strlen(portstring), 0); +#endif } + } else { + debug(1, "Resolve callback: Can't see a DACP string in a DACP Record!"); } } + } // debug(1,"service resolver freed by resolve_callback"); check_avahi_response(1, avahi_service_resolver_free(r)); } @@ -164,47 +164,47 @@ static void register_service(AvahiClient *c); static void egroup_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) { switch (state) { - case AVAHI_ENTRY_GROUP_ESTABLISHED: - /* The entry group has been established successfully */ - debug(1, "avahi: service '%s' successfully added.", service_name); - break; + case AVAHI_ENTRY_GROUP_ESTABLISHED: + /* The entry group has been established successfully */ + debug(1, "avahi: service '%s' successfully added.", service_name); + break; - case AVAHI_ENTRY_GROUP_COLLISION: { - char *n; + case AVAHI_ENTRY_GROUP_COLLISION: { + char *n; - /* A service name collision with a remote service - * happened. Let's pick a new name */ - debug(1, "avahi name collision -- look for another"); - n = avahi_alternative_service_name(service_name); - if (service_name) - avahi_free(service_name); - else - debug(1, "avahi attempt to free a NULL service name"); - service_name = n; + /* A service name collision with a remote service + * happened. Let's pick a new name */ + debug(1, "avahi name collision -- look for another"); + n = avahi_alternative_service_name(service_name); + if (service_name) + avahi_free(service_name); + else + debug(1, "avahi attempt to free a NULL service name"); + service_name = n; - debug(2, "avahi: service name collision, renaming service to '%s'", service_name); + debug(2, "avahi: service name collision, renaming service to '%s'", service_name); - /* And recreate the services */ - register_service(avahi_entry_group_get_client(g)); - break; - } + /* And recreate the services */ + register_service(avahi_entry_group_get_client(g)); + break; + } - case AVAHI_ENTRY_GROUP_FAILURE: - debug(1, "avahi: entry group failure: %s", - avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); - break; + case AVAHI_ENTRY_GROUP_FAILURE: + debug(1, "avahi: entry group failure: %s", + avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); + break; - case AVAHI_ENTRY_GROUP_UNCOMMITED: - debug(2, "avahi: service '%s' group is not yet committed.", service_name); - break; + case AVAHI_ENTRY_GROUP_UNCOMMITED: + debug(2, "avahi: service '%s' group is not yet committed.", service_name); + break; - case AVAHI_ENTRY_GROUP_REGISTERING: - debug(2, "avahi: service '%s' group is registering.", service_name); - break; + case AVAHI_ENTRY_GROUP_REGISTERING: + debug(2, "avahi: service '%s' group is registering.", service_name); + break; - default: - debug(1, "avahi: unhandled egroup state: %d", state); - break; + default: + debug(1, "avahi: unhandled egroup state: %d", state); + break; } } @@ -464,7 +464,7 @@ void *avahi_dacp_monitor(char *dacp_id) { } void avahi_dacp_dont_monitor(void *userdata) { - debug(3,"avahi_dacp_dont_monitor"); + debug(3, "avahi_dacp_dont_monitor"); if (userdata) { dacp_browser_struct *dbs = (dacp_browser_struct *)userdata; // stop and dispose of everything @@ -472,13 +472,13 @@ void avahi_dacp_dont_monitor(void *userdata) { avahi_threaded_poll_stop((dbs)->service_poll); */ if (dbs->service_poll) { - avahi_threaded_poll_stop(dbs->service_poll); + avahi_threaded_poll_stop(dbs->service_poll); avahi_threaded_poll_lock(dbs->service_poll); if (dbs->service_browser) avahi_service_browser_free(dbs->service_browser); if (dbs->service_client) avahi_client_free(dbs->service_client); - avahi_threaded_poll_unlock(dbs->service_poll); + avahi_threaded_poll_unlock(dbs->service_poll); avahi_threaded_poll_free(dbs->service_poll); } free(dbs->dacp_id); @@ -487,7 +487,7 @@ void avahi_dacp_dont_monitor(void *userdata) { } else { debug(1, "Avahi DACP Monitor is not running."); } - debug(3,"avahi_dacp_dont_monitor exit"); + debug(3, "avahi_dacp_dont_monitor exit"); } mdns_backend mdns_avahi = {.name = "avahi", diff --git a/metadata_hub.h b/metadata_hub.h index 66368f98..b2084c12 100644 --- a/metadata_hub.h +++ b/metadata_hub.h @@ -56,7 +56,7 @@ typedef struct metadata_bundle { char *client_ip; // IP number used by the audio source (i.e. the "client"), which is also the DACP // server char *server_ip; // IP number used by Shairport Sync - char *progress_string; // progress string, emitted by the source from time to time + char *progress_string; // progress string, emitted by the source from time to time int player_thread_active; // true if a play thread is running int dacp_server_active; // true if there's a reachable DACP server (assumed to be the Airplay // client) ; false otherwise diff --git a/mpris-service.c b/mpris-service.c index 3d6cf96b..4901a819 100644 --- a/mpris-service.c +++ b/mpris-service.c @@ -123,7 +123,8 @@ void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused) } else if ((argc->track_metadata) && (argc->track_metadata->item_id)) { char trackidstring[128]; // debug(1, "Set ID using mper ID: \"%u\".",argc->item_id); - snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/mper_%u", argc->track_metadata->item_id); + snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/mper_%u", + argc->track_metadata->item_id); GVariant *trackid = g_variant_new("o", trackidstring); g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid); } @@ -291,7 +292,7 @@ static void on_mpris_name_lost(__attribute__((unused)) GDBusConnection *connecti // name,(mpris_bus_type==G_BUS_TYPE_SESSION) ? "session" : "system"); pid_t pid = getpid(); char interface_name[256] = ""; - snprintf(interface_name, sizeof(interface_name), "org.mpris.MediaPlayer2.ShairportSync.i%d", pid); + snprintf(interface_name, sizeof(interface_name), "org.mpris.MediaPlayer2.ShairportSync.i%d", pid); GBusType mpris_bus_type = G_BUS_TYPE_SYSTEM; if (config.mpris_service_bus_type == DBT_session) mpris_bus_type = G_BUS_TYPE_SESSION; diff --git a/mqtt.c b/mqtt.c index c08a4318..6d160e81 100644 --- a/mqtt.c +++ b/mqtt.c @@ -11,170 +11,168 @@ #include "rtp.h" #include "dacp.h" -#include #include "mqtt.h" +#include -//this holds the mosquitto client +// this holds the mosquitto client struct mosquitto *global_mosq = NULL; char *topic = NULL; int connected = 0; -//mosquitto logging -void _cb_log( __attribute__((unused)) struct mosquitto *mosq, - __attribute__((unused)) void *userdata, int level, const char *str){ - switch(level){ - case MOSQ_LOG_DEBUG: - debug(1, str); - break; - case MOSQ_LOG_INFO: - debug(2, str); - break; - case MOSQ_LOG_NOTICE: - debug(3, str); - break; - case MOSQ_LOG_WARNING: - inform(str); - break; - case MOSQ_LOG_ERR: { - die("MQTT: Error: %s\n", str); - } +// mosquitto logging +void _cb_log(__attribute__((unused)) struct mosquitto *mosq, __attribute__((unused)) void *userdata, + int level, const char *str) { + switch (level) { + case MOSQ_LOG_DEBUG: + debug(1, str); + break; + case MOSQ_LOG_INFO: + debug(2, str); + break; + case MOSQ_LOG_NOTICE: + debug(3, str); + break; + case MOSQ_LOG_WARNING: + inform(str); + break; + case MOSQ_LOG_ERR: { + die("MQTT: Error: %s\n", str); + } } } -//mosquitto message handler -void on_message( __attribute__((unused)) struct mosquitto* mosq, - __attribute__((unused)) void* userdata, const struct mosquitto_message* msg){ - - //null-terminate the payload - char payload[msg->payloadlen+1]; - memcpy(payload,msg->payload,msg->payloadlen); - payload[msg->payloadlen]=0; - - debug(1, "[MQTT]: received Message on topic %s: %s\n",msg->topic, payload); - - //All recognized commands - char* commands[] = { - "command", "beginff", "beginrew", "mutetoggle", "nextitem", "previtem", "pause", - "playpause", "play", "stop", "playresume", "shuffle_songs", "volumedown", "volumeup", - NULL}; - - int it=0; - - //send command if it's a valid one - while(commands[it++]!=NULL){ - if( (size_t)msg->payloadlen>=strlen(commands[it]) && - strncmp(msg->payload, commands[it], strlen(commands[it]))==0 - ){ - debug(1, "[MQTT]: DACP Command: %s\n",commands[it]); +// mosquitto message handler +void on_message(__attribute__((unused)) struct mosquitto *mosq, + __attribute__((unused)) void *userdata, const struct mosquitto_message *msg) { + + // null-terminate the payload + char payload[msg->payloadlen + 1]; + memcpy(payload, msg->payload, msg->payloadlen); + payload[msg->payloadlen] = 0; + + debug(1, "[MQTT]: received Message on topic %s: %s\n", msg->topic, payload); + + // All recognized commands + char *commands[] = {"command", "beginff", "beginrew", "mutetoggle", "nextitem", + "previtem", "pause", "playpause", "play", "stop", + "playresume", "shuffle_songs", "volumedown", "volumeup", NULL}; + + int it = 0; + + // send command if it's a valid one + while (commands[it++] != NULL) { + if ((size_t)msg->payloadlen >= strlen(commands[it]) && + strncmp(msg->payload, commands[it], strlen(commands[it])) == 0) { + debug(1, "[MQTT]: DACP Command: %s\n", commands[it]); send_simple_dacp_command(commands[it]); break; } } } -void on_disconnect( __attribute__((unused)) struct mosquitto* mosq, - __attribute__((unused)) void* userdata, - __attribute__((unused)) int rc){ +void on_disconnect(__attribute__((unused)) struct mosquitto *mosq, + __attribute__((unused)) void *userdata, __attribute__((unused)) int rc) { connected = 0; debug(1, "[MQTT]: disconnected"); } -void on_connect(struct mosquitto* mosq, - __attribute__((unused)) void* userdata, - __attribute__((unused)) int rc){ +void on_connect(struct mosquitto *mosq, __attribute__((unused)) void *userdata, + __attribute__((unused)) int rc) { connected = 1; debug(1, "[MQTT]: connected"); - - //subscribe if requested - if(config.mqtt_enable_remote){ - char remotetopic[strlen(config.mqtt_topic)+8]; - snprintf(remotetopic,strlen(config.mqtt_topic)+8,"%s/remote",config.mqtt_topic); - mosquitto_subscribe(mosq,NULL,remotetopic,0); + + // subscribe if requested + if (config.mqtt_enable_remote) { + char remotetopic[strlen(config.mqtt_topic) + 8]; + snprintf(remotetopic, strlen(config.mqtt_topic) + 8, "%s/remote", config.mqtt_topic); + mosquitto_subscribe(mosq, NULL, remotetopic, 0); } } -//helper function to publish under a topic and automatically append the main topic -void mqtt_publish(char* topic, char* data, uint32_t length){ - char fulltopic[strlen(config.mqtt_topic)+strlen(topic)+3]; - snprintf(fulltopic, strlen(config.mqtt_topic)+strlen(topic)+2, "%s/%s", config.mqtt_topic, topic); - debug(1, "[MQTT]: publishing under %s",fulltopic); - +// helper function to publish under a topic and automatically append the main topic +void mqtt_publish(char *topic, char *data, uint32_t length) { + char fulltopic[strlen(config.mqtt_topic) + strlen(topic) + 3]; + snprintf(fulltopic, strlen(config.mqtt_topic) + strlen(topic) + 2, "%s/%s", config.mqtt_topic, + topic); + debug(1, "[MQTT]: publishing under %s", fulltopic); + int rc; - if((rc=mosquitto_publish(global_mosq, NULL, fulltopic, length, data, 0, 0))!=MOSQ_ERR_SUCCESS) { - switch(rc){ - case MOSQ_ERR_NO_CONN: - debug(1, "[MQTT]: Publish failed: not connected to broker"); - break; - default: - debug(1, "[MQTT]: Publish failed: unknown error"); - break; + if ((rc = mosquitto_publish(global_mosq, NULL, fulltopic, length, data, 0, 0)) != + MOSQ_ERR_SUCCESS) { + switch (rc) { + case MOSQ_ERR_NO_CONN: + debug(1, "[MQTT]: Publish failed: not connected to broker"); + break; + default: + debug(1, "[MQTT]: Publish failed: unknown error"); + break; } } } -//handler for incoming metadata -void mqtt_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t length){ - if(global_mosq==NULL || connected!=1){ +// handler for incoming metadata +void mqtt_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t length) { + if (global_mosq == NULL || connected != 1) { debug(3, "[MQTT]: Client not connected, skipping metadata handling"); return; } - if(config.mqtt_publish_raw){ + if (config.mqtt_publish_raw) { uint32_t val; char topic[] = "____/____"; - - val=htonl(type); - memcpy(topic,&val, 4); - val=htonl(code); - memcpy(topic+5,&val, 4); + + val = htonl(type); + memcpy(topic, &val, 4); + val = htonl(code); + memcpy(topic + 5, &val, 4); mqtt_publish(topic, data, length); } - if(config.mqtt_publish_parsed){ - if(type=='core'){ + if (config.mqtt_publish_parsed) { + if (type == 'core') { switch (code) { - case 'asar': - mqtt_publish("artist", data, length); - break; - case 'asal': - mqtt_publish("album", data, length); - break; - case 'minm': - mqtt_publish("title", data, length); - break; - case 'asgn': - mqtt_publish("genre", data, length); - break; - case 'asfm': - mqtt_publish("format", data, length); - break; + case 'asar': + mqtt_publish("artist", data, length); + break; + case 'asal': + mqtt_publish("album", data, length); + break; + case 'minm': + mqtt_publish("title", data, length); + break; + case 'asgn': + mqtt_publish("genre", data, length); + break; + case 'asfm': + mqtt_publish("format", data, length); + break; } - }else if(type=='ssnc'){ + } else if (type == 'ssnc') { switch (code) { - case 'asal': - mqtt_publish("songalbum", data, length); - break; - case 'pvol': - mqtt_publish("volume", data, length); - break; - case 'clip': - mqtt_publish("client_ip", data, length); - break; - case 'pbeg': - mqtt_publish("play_start", data, length); - break; - case 'pend': - mqtt_publish("play_end", data, length); - break; - case 'pfls': - mqtt_publish("play_flush", data, length); - break; - case 'prsm': - mqtt_publish("play_resume", data, length); - break; - case 'PICT': - if(config.mqtt_publish_parsed){ - mqtt_publish("cover", data, length); - } - break; + case 'asal': + mqtt_publish("songalbum", data, length); + break; + case 'pvol': + mqtt_publish("volume", data, length); + break; + case 'clip': + mqtt_publish("client_ip", data, length); + break; + case 'pbeg': + mqtt_publish("play_start", data, length); + break; + case 'pend': + mqtt_publish("play_end", data, length); + break; + case 'pfls': + mqtt_publish("play_flush", data, length); + break; + case 'prsm': + mqtt_publish("play_resume", data, length); + break; + case 'PICT': + if (config.mqtt_publish_parsed) { + mqtt_publish("cover", data, length); + } + break; } } } @@ -182,50 +180,44 @@ void mqtt_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t le return; } - int initialise_mqtt() { debug(1, "Initialising MQTT"); - if(config.mqtt_hostname==NULL){ + if (config.mqtt_hostname == NULL) { debug(1, "[MQTT]: Not initialized, as the hostname is not set"); return 0; } int keepalive = 60; mosquitto_lib_init(); - if( !(global_mosq = mosquitto_new(config.service_name, true, NULL)) ){ + if (!(global_mosq = mosquitto_new(config.service_name, true, NULL))) { die("[MQTT]: FATAL: Could not create mosquitto object! %d\n", global_mosq); } - if( - config.mqtt_cafile != NULL || - config.mqtt_capath != NULL || - config.mqtt_certfile != NULL || - config.mqtt_keyfile != NULL - ){ - if(mosquitto_tls_set(global_mosq,config.mqtt_cafile, config.mqtt_capath, config.mqtt_certfile, config.mqtt_keyfile, NULL) != MOSQ_ERR_SUCCESS) { + if (config.mqtt_cafile != NULL || config.mqtt_capath != NULL || config.mqtt_certfile != NULL || + config.mqtt_keyfile != NULL) { + if (mosquitto_tls_set(global_mosq, config.mqtt_cafile, config.mqtt_capath, config.mqtt_certfile, + config.mqtt_keyfile, NULL) != MOSQ_ERR_SUCCESS) { die("[MQTT]: TLS Setup failed"); } } - if( - config.mqtt_username != NULL || - config.mqtt_password != NULL - ){ - if(mosquitto_username_pw_set(global_mosq,config.mqtt_username,config.mqtt_password) != MOSQ_ERR_SUCCESS) { + if (config.mqtt_username != NULL || config.mqtt_password != NULL) { + if (mosquitto_username_pw_set(global_mosq, config.mqtt_username, config.mqtt_password) != + MOSQ_ERR_SUCCESS) { die("[MQTT]: Username/Password set failed"); } } mosquitto_log_callback_set(global_mosq, _cb_log); - - if(config.mqtt_enable_remote){ + + if (config.mqtt_enable_remote) { mosquitto_message_callback_set(global_mosq, on_message); } - + mosquitto_disconnect_callback_set(global_mosq, on_disconnect); mosquitto_connect_callback_set(global_mosq, on_connect); - if(mosquitto_connect(global_mosq, config.mqtt_hostname, config.mqtt_port, keepalive)){ + if (mosquitto_connect(global_mosq, config.mqtt_hostname, config.mqtt_port, keepalive)) { inform("[MQTT]: Could not establish a mqtt connection"); } - if(mosquitto_loop_start(global_mosq) != MOSQ_ERR_SUCCESS){ + if (mosquitto_loop_start(global_mosq) != MOSQ_ERR_SUCCESS) { inform("[MQTT]: Could start MQTT Main loop"); } diff --git a/mqtt.h b/mqtt.h index b5ec5ab0..4bf680ed 100644 --- a/mqtt.h +++ b/mqtt.h @@ -1,15 +1,14 @@ #ifndef MQTT_H #define MQTT_H -#include #include - +#include int initialise_mqtt(); void mqtt_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t length); -void mqtt_publish(char* topic, char* data, uint32_t length); +void mqtt_publish(char *topic, char *data, uint32_t length); void mqtt_setup(); -void on_connect(struct mosquitto* mosq, void* userdata, int rc); -void on_disconnect(struct mosquitto* mosq, void* userdata, int rc); -void on_message(struct mosquitto* mosq, void* userdata, const struct mosquitto_message* msg); +void on_connect(struct mosquitto *mosq, void *userdata, int rc); +void on_disconnect(struct mosquitto *mosq, void *userdata, int rc); +void on_message(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *msg); void _cb_log(struct mosquitto *mosq, void *userdata, int level, const char *str); #endif /* #ifndef MQTT_H */ diff --git a/player.c b/player.c index 3c60e24d..2a659f6e 100644 --- a/player.c +++ b/player.c @@ -315,18 +315,16 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_co } if (outsize > toutsize) { - debug(2, - "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- " - "truncated, but buffer overflow possible! Encrypted = %d.", + debug(2, "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- " + "truncated, but buffer overflow possible! Encrypted = %d.", outsize, toutsize, conn->stream.encrypted); reply = -1; // output packet is the wrong size } *destlen = outsize / conn->input_bytes_per_frame; if ((outsize % conn->input_bytes_per_frame) != 0) - debug(1, - "Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) " - "and the audio frame size (%d).", + debug(1, "Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) " + "and the audio frame size (%d).", *destlen, outsize, conn->input_bytes_per_frame); return reply; } @@ -420,7 +418,8 @@ static int init_decoder(int32_t fmtp[12], rtsp_conn_info *conn) { conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); - alac = alac_create(conn->input_bit_depth, conn->input_num_channels); + alac = alac_create(conn->input_bit_depth, + conn->input_num_channels); // no pthread cancellation point in here if (!alac) return 1; conn->decoder_info = alac; @@ -436,10 +435,10 @@ static int init_decoder(int32_t fmtp[12], rtsp_conn_info *conn) { alac->setinfo_82 = fmtp[9]; alac->setinfo_86 = fmtp[10]; alac->setinfo_8a_rate = fmtp[11]; - alac_allocate_buffers(alac); + alac_allocate_buffers(alac); // no pthread cancellation point in here #ifdef HAVE_APPLE_ALAC - apple_alac_init(fmtp); + apple_alac_init(fmtp); // no pthread cancellation point in here #endif return 0; @@ -465,193 +464,172 @@ static void free_audio_buffers(rtsp_conn_info *conn) { free(conn->audio_buffer[i].data); } -void player_thread_lock_cleanup(void *arg) { - rtsp_conn_info *conn = (rtsp_conn_info *)arg; - debug(3, "Cleaning up player_thread_lock."); - pthread_rwlock_unlock(&conn->player_thread_lock); -} - void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp, uint8_t *data, int len, rtsp_conn_info *conn) { - if (pthread_rwlock_tryrdlock(&conn->player_thread_lock) == 0) { - pthread_cleanup_push(player_thread_lock_cleanup, (void *)conn); - if (conn->player_thread != NULL) { + // all timestamps are done at the output rate + // the "actual_timestamp" is the one that comes in the packet, and is carried over for + // debugging + // and checking only. - // all timestamps are done at the output rate - // the "actual_timestamp" is the one that comes in the packet, and is carried over for - // debugging - // and checking only. + int64_t ltimestamp = timestamp * conn->output_sample_ratio; - int64_t ltimestamp = timestamp * conn->output_sample_ratio; + // ignore a request to flush that has been made before the first packet... + if (conn->packet_count == 0) { + debug_mutex_lock(&conn->flush_mutex, 1000, 1); + conn->flush_requested = 0; + conn->flush_rtp_timestamp = 0; + debug_mutex_unlock(&conn->flush_mutex, 3); + } - // ignore a request to flush that has been made before the first packet... - if (conn->packet_count == 0) { - debug_mutex_lock(&conn->flush_mutex, 1000, 1); - conn->flush_requested = 0; - conn->flush_rtp_timestamp = 0; - debug_mutex_unlock(&conn->flush_mutex, 3); + debug_mutex_lock(&conn->ab_mutex, 30000, 1); + conn->packet_count++; + conn->time_of_last_audio_packet = get_absolute_time_in_fp(); + if (conn->connection_state_to_output) { // if we are supposed to be processing these packets + + // if (flush_rtp_timestamp != 0) + // debug(1,"Flush_rtp_timestamp is %u",flush_rtp_timestamp); + + if ((conn->flush_rtp_timestamp != 0) && (ltimestamp <= conn->flush_rtp_timestamp)) { + debug(3, + "Dropping flushed packet in player_put_packet, seqno %u, timestamp %lld, flushing to " + "timestamp: %lld.", + seqno, ltimestamp, conn->flush_rtp_timestamp); + } else { + if ((conn->flush_rtp_timestamp != 0x0) && + (ltimestamp > conn->flush_rtp_timestamp)) // if we have gone past the flush boundary time + conn->flush_rtp_timestamp = 0x0; + + abuf_t *abuf = 0; + + if (!conn->ab_synced) { + debug(3, "syncing to seqno %u.", seqno); + conn->ab_write = seqno; + conn->ab_read = seqno; + conn->ab_synced = 1; } - debug_mutex_lock(&conn->ab_mutex, 30000, 1); - conn->packet_count++; - conn->time_of_last_audio_packet = get_absolute_time_in_fp(); - if (conn->connection_state_to_output) { // if we are supposed to be processing these packets + // here, we should check for missing frames + int resend_interval = (((250 * 44100) / 352) / 1000); // approximately 250 ms intervals + const int number_of_resend_attempts = 8; + int latency_based_resend_interval = + (conn->latency) / (number_of_resend_attempts * conn->max_frames_per_packet); + if (latency_based_resend_interval > resend_interval) + resend_interval = latency_based_resend_interval; - // if (flush_rtp_timestamp != 0) - // debug(1,"Flush_rtp_timestamp is %u",flush_rtp_timestamp); + if (conn->resend_interval != resend_interval) { + debug(2, "Resend interval for latency of %" PRId64 " frames is %d frames.", conn->latency, + resend_interval); + conn->resend_interval = resend_interval; + } - if ((conn->flush_rtp_timestamp != 0) && (ltimestamp <= conn->flush_rtp_timestamp)) { - debug( - 3, - "Dropping flushed packet in player_put_packet, seqno %u, timestamp %lld, flushing to " - "timestamp: %lld.", - seqno, ltimestamp, conn->flush_rtp_timestamp); + if (conn->ab_write == seqno) { // expected packet + abuf = conn->audio_buffer + BUFIDX(seqno); + conn->ab_write = SUCCESSOR(seqno); + } else if (seq_order(conn->ab_write, seqno, conn->ab_read)) { // newer than expected + // if (ORDINATE(seqno)>(BUFFER_FRAMES*7)/8) + // debug(1,"An interval of %u frames has opened, with ab_read: %u, ab_write: %u and + // seqno: + // %u.",seq_diff(ab_read,seqno),ab_read,ab_write,seqno); + int32_t gap = seq_diff(conn->ab_write, seqno, conn->ab_read); + if (gap <= 0) + debug(1, "Unexpected gap size: %d.", gap); + int i; + for (i = 0; i < gap; i++) { + abuf = conn->audio_buffer + BUFIDX(seq_sum(conn->ab_write, i)); + abuf->ready = 0; // to be sure, to be sure + abuf->resend_level = 0; + abuf->timestamp = 0; + abuf->given_timestamp = 0; + abuf->sequence_number = 0; + } + // debug(1,"N %d s %u.",seq_diff(ab_write,PREDECESSOR(seqno))+1,ab_write); + abuf = conn->audio_buffer + BUFIDX(seqno); + // rtp_request_resend(ab_write, gap); + // resend_requests++; + conn->ab_write = SUCCESSOR(seqno); + } else if (seq_order(conn->ab_read, seqno, conn->ab_read)) { // late but not yet played + conn->late_packets++; + abuf = conn->audio_buffer + BUFIDX(seqno); + /* + if (abuf->ready) + debug(1,"Late apparently duplicate packet received that is %d packets + late.",seq_diff(seqno, conn->ab_write, conn->ab_read)); + else + debug(1,"Late packet received that is %d packets late.",seq_diff(seqno, + conn->ab_write, conn->ab_read)); + */ + } else { // too late. + + // debug(1,"Too late packet received that is %d packets late.",seq_diff(seqno, + // conn->ab_write, conn->ab_read)); + conn->too_late_packets++; + } + // pthread_mutex_unlock(&ab_mutex); + + if (abuf) { + int datalen = conn->max_frames_per_packet; + if (alac_decode(abuf->data, &datalen, data, len, conn) == 0) { + abuf->ready = 1; + abuf->length = datalen; + abuf->timestamp = ltimestamp; + abuf->given_timestamp = actual_timestamp; + abuf->sequence_number = seqno; } else { - if ((conn->flush_rtp_timestamp != 0x0) && - (ltimestamp > - conn->flush_rtp_timestamp)) // if we have gone past the flush boundary time - conn->flush_rtp_timestamp = 0x0; + debug(1, "Bad audio packet detected and discarded."); + abuf->ready = 0; + abuf->resend_level = 0; + abuf->timestamp = 0; + abuf->given_timestamp = 0; + abuf->sequence_number = 0; + } + } - abuf_t *abuf = 0; + // pthread_mutex_lock(&ab_mutex); + int rc = pthread_cond_signal(&conn->flowcontrol); + if (rc) + debug(1, "Error signalling flowcontrol."); - if (!conn->ab_synced) { - debug(3, "syncing to seqno %u.", seqno); - conn->ab_write = seqno; - conn->ab_read = seqno; - conn->ab_synced = 1; - } - - // here, we should check for missing frames - int resend_interval = (((250 * 44100) / 352) / 1000); // approximately 250 ms intervals - const int number_of_resend_attempts = 8; - int latency_based_resend_interval = - (conn->latency) / (number_of_resend_attempts * conn->max_frames_per_packet); - if (latency_based_resend_interval > resend_interval) - resend_interval = latency_based_resend_interval; - - if (conn->resend_interval != resend_interval) { - debug(2, "Resend interval for latency of %" PRId64 " frames is %d frames.", - conn->latency, resend_interval); - conn->resend_interval = resend_interval; - } - - if (conn->ab_write == seqno) { // expected packet - abuf = conn->audio_buffer + BUFIDX(seqno); - conn->ab_write = SUCCESSOR(seqno); - } else if (seq_order(conn->ab_write, seqno, conn->ab_read)) { // newer than expected - // if (ORDINATE(seqno)>(BUFFER_FRAMES*7)/8) - // debug(1,"An interval of %u frames has opened, with ab_read: %u, ab_write: %u and - // seqno: - // %u.",seq_diff(ab_read,seqno),ab_read,ab_write,seqno); - int32_t gap = seq_diff(conn->ab_write, seqno, conn->ab_read); - if (gap <= 0) - debug(1, "Unexpected gap size: %d.", gap); - int i; - for (i = 0; i < gap; i++) { - abuf = conn->audio_buffer + BUFIDX(seq_sum(conn->ab_write, i)); - abuf->ready = 0; // to be sure, to be sure - abuf->resend_level = 0; - abuf->timestamp = 0; - abuf->given_timestamp = 0; - abuf->sequence_number = 0; - } - // debug(1,"N %d s %u.",seq_diff(ab_write,PREDECESSOR(seqno))+1,ab_write); - abuf = conn->audio_buffer + BUFIDX(seqno); - // rtp_request_resend(ab_write, gap); - // resend_requests++; - conn->ab_write = SUCCESSOR(seqno); - } else if (seq_order(conn->ab_read, seqno, conn->ab_read)) { // late but not yet played - conn->late_packets++; - abuf = conn->audio_buffer + BUFIDX(seqno); - /* - if (abuf->ready) - debug(1,"Late apparently duplicate packet received that is %d packets - late.",seq_diff(seqno, conn->ab_write, conn->ab_read)); - else - debug(1,"Late packet received that is %d packets late.",seq_diff(seqno, - conn->ab_write, conn->ab_read)); - */ - } else { // too late. - - // debug(1,"Too late packet received that is %d packets late.",seq_diff(seqno, - // conn->ab_write, conn->ab_read)); - conn->too_late_packets++; - } - // pthread_mutex_unlock(&ab_mutex); - - if (abuf) { - int datalen = conn->max_frames_per_packet; - if (alac_decode(abuf->data, &datalen, data, len, conn) == 0) { - abuf->ready = 1; - abuf->length = datalen; - abuf->timestamp = ltimestamp; - abuf->given_timestamp = actual_timestamp; - abuf->sequence_number = seqno; - } else { - debug(1, "Bad audio packet detected and discarded."); - abuf->ready = 0; - abuf->resend_level = 0; - abuf->timestamp = 0; - abuf->given_timestamp = 0; - abuf->sequence_number = 0; - } - } - - // pthread_mutex_lock(&ab_mutex); - int rc = pthread_cond_signal(&conn->flowcontrol); - if (rc) - debug(1, "Error signalling flowcontrol."); - - // if it's at the expected time, do a look back for missing packets - // but release the ab_mutex when doing a resend - if (!conn->ab_buffering) { - int j; - for (j = 1; j <= number_of_resend_attempts; j++) { - // check j times, after a short period of has elapsed, assuming 352 frames per packet - // the higher the step_exponent, the less it will try. 1 means it will try very - // hard. 2.0 seems good. - float step_exponent = 2.0; - int back_step = (int)(resend_interval * pow(j, step_exponent)); - int k; - for (k = -1; k <= 1; k++) { - if ((back_step + k) < - seq_diff(conn->ab_read, conn->ab_write, - conn->ab_read)) { // if it's within the range of frames in use... - int item_to_check = (conn->ab_write - (back_step + k)) & 0xffff; - seq_t next = item_to_check; - abuf_t *check_buf = conn->audio_buffer + BUFIDX(next); - if ((!check_buf->ready) && - (check_buf->resend_level < - j)) { // prevent multiple requests from the same level of lookback - check_buf->resend_level = j; - if (config.disable_resend_requests == 0) { - if (((int)(resend_interval * pow(j + 1, step_exponent)) + k) >= - seq_diff(conn->ab_read, conn->ab_write, conn->ab_read)) - debug(3, - "Last-ditch (#%d) resend request for packet %u in range %u to %u. " - "Looking back %d packets.", - j, next, conn->ab_read, conn->ab_write, back_step + k); - debug_mutex_unlock(&conn->ab_mutex, 3); - rtp_request_resend(next, 1, conn); - conn->resend_requests++; - debug_mutex_lock(&conn->ab_mutex, 20000, 1); - } - } + // if it's at the expected time, do a look back for missing packets + // but release the ab_mutex when doing a resend + if (!conn->ab_buffering) { + int j; + for (j = 1; j <= number_of_resend_attempts; j++) { + // check j times, after a short period of has elapsed, assuming 352 frames per packet + // the higher the step_exponent, the less it will try. 1 means it will try very + // hard. 2.0 seems good. + float step_exponent = 2.0; + int back_step = (int)(resend_interval * pow(j, step_exponent)); + int k; + for (k = -1; k <= 1; k++) { + if ((back_step + k) < + seq_diff(conn->ab_read, conn->ab_write, + conn->ab_read)) { // if it's within the range of frames in use... + int item_to_check = (conn->ab_write - (back_step + k)) & 0xffff; + seq_t next = item_to_check; + abuf_t *check_buf = conn->audio_buffer + BUFIDX(next); + if ((!check_buf->ready) && + (check_buf->resend_level < + j)) { // prevent multiple requests from the same level of lookback + check_buf->resend_level = j; + if (config.disable_resend_requests == 0) { + if (((int)(resend_interval * pow(j + 1, step_exponent)) + k) >= + seq_diff(conn->ab_read, conn->ab_write, conn->ab_read)) + debug(3, "Last-ditch (#%d) resend request for packet %u in range %u to %u. " + "Looking back %d packets.", + j, next, conn->ab_read, conn->ab_write, back_step + k); + debug_mutex_unlock(&conn->ab_mutex, 3); + rtp_request_resend(next, 1, conn); + conn->resend_requests++; + debug_mutex_lock(&conn->ab_mutex, 20000, 1); } } } } } } - debug_mutex_unlock(&conn->ab_mutex, 3); - } else { - debug(1, "player_put_packet discarded packet %d because the player thread was gone."); } - pthread_cleanup_pop(1); - // pthread_rwlock_unlock(&conn->player_thread_lock); - } else { - debug(1, "player_put_packet discarded packet %d because the player thread was locked.", seqno); } + debug_mutex_unlock(&conn->ab_mutex, 3); } int32_t rand_in_range(int32_t exclusive_range_limit) { @@ -820,9 +798,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // if (conn->packet_count>500) { //for testing -- about 4 seconds of play first if ((local_time_now > conn->time_of_last_audio_packet) && (local_time_now - conn->time_of_last_audio_packet >= ct << 32)) { - debug(1, - "As Yeats almost said, \"Too long a silence / can make a stone of the heart\" " - "from RTSP conversation %d.", + debug(1, "As Yeats almost said, \"Too long a silence / can make a stone of the heart\" " + "from RTSP conversation %d.", conn->connection_number); conn->stop = 1; pthread_kill(conn->thread, SIGUSR1); @@ -1439,36 +1416,14 @@ typedef struct stats { // statistics for running averages int64_t sync_error, correction, drift; } stats_t; -void *player_thread_func(void *arg) { - // note, the thread will be started up with the player_thread_lock locked. You must release it - // quickly. - +void player_thread_cleanup_handler(void *arg) { + debug(1, "player_thread_cleanup_handler called"); rtsp_conn_info *conn = (rtsp_conn_info *)arg; +} - pthread_rwlock_wrlock(&conn->player_thread_lock); - - int rc = pthread_mutex_init(&conn->ab_mutex, NULL); - if (rc) - debug(1, "Error initialising ab_mutex."); - rc = pthread_mutex_init(&conn->flush_mutex, NULL); - if (rc) - debug(1, "Error initialising flush_mutex."); -// set the flowcontrol condition variable to wait on a monotonic clock -#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD - pthread_condattr_t attr; - pthread_condattr_init(&attr); - pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it. - rc = pthread_cond_init(&conn->flowcontrol, &attr); -#endif -#ifdef COMPILE_FOR_OSX - rc = pthread_cond_init(&conn->flowcontrol, NULL); -#endif - if (rc) - debug(1, "Error initialising flowcontrol condition variable."); - - pthread_rwlock_unlock(&conn->player_thread_lock); - - // it's safe now +void *player_thread_func(void *arg) { + pthread_cleanup_push(player_thread_cleanup_handler, arg); + rtsp_conn_info *conn = (rtsp_conn_info *)arg; conn->packet_count = 0; conn->previous_random_number = 0; @@ -1487,12 +1442,13 @@ void *player_thread_func(void *arg) { conn->latency = 88200; } - config.output->start(config.output_rate, config.output_format); + config.output->start(config.output_rate, config.output_format); // will need a corresponding stop init_decoder((int32_t *)&conn->stream.fmtp, - conn); // this sets up incoming rate, bit depth, channels - // must be after decoder init - init_buffer(conn); + conn); // this sets up incoming rate, bit depth, channels. + // No pthread cancellation point in here + // This must be after init_decoder + init_buffer(conn); // will need a corresponding deallocation if (conn->stream.encrypted) { #ifdef HAVE_LIBMBEDTLS @@ -1639,18 +1595,16 @@ void *player_thread_func(void *arg) { // if ((input_rate!=config.output_rate) || (input_bit_depth!=output_bit_depth)) { // debug(1,"Define tbuf of length // %d.",output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); - tbuf = malloc( - sizeof(int32_t) * 2 * - (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change)); + tbuf = malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + + conn->max_frame_size_change)); if (tbuf == NULL) die("Failed to allocate memory for the transition buffer."); sbuf = 0; // initialise this, because soxr stuffing might be chosen later - sbuf = malloc( - sizeof(int32_t) * 2 * - (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change)); + sbuf = malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + + conn->max_frame_size_change)); if (sbuf == NULL) debug(1, "Failed to allocate memory for the sbuf buffer."); @@ -1670,7 +1624,8 @@ void *player_thread_func(void *arg) { // stop looking elsewhere for DACP stuff #ifdef HAVE_DACP_CLIENT - set_dacp_server_information(conn); // this will start scanning when a port is registered by the + // this may have pthread cancellation points in it + set_dacp_server_information(conn); // this will start scanning when a port is registered by the // code initiated by the mdns_dacp_monitor #else // this is only used for compatability, if dacp stuff isn't enabled. @@ -1679,7 +1634,7 @@ void *player_thread_func(void *arg) { if (conn->dapo_private_storage) debug(1, "DACP monitor already initialised?"); else - conn->dapo_private_storage = mdns_dacp_monitor(conn->dacp_id); + conn->dapo_private_storage = mdns_dacp_monitor(conn->dacp_id); // ?? #endif conn->framesProcessedInThisEpoch = 0; @@ -1726,7 +1681,7 @@ void *player_thread_func(void *arg) { // set the default volume to whaterver it was before, as stored in the config airplay_volume debug(3, "Set initial volume to %f.", config.airplay_volume); - player_volume(config.airplay_volume, conn); + player_volume(config.airplay_volume, conn); // ?? int64_t frames_to_drop = 0; // debug(1, "Play begin"); while (!conn->player_thread_please_stop) { @@ -1752,9 +1707,8 @@ void *player_thread_func(void *arg) { } else { // the player may change the contents of the buffer, so it has to be zeroed each time; // might as well malloc and freee it locally - memset(silence, 0, - conn->output_bytes_per_frame * conn->max_frames_per_packet * - conn->output_sample_ratio); + memset(silence, 0, conn->output_bytes_per_frame * conn->max_frames_per_packet * + conn->output_sample_ratio); config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio); free(silence); } @@ -1774,9 +1728,8 @@ void *player_thread_func(void *arg) { } else { // the player may change the contents of the buffer, so it has to be zeroed each time; // might as well malloc and freee it locally - memset(silence, 0, - conn->output_bytes_per_frame * conn->max_frames_per_packet * - conn->output_sample_ratio); + memset(silence, 0, conn->output_bytes_per_frame * conn->max_frames_per_packet * + conn->output_sample_ratio); config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio); free(silence); } @@ -1923,9 +1876,8 @@ void *player_thread_func(void *arg) { SUCCESSOR(conn->last_seqno_read); // int32_t from seq_t, i.e. uint16_t, so okay. if (inframe->sequence_number != conn->last_seqno_read) { // seq_t, ei.e. uint16_t and int32_t, so okay - debug(2, - "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u " - "and ab_write: %u.", + debug(2, "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u " + "and ab_write: %u.", conn->last_seqno_read, inframe->sequence_number, conn->ab_read, conn->ab_write); conn->last_seqno_read = inframe->sequence_number; // reset warning... } @@ -2076,7 +2028,7 @@ void *player_thread_func(void *arg) { #ifdef CONFIG_CONVOLUTION || config.convolution #endif - ) { + ) { int32_t *tbuf32 = (int32_t *)tbuf; float fbuf_l[inbuflength]; float fbuf_r[inbuflength]; @@ -2278,10 +2230,10 @@ void *player_thread_func(void *arg) { "%*d", /* max buffer occupancy */ 9, /* should be 10, but there's an explicit space at the start to ensure alignment */ - 1000 * moving_average_sync_error / config.output_rate, 10, - moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), 10, - moving_average_insertions_plus_deletions * 1000000 / - (352 * conn->output_sample_ratio), + 1000 * moving_average_sync_error / config.output_rate, + 10, moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), + 10, moving_average_insertions_plus_deletions * 1000000 / + (352 * conn->output_sample_ratio), 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy); @@ -2297,10 +2249,11 @@ void *player_thread_func(void *arg) { "%*d", /* max buffer occupancy */ 9, /* should be 10, but there's an explicit space at the start to ensure alignment */ - 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, - conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, - conn->resend_requests, 7, minimum_dac_queue_size, 5, - minimum_buffer_occupancy, 5, maximum_buffer_occupancy); + 1000 * moving_average_sync_error / config.output_rate, + 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, + conn->too_late_packets, 7, conn->resend_requests, 7, + minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, + maximum_buffer_occupancy); } } else { inform(" %*.1f," /* Sync error in milliseconds */ @@ -2313,10 +2266,10 @@ void *player_thread_func(void *arg) { "%*d", /* max buffer occupancy */ 9, /* should be 10, but there's an explicit space at the start to ensure alignment */ - 1000 * moving_average_sync_error / config.output_rate, 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); + 1000 * moving_average_sync_error / config.output_rate, + 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); } } else { inform("No frames received in the last sampling interval."); @@ -2381,16 +2334,6 @@ void *player_thread_func(void *arg) { free_audio_buffers(conn); terminate_decoders(conn); - // remove flow control and mutexes - rc = pthread_cond_destroy(&conn->flowcontrol); - if (rc) - debug(1, "Error destroying flowcontrol condition variable."); - rc = pthread_mutex_destroy(&conn->flush_mutex); - if (rc) - debug(1, "Error destroying flush_mutex variable."); - rc = pthread_mutex_destroy(&conn->ab_mutex); - if (rc) - debug(1, "Error destroying ab_mutex variable."); debug(2, "Connection %d: player thread terminated.", conn->connection_number); if (conn->dacp_id) { free(conn->dacp_id); @@ -2402,6 +2345,7 @@ void *player_thread_func(void *arg) { free(tbuf); if (sbuf) free(sbuf); + pthread_cleanup_pop(1); pthread_exit(NULL); } @@ -2640,14 +2584,16 @@ void player_volume(double airplay_volume, rtsp_conn_info *conn) { } void do_flush(int64_t timestamp, rtsp_conn_info *conn) { + debug(3, "Flush requested up to %u. It seems as if 0 is special.", timestamp); debug_mutex_lock(&conn->flush_mutex, 1000, 1); conn->flush_requested = 1; // if (timestamp!=0) conn->flush_rtp_timestamp = timestamp; // flush all packets up to (and including?) this - debug_mutex_unlock(&conn->flush_mutex, 3); conn->play_segment_reference_frame = 0; conn->play_number_after_flush = 0; + debug_mutex_unlock(&conn->flush_mutex, 3); + #ifdef CONFIG_METADATA // only send a flush metadata message if the first packet has been seen -- it's a bogus message // otherwise @@ -2656,20 +2602,13 @@ void do_flush(int64_t timestamp, rtsp_conn_info *conn) { send_ssnc_metadata('pfls', NULL, 0, 1); } #endif + debug(3, "Flush request made."); } void player_flush(int64_t timestamp, rtsp_conn_info *conn) { debug(3, "player_flush"); - if (pthread_rwlock_tryrdlock(&conn->player_thread_lock) == 0) { - if (conn->player_thread != NULL) - do_flush(timestamp, conn); - else - debug(1, "Flush requested when player thread is gone."); - pthread_rwlock_unlock(&conn->player_thread_lock); - } else { - debug(3, "Can't acquire a read lock for a flush -- ignored."); - } + do_flush(timestamp, conn); } int player_play(rtsp_conn_info *conn) { @@ -2694,8 +2633,6 @@ int player_play(rtsp_conn_info *conn) { int rc = pthread_attr_setstacksize(&tattr, size); if (rc) debug(1, "Error setting stack size for player_thread: %s", strerror(errno)); - - // hack alert -- the player thread itself releases the player_thread_lock rwlock as soon as it's // finished initialising. pthread_create(pt, &tattr, player_thread_func, (void *)conn); pthread_attr_destroy(&tattr); @@ -2704,8 +2641,6 @@ int player_play(rtsp_conn_info *conn) { int player_stop(rtsp_conn_info *conn) { debug(3, "player_stop"); - pthread_rwlock_wrlock(&conn->player_thread_lock); - debug(3, "player_thread_lock acquired"); if (conn->player_thread) { debug(3, "player_thread exists"); conn->player_thread_please_stop = 1; @@ -2715,7 +2650,6 @@ int player_stop(rtsp_conn_info *conn) { pthread_join(*conn->player_thread, NULL); free(conn->player_thread); conn->player_thread = NULL; - pthread_rwlock_unlock(&conn->player_thread_lock); #ifdef CONFIG_METADATA debug(2, "pend"); send_ssnc_metadata('pend', NULL, 0, 1); @@ -2723,8 +2657,7 @@ int player_stop(rtsp_conn_info *conn) { command_stop(); return 0; } else { - pthread_rwlock_unlock(&conn->player_thread_lock); - debug(3, "player thread of RTSP conversation %d is already deleted.", conn->connection_number); + debug(3, "Connection %d: player thread already deleted.", conn->connection_number); return -1; } } diff --git a/player.h b/player.h index 3274d648..4c12c803 100644 --- a/player.h +++ b/player.h @@ -85,7 +85,6 @@ typedef struct { // pthread_t *ptp; pthread_t *player_thread; - pthread_rwlock_t player_thread_lock; // used to control access by "outsiders" abuf_t audio_buffer[BUFFER_FRAMES]; int max_frames_per_packet, input_num_channels, input_bit_depth, input_rate; diff --git a/rtp.c b/rtp.c index 6a588b0b..c86d3fa0 100644 --- a/rtp.c +++ b/rtp.c @@ -68,9 +68,9 @@ void rtp_terminate(rtsp_conn_info *conn) { void rtp_audio_receiver_cleanup_handler(void *arg) { debug(3, "Audio Receiver Cleanup."); rtsp_conn_info *conn = (rtsp_conn_info *)arg; - debug(1,"shutdown audio socket."); - shutdown(conn->audio_socket,SHUT_RDWR); - debug(1,"close audio socket."); + debug(1, "shutdown audio socket."); + shutdown(conn->audio_socket, SHUT_RDWR); + debug(1, "close audio socket."); close(conn->audio_socket); debug(3, "Audio Receiver Cleanup Successful."); } @@ -191,9 +191,9 @@ void *rtp_audio_receiver(void *arg) { void rtp_control_handler_cleanup_handler(void *arg) { debug(3, "Control Receiver Cleanup."); rtsp_conn_info *conn = (rtsp_conn_info *)arg; - debug(1,"shutdown control socket."); - shutdown(conn->control_socket,SHUT_RDWR); - debug(1,"close control socket."); + debug(1, "shutdown control socket."); + shutdown(conn->control_socket, SHUT_RDWR); + debug(1, "close control socket."); close(conn->control_socket); debug(3, "Control Receiver Cleanup Successful."); } @@ -477,9 +477,9 @@ void rtp_timing_receiver_cleanup_handler(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; pthread_cancel(conn->timer_requester); pthread_join(conn->timer_requester, NULL); - debug(1,"shutdown timing socket."); - shutdown(conn->timing_socket,SHUT_RDWR); - debug(1,"close timing socket."); + debug(1, "shutdown timing socket."); + shutdown(conn->timing_socket, SHUT_RDWR); + debug(1, "close timing socket."); close(conn->timing_socket); debug(3, "Timing Receiver Cleanup Successful."); } diff --git a/rtsp.c b/rtsp.c index b66e8954..00a168ba 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1887,11 +1887,25 @@ authenticate: static void *rtsp_conversation_thread_func(void *pconn) { rtsp_conn_info *conn = pconn; - // create the player thread lock. - int rwli = pthread_rwlock_init(&conn->player_thread_lock, NULL); - if (rwli != 0) - die("Error %d initialising player_thread_lock for conversation thread %d.", rwli, - conn->connection_number); + int rc = pthread_mutex_init(&conn->flush_mutex, NULL); + if (rc) + die("Connection %d: error %d initialising flush_mutex.", conn->connection_number, rc); + rc = pthread_mutex_init(&conn->ab_mutex, NULL); + if (rc) + die("Connection %d: error %d initialising ab_mutex.", conn->connection_number, rc); +// set the flowcontrol condition variable to wait on a monotonic clock +#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD + pthread_condattr_t attr; + pthread_condattr_init(&attr); + pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it. + rc = pthread_cond_init(&conn->flowcontrol, &attr); +#endif +#ifdef COMPILE_FOR_OSX + rc = pthread_cond_init(&conn->flowcontrol, NULL); +#endif + if (rc) + die("Connection %d: error %d initialising flow control condition variable.", + conn->connection_number, rc); rtp_initialise(conn); @@ -2005,12 +2019,17 @@ static void *rtsp_conversation_thread_func(void *pconn) { debug(2, "Connection %d: RTSP thread terminated.", conn->connection_number); conn->running = 0; - // release the player_thread_lock - int rwld = pthread_rwlock_destroy(&conn->player_thread_lock); - if (rwld) - debug(1, "Error %d destroying player_thread_lock for conversation thread %d.", rwld, - conn->connection_number); - + // remove flow control and mutexes + rc = pthread_cond_destroy(&conn->flowcontrol); + if (rc) + debug(1, "Connection %d: error %d destroying flow control condition variable.", + conn->connection_number, rc); + rc = pthread_mutex_destroy(&conn->ab_mutex); + if (rc) + debug(1, "Connection %d: error %d destroying ab_mutex.", conn->connection_number, rc); + rc = pthread_mutex_destroy(&conn->flush_mutex); + if (rc) + debug(1, "Connection %d: error %d destroying flush_mutex.", conn->connection_number, rc); pthread_exit(NULL); } diff --git a/shairport.c b/shairport.c index c5450ad7..92521107 100644 --- a/shairport.c +++ b/shairport.c @@ -95,20 +95,20 @@ #include #endif -static inline int config_set_lookup_bool(config_t* cfg, char* where, int* dst) { +static inline int config_set_lookup_bool(config_t *cfg, char *where, int *dst) { const char *str = 0; if (config_lookup_string(cfg, where, &str)) { - if (strcasecmp(str, "no") == 0){ - (*dst)=0; + if (strcasecmp(str, "no") == 0) { + (*dst) = 0; return 1; - }else if (strcasecmp(str, "yes") == 0){ - (*dst)=1; + } else if (strcasecmp(str, "yes") == 0) { + (*dst) = 1; return 1; - }else{ + } else { die("Invalid %s option choice \"%s\". It should be \"yes\" or \"no\"", where, str); return 0; } - }else{ + } else { return 0; } } @@ -404,7 +404,8 @@ int parse_options(int argc, char **argv) { config_set_lookup_bool(config.cfg, "sessioncontrol.daemonize_with_pid_file", &daemonisewith); /* Get the Just_Daemonize setting. */ - config_set_lookup_bool(config.cfg, "sessioncontrol.daemonize_without_pid_file", &daemonisewithout); + config_set_lookup_bool(config.cfg, "sessioncontrol.daemonize_without_pid_file", + &daemonisewithout); if ((daemonisewith) && (daemonisewithout)) die("Select either daemonize_with_pid_file or daemonize_without_pid_file -- you have " @@ -474,7 +475,8 @@ int parse_options(int argc, char **argv) { } /* Get the statistics setting. */ - if (!config_set_lookup_bool(config.cfg, "general.statistics", &(config.statistics_requested))) { + if (!config_set_lookup_bool(config.cfg, "general.statistics", + &(config.statistics_requested))) { warn("The \"general\" \"statistics\" setting is deprecated. Please use the \"diagnostics\" " "\"statistics\" setting instead."); } @@ -881,61 +883,60 @@ int parse_options(int argc, char **argv) { #endif #ifdef CONFIG_MQTT - int tmpval=0; - config_set_lookup_bool(config.cfg, "mqtt.enabled", &config.mqtt_enabled); - if(config.mqtt_enabled && !config.metadata_enabled){ - die("You need to have metadata enabled in order to use mqtt"); - } - if (config_lookup_string(config.cfg, "mqtt.hostname", &str)) { - config.mqtt_hostname = (char *)str; - //TODO: Document that, if this is false, whole mqtt func is disabled - } - if (config_lookup_int(config.cfg, "mqtt.port", &tmpval)) { - config.mqtt_port = tmpval; - }else{ - //TODO: Is this the correct way to set a default value? - config.mqtt_port = 1883; - } - - if (config_lookup_string(config.cfg, "mqtt.username", &str)) { - config.mqtt_username = (char *)str; - } - if (config_lookup_string(config.cfg, "mqtt.password", &str)) { - config.mqtt_password = (char *)str; - } - int capath=0; - if (config_lookup_string(config.cfg, "mqtt.capath", &str)) { - config.mqtt_capath = (char *)str; - capath=1; - } - if (config_lookup_string(config.cfg, "mqtt.cafile", &str)) { - if(capath) - die("Supply either mqtt cafile or mqtt capath -- you have supplied both!"); - config.mqtt_cafile = (char *)str; - } - int certkeynum=0; - if (config_lookup_string(config.cfg, "mqtt.certfile", &str)) { - config.mqtt_certfile = (char *)str; - certkeynum++; - } - if (config_lookup_string(config.cfg, "mqtt.keyfile", &str)) { - config.mqtt_keyfile = (char *)str; - certkeynum++; - } - if( certkeynum!=0 && certkeynum!=2){ - die("If you want to use TLS Client Authentication, you have to specify " - "mqtt.certfile AND mqtt.keyfile.\nYou have supplied only one of them.\n" - "If you do not want to use TLS Client Authentication, leave both empty." - ); - } - - if(config_lookup_string(config.cfg, "mqtt.topic", &str)){ - config.mqtt_topic = (char *)str; - } - config_set_lookup_bool(config.cfg, "mqtt.publish_raw", &config.mqtt_publish_raw); - config_set_lookup_bool(config.cfg, "mqtt.publish_parsed", &config.mqtt_publish_parsed); - config_set_lookup_bool(config.cfg, "mqtt.publish_cover", &config.mqtt_publish_cover); - config_set_lookup_bool(config.cfg, "mqtt.enable_remote", &config.mqtt_enable_remote); + int tmpval = 0; + config_set_lookup_bool(config.cfg, "mqtt.enabled", &config.mqtt_enabled); + if (config.mqtt_enabled && !config.metadata_enabled) { + die("You need to have metadata enabled in order to use mqtt"); + } + if (config_lookup_string(config.cfg, "mqtt.hostname", &str)) { + config.mqtt_hostname = (char *)str; + // TODO: Document that, if this is false, whole mqtt func is disabled + } + if (config_lookup_int(config.cfg, "mqtt.port", &tmpval)) { + config.mqtt_port = tmpval; + } else { + // TODO: Is this the correct way to set a default value? + config.mqtt_port = 1883; + } + + if (config_lookup_string(config.cfg, "mqtt.username", &str)) { + config.mqtt_username = (char *)str; + } + if (config_lookup_string(config.cfg, "mqtt.password", &str)) { + config.mqtt_password = (char *)str; + } + int capath = 0; + if (config_lookup_string(config.cfg, "mqtt.capath", &str)) { + config.mqtt_capath = (char *)str; + capath = 1; + } + if (config_lookup_string(config.cfg, "mqtt.cafile", &str)) { + if (capath) + die("Supply either mqtt cafile or mqtt capath -- you have supplied both!"); + config.mqtt_cafile = (char *)str; + } + int certkeynum = 0; + if (config_lookup_string(config.cfg, "mqtt.certfile", &str)) { + config.mqtt_certfile = (char *)str; + certkeynum++; + } + if (config_lookup_string(config.cfg, "mqtt.keyfile", &str)) { + config.mqtt_keyfile = (char *)str; + certkeynum++; + } + if (certkeynum != 0 && certkeynum != 2) { + die("If you want to use TLS Client Authentication, you have to specify " + "mqtt.certfile AND mqtt.keyfile.\nYou have supplied only one of them.\n" + "If you do not want to use TLS Client Authentication, leave both empty."); + } + + if (config_lookup_string(config.cfg, "mqtt.topic", &str)) { + config.mqtt_topic = (char *)str; + } + config_set_lookup_bool(config.cfg, "mqtt.publish_raw", &config.mqtt_publish_raw); + config_set_lookup_bool(config.cfg, "mqtt.publish_parsed", &config.mqtt_publish_parsed); + config_set_lookup_bool(config.cfg, "mqtt.publish_cover", &config.mqtt_publish_cover); + config_set_lookup_bool(config.cfg, "mqtt.enable_remote", &config.mqtt_enable_remote); #endif free(config_file_real_path); } @@ -1035,13 +1036,13 @@ int parse_options(int argc, char **argv) { free(i2); free(i3); free(vs); - + #ifdef CONFIG_MQTT // mqtt topic was not set. As we have the service name just now, set it - if(config.mqtt_topic==NULL){ - int topic_length=1+strlen(config.service_name)+1; - char* topic=malloc(topic_length+1); - snprintf(topic,topic_length,"/%s/",config.service_name); + if (config.mqtt_topic == NULL) { + int topic_length = 1 + strlen(config.service_name) + 1; + char *topic = malloc(topic_length + 1); + snprintf(topic, topic_length, "/%s/", config.service_name); config.mqtt_topic = topic; } #endif @@ -1251,7 +1252,7 @@ int main(int argc, char **argv) { daemon_pid_file_ident = daemon_log_ident = daemon_ident_from_argv0(argv[0]); daemon_pid_file_proc = pid_file_proc; - + /* Check if we are called with -D or --disconnectFromOutput parameter */ if (argc >= 2 && ((strcmp(argv[1], "-D") == 0) || (strcmp(argv[1], "--disconnectFromOutput") == 0))) { @@ -1577,7 +1578,7 @@ int main(int argc, char **argv) { #endif #ifdef HAVE_MQTT - if(config.mqtt_enabled){ + if (config.mqtt_enabled) { initialise_mqtt(); } #endif From bae7585cff160ba7dea348267c42b861247a84b3 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 14 Jul 2018 14:32:22 +0100 Subject: [PATCH 006/569] Make pc add and get items work if pthread_cancel occurs --- player.c | 15 ++++++++------- rtsp.c | 17 +++++++++++------ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/player.c b/player.c index 2a659f6e..e4dd7389 100644 --- a/player.c +++ b/player.c @@ -2385,7 +2385,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c int32_t hw_min_db, hw_max_db, hw_range_db, min_db, max_db; // hw_range_db is a flag; if 0 means no mixer - if (config.output->parameters) { + if (config.output->parameters) { // no cancellation points in here audio_parameters audio_information; // have a hardware mixer config.output->parameters(&audio_information); @@ -2515,9 +2515,9 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c if (config.ignore_volume_control == 1) scaled_attenuation = max_db; else if (config.volume_control_profile == VCP_standard) - scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); + scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); // no cancellation points else if (config.volume_control_profile == VCP_flat) - scaled_attenuation = flat_vol2attn(airplay_volume, max_db, min_db); + scaled_attenuation = flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points else debug(1, "Unrecognised volume control profile"); @@ -2552,7 +2552,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c // %f",software_attenuation,temp_fix_volume,airplay_volume); conn->fix_volume = temp_fix_volume; - memory_barrier(); + memory_barrier(); // no cancellation points if (config.loudness) loudness_set_volume(software_attenuation / 100); @@ -2599,7 +2599,7 @@ void do_flush(int64_t timestamp, rtsp_conn_info *conn) { // otherwise if (conn->first_packet_timestamp) { debug(2, "pfls"); - send_ssnc_metadata('pfls', NULL, 0, 1); + send_ssnc_metadata('pfls', NULL, 0, 1); // contains cancellation points } #endif @@ -2621,7 +2621,7 @@ int player_play(rtsp_conn_info *conn) { command_start(); #ifdef CONFIG_METADATA debug(2, "pbeg"); - send_ssnc_metadata('pbeg', NULL, 0, 1); + send_ssnc_metadata('pbeg', NULL, 0, 1); // contains cancellation points #endif pthread_t *pt = malloc(sizeof(pthread_t)); if (pt == NULL) @@ -2640,6 +2640,7 @@ int player_play(rtsp_conn_info *conn) { } int player_stop(rtsp_conn_info *conn) { + // will only ever be called by the connection thread debug(3, "player_stop"); if (conn->player_thread) { debug(3, "player_thread exists"); @@ -2652,7 +2653,7 @@ int player_stop(rtsp_conn_info *conn) { conn->player_thread = NULL; #ifdef CONFIG_METADATA debug(2, "pend"); - send_ssnc_metadata('pend', NULL, 0, 1); + send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points #endif command_stop(); return 0; diff --git a/rtsp.c b/rtsp.c index 00a168ba..3d515247 100644 --- a/rtsp.c +++ b/rtsp.c @@ -163,6 +163,13 @@ int send_ssnc_metadata(uint32_t code, char *data, uint32_t length, int block) { return send_metadata('ssnc', code, data, length, NULL, block); } +void pc_queue_cleanup_handler(void *arg) { + pc_queue *the_queue = (pc_queue *)arg; + int rc = pthread_mutex_unlock(&the_queue->pc_queue_lock); + if (rc) + debug(1, "Error unlocking for pc_queue_add_item or pc_queue_get_item."); +} + int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) { int rc; if (the_queue) { @@ -174,6 +181,7 @@ int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) { rc = pthread_mutex_lock(&the_queue->pc_queue_lock); if (rc) debug(1, "Error locking for pc_queue_add_item"); + pthread_cleanup_push(pc_queue_cleanup_handler,(void *)the_queue); 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) @@ -196,9 +204,7 @@ int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) { rc = pthread_cond_signal(&the_queue->pc_queue_item_added_signal); if (rc) debug(1, "Error signalling after pc_queue_add_item"); - rc = pthread_mutex_unlock(&the_queue->pc_queue_lock); - if (rc) - debug(1, "Error unlocking for pc_queue_add_item"); + pthread_cleanup_pop(1); // unlock the queue lock. } else { debug(1, "Adding an item to a NULL queue"); } @@ -211,6 +217,7 @@ int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) { rc = pthread_mutex_lock(&the_queue->pc_queue_lock); if (rc) debug(1, "Error locking for pc_queue_get_item"); + pthread_cleanup_push(pc_queue_cleanup_handler,(void *)the_queue); while (the_queue->count == 0) { rc = pthread_cond_wait(&the_queue->pc_queue_item_added_signal, &the_queue->pc_queue_lock); if (rc) @@ -231,9 +238,7 @@ int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) { rc = pthread_cond_signal(&the_queue->pc_queue_item_removed_signal); if (rc) debug(1, "Error signalling after pc_queue_removed_item"); - rc = pthread_mutex_unlock(&the_queue->pc_queue_lock); - if (rc) - debug(1, "Error unlocking for pc_queue_get_item"); + pthread_cleanup_pop(1); // unlock the queue lock. } else { debug(1, "Removing an item from a NULL queue"); } From d3b79b965d5afb21d479777b4a3fcfd3a9af1ee8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 14 Jul 2018 19:34:31 +0100 Subject: [PATCH 007/569] Make more of the code amenable to pthread cancel --- audio.c | 1 + common.c | 5 +- dacp.h | 1 + mdns_avahi.c | 3 -- player.c | 142 +++++++++++++++++++++++++++++++++++---------------- player.h | 9 ++-- rtsp.c | 6 +++ 7 files changed, 114 insertions(+), 53 deletions(-) diff --git a/audio.c b/audio.c index 058cbfe7..ce317f13 100644 --- a/audio.c +++ b/audio.c @@ -1,6 +1,7 @@ /* * Audio driver handler. This file is part of Shairport. * Copyright (c) James Laird 2013 + * Modifications (c) Mike Brady 2014 -- 2018 * All rights reserved. * * Permission is hereby granted, free of charge, to any person diff --git a/common.c b/common.c index 2c8810c9..886750ba 100644 --- a/common.c +++ b/common.c @@ -419,14 +419,15 @@ static char super_secret_key[] = #ifdef HAVE_LIBSSL uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { - static RSA *rsa = NULL; - + RSA *rsa = NULL; if (!rsa) { BIO *bmem = BIO_new_mem_buf(super_secret_key, -1); rsa = PEM_read_bio_RSAPrivateKey(bmem, NULL, NULL, NULL); BIO_free(bmem); } + debug(1,"RSA_size(rsa) is %d",RSA_size(rsa)); + uint8_t *out = malloc(RSA_size(rsa)); switch (mode) { case RSA_MODE_AUTH: diff --git a/dacp.h b/dacp.h index 09dd477d..fe89d397 100644 --- a/dacp.h +++ b/dacp.h @@ -26,6 +26,7 @@ int dacp_get_speaker_list(dacp_spkr_stuff *speaker_array, int max_size_of_array, void set_dacp_server_information(rtsp_conn_info *conn); // tell the DACP conversation thread that // the dacp server information has been set // or changed +void relinquish_dacp_server_information(rtsp_conn_info *conn); // tell the DACP conversation thread that the player thread is no longer associated with it. void dacp_monitor_port_update_callback( char *dacp_id, uint16_t port); // a callback to say the port is no longer in use int send_simple_dacp_command(const char *command); diff --git a/mdns_avahi.c b/mdns_avahi.c index c54f2df6..bdca81e3 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -468,9 +468,6 @@ void avahi_dacp_dont_monitor(void *userdata) { if (userdata) { dacp_browser_struct *dbs = (dacp_browser_struct *)userdata; // stop and dispose of everything - /*if (dbs->service_poll) - avahi_threaded_poll_stop((dbs)->service_poll); - */ if (dbs->service_poll) { avahi_threaded_poll_stop(dbs->service_poll); avahi_threaded_poll_lock(dbs->service_poll); diff --git a/player.c b/player.c index e4dd7389..dd1982ce 100644 --- a/player.c +++ b/player.c @@ -1419,10 +1419,61 @@ typedef struct stats { // statistics for running averages void player_thread_cleanup_handler(void *arg) { debug(1, "player_thread_cleanup_handler called"); rtsp_conn_info *conn = (rtsp_conn_info *)arg; + +#ifdef HAVE_DACP_CLIENT + + relinquish_dacp_server_information(conn); // say it doesn't belong to this conversation thread any more... + +#else + // stop watching for DACP port number stuff + // this is only used for compatability, if dacp stuff isn't enabled. + if (conn->dapo_private_storage) { + mdns_dacp_dont_monitor(conn->dapo_private_storage); + conn->dapo_private_storage = NULL; + } else { + debug(2, "DACP Monitor already stopped"); + } +#endif + + debug(2, "Cancelling timing, control and audio threads..."); + debug(2, "Cancel timing thread."); + pthread_cancel(conn->rtp_timing_thread); + debug(2, "Join timing thread."); + pthread_join(conn->rtp_timing_thread, NULL); + debug(2, "Timing thread terminated."); + debug(2, "Cancel control thread."); + pthread_cancel(conn->rtp_control_thread); + debug(2, "Join control thread."); + pthread_join(conn->rtp_control_thread, NULL); + debug(2, "Control thread terminated."); + debug(2, "Cancel audio thread."); + pthread_cancel(conn->rtp_audio_thread); + debug(2, "Join audio thread."); + pthread_join(conn->rtp_audio_thread, NULL); + debug(2, "Audio thread terminated."); + + if (conn->outbuf) { + free(conn->outbuf); + conn->outbuf = NULL; + } + if (conn->sbuf) { + free(conn->sbuf); + conn->sbuf = NULL; + } + if (conn->tbuf) { + free(conn->tbuf); + conn->tbuf = NULL; + } + free_audio_buffers(conn); + terminate_decoders(conn); + if (config.output->stop) + config.output->stop(); + + clear_reference_timestamp(conn); + conn->rtp_running = 0; } void *player_thread_func(void *arg) { - pthread_cleanup_push(player_thread_cleanup_handler, arg); rtsp_conn_info *conn = (rtsp_conn_info *)arg; conn->packet_count = 0; @@ -1497,12 +1548,6 @@ void *player_thread_func(void *arg) { debug(3, "Output frame bytes is %d.", conn->output_bytes_per_frame); - // create and start the timing, control and audio receiver threads - pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread; - pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, (void *)conn); - pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, (void *)conn); - pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, (void *)conn); - conn->session_corrections = 0; conn->play_segment_reference_frame = 0; // zero signals that we are not in a play segment @@ -1550,12 +1595,7 @@ void *player_thread_func(void *arg) { static char rnstate[256]; initstate(time(NULL), rnstate, 256); - signed short *inbuf, *tbuf; - - int32_t *sbuf; - - char *outbuf; - + signed short *inbuf; int inbuflength; int output_bit_depth = 16; // default; @@ -1595,25 +1635,24 @@ void *player_thread_func(void *arg) { // if ((input_rate!=config.output_rate) || (input_bit_depth!=output_bit_depth)) { // debug(1,"Define tbuf of length // %d.",output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); - tbuf = malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + + conn->tbuf = malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change)); - if (tbuf == NULL) + if (conn->tbuf == NULL) die("Failed to allocate memory for the transition buffer."); - sbuf = 0; // initialise this, because soxr stuffing might be chosen later - sbuf = malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + + conn->sbuf = malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change)); - if (sbuf == NULL) - debug(1, "Failed to allocate memory for the sbuf buffer."); + if (conn->sbuf == NULL) + die("Failed to allocate memory for the sbuf buffer."); // The size of these dependents on the number of frames, the size of each frame and the maximum // size change - outbuf = malloc( + conn->outbuf = malloc( conn->output_bytes_per_frame * (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change)); - if (outbuf == NULL) + if (conn->outbuf == NULL) die("Failed to allocate memory for an output buffer."); conn->first_packet_timestamp = 0; conn->missing_packets = conn->late_packets = conn->too_late_packets = conn->resend_requests = 0; @@ -1624,7 +1663,7 @@ void *player_thread_func(void *arg) { // stop looking elsewhere for DACP stuff #ifdef HAVE_DACP_CLIENT - // this may have pthread cancellation points in it + // this does not have pthread cancellation points in it (assuming avahi doesn't) set_dacp_server_information(conn); // this will start scanning when a port is registered by the // code initiated by the mdns_dacp_monitor #else @@ -1634,6 +1673,7 @@ void *player_thread_func(void *arg) { if (conn->dapo_private_storage) debug(1, "DACP monitor already initialised?"); else + // this does not have pthread cancellation points in it (assuming avahi doesn't) conn->dapo_private_storage = mdns_dacp_monitor(conn->dacp_id); // ?? #endif @@ -1680,9 +1720,16 @@ void *player_thread_func(void *arg) { // set the default volume to whaterver it was before, as stored in the config airplay_volume debug(3, "Set initial volume to %f.", config.airplay_volume); - player_volume(config.airplay_volume, conn); // ?? int64_t frames_to_drop = 0; + + // create and start the timing, control and audio receiver threads + pthread_create(&conn->rtp_audio_thread, NULL, &rtp_audio_receiver, (void *)conn); + pthread_create(&conn->rtp_control_thread, NULL, &rtp_control_receiver, (void *)conn); + pthread_create(&conn->rtp_timing_thread, NULL, &rtp_timing_receiver, (void *)conn); + + pthread_cleanup_push(player_thread_cleanup_handler, arg); // undo what's been done so far + // debug(1, "Play begin"); while (!conn->player_thread_please_stop) { abuf_t *inframe = buffer_get_frame(conn); @@ -1759,7 +1806,7 @@ void *player_thread_func(void *arg) { int32_t ll = 0, rl = 0; int16_t *inps = inbuf; // int16_t *outps = tbuf; - int32_t *outpl = (int32_t *)tbuf; + int32_t *outpl = (int32_t *)conn->tbuf; for (i = 0; i < inbuflength; i++) { ls = *inps++; rs = *inps++; @@ -2029,7 +2076,7 @@ void *player_thread_func(void *arg) { || config.convolution #endif ) { - int32_t *tbuf32 = (int32_t *)tbuf; + int32_t *tbuf32 = (int32_t *)conn->tbuf; float fbuf_l[inbuflength]; float fbuf_r[inbuflength]; @@ -2079,14 +2126,14 @@ void *player_thread_func(void *arg) { case ST_basic: // if (amount_to_stuff) debug(1,"Basic stuff..."); play_samples = - stuff_buffer_basic_32((int32_t *)tbuf, inbuflength, config.output_format, - outbuf, amount_to_stuff, enable_dither, conn); + stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format, + conn->outbuf, amount_to_stuff, enable_dither, conn); break; case ST_soxr: #ifdef HAVE_LIBSOXR // if (amount_to_stuff) debug(1,"Soxr stuff..."); - play_samples = stuff_buffer_soxr_32((int32_t *)tbuf, (int32_t *)sbuf, inbuflength, - config.output_format, outbuf, amount_to_stuff, + play_samples = stuff_buffer_soxr_32((int32_t *)conn->tbuf, (int32_t *)conn->sbuf, inbuflength, + config.output_format, conn->outbuf, amount_to_stuff, enable_dither, conn); #endif break; @@ -2107,13 +2154,13 @@ void *player_thread_func(void *arg) { } */ - if (outbuf == NULL) + if (conn->outbuf == NULL) debug(1, "NULL outbuf to play -- skipping it."); else { if (play_samples == 0) debug(1, "play_samples==0 skipping it (1)."); else - config.output->play(outbuf, play_samples); + config.output->play(conn->outbuf, play_samples); } // check for loss of sync @@ -2142,12 +2189,12 @@ void *player_thread_func(void *arg) { } else { // if there is no delay procedure, or it's not working or not allowed, there can be no // synchronising - play_samples = stuff_buffer_basic_32((int32_t *)tbuf, inbuflength, config.output_format, - outbuf, 0, enable_dither, conn); - if (outbuf == NULL) + play_samples = stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format, + conn->outbuf, 0, enable_dither, conn); + if (conn->outbuf == NULL) debug(1, "NULL outbuf to play -- skipping it."); else - config.output->play(outbuf, play_samples); // remove the (short*)! + config.output->play(conn->outbuf, play_samples); // remove the (short*)! } // mark the frame as finished @@ -2295,7 +2342,13 @@ void *player_thread_func(void *arg) { elapsedSec); } -#ifndef HAVE_DACP_CLIENT + /* all done in the cleanup... + +#ifdef HAVE_DACP_CLIENT + + relinquish_dacp_server_information(conn); // say it doesn't belong to this conversation thread any more... + +#else // stop watching for DACP port number stuff // this is only used for compatability, if dacp stuff isn't enabled. if (conn->dapo_private_storage) { @@ -2306,11 +2359,6 @@ void *player_thread_func(void *arg) { } #endif - debug(3, "Connection %d: stopping output device.", conn->connection_number); - - if (config.output->stop) - config.output->stop(); - debug(2, "Cancelling timing, control and audio threads..."); debug(2, "Cancel timing thread."); pthread_cancel(rtp_timing_thread); @@ -2330,21 +2378,23 @@ void *player_thread_func(void *arg) { clear_reference_timestamp(conn); conn->rtp_running = 0; + debug(3, "Connection %d: stopping output device.", conn->connection_number); + + if (config.output->stop) + config.output->stop(); + debug(2, "Freeing audio buffers and decoders."); free_audio_buffers(conn); terminate_decoders(conn); debug(2, "Connection %d: player thread terminated.", conn->connection_number); - if (conn->dacp_id) { - free(conn->dacp_id); - conn->dacp_id = NULL; - } if (outbuf) free(outbuf); if (tbuf) free(tbuf); if (sbuf) free(sbuf); + */ pthread_cleanup_pop(1); pthread_exit(NULL); } @@ -2352,6 +2402,8 @@ void *player_thread_func(void *arg) { // takes the volume as specified by the airplay protocol void player_volume_without_notification(double airplay_volume, rtsp_conn_info *conn) { + // no cancellation points here if we assume that the mute call to the back end has no cancellation points + // The volume ranges -144.0 (mute) or -30 -- 0. See // http://git.zx2c4.com/Airtunes2/about/#setting-volume // By examination, the -30 -- 0 range is linear on the slider; i.e. the slider is calibrated in 30 diff --git a/player.h b/player.h index 4c12c803..9d4774b7 100644 --- a/player.h +++ b/player.h @@ -81,11 +81,14 @@ typedef struct { SOCKADDR remote, local; int stop; int running; - pthread_t thread, timer_requester; - + pthread_t thread, timer_requester, rtp_audio_thread, rtp_control_thread, rtp_timing_thread; // pthread_t *ptp; + + signed short *tbuf; + int32_t *sbuf; + char *outbuf; + pthread_t *player_thread; - abuf_t audio_buffer[BUFFER_FRAMES]; int max_frames_per_packet, input_num_channels, input_bit_depth, input_rate; int input_bytes_per_frame, output_bytes_per_frame, output_sample_ratio; diff --git a/rtsp.c b/rtsp.c index 3d515247..872e8b7f 100644 --- a/rtsp.c +++ b/rtsp.c @@ -2021,6 +2021,12 @@ static void *rtsp_conversation_thread_func(void *pconn) { playing_conn = NULL; pthread_mutex_unlock(&play_lock); } + + if (conn->dacp_id) { + free(conn->dacp_id); + conn->dacp_id = NULL; + } + debug(2, "Connection %d: RTSP thread terminated.", conn->connection_number); conn->running = 0; From 768dc7759a953c8021eb717986df21f9049b1125 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 15 Jul 2018 11:54:48 +0100 Subject: [PATCH 008/569] Tidy up log output and add timers to inform statements. Move more stuff to the player thread pop function. Warn about cancellation points in debug versions of mutex lock and unlock. Ensure they are not cancellation points when debug not in use. Warn about Mac OS X cancellation points. --- common.c | 24 ++++++++++++++++++++++-- player.c | 39 +++++++++++++++++++++++++-------------- player.h | 1 + 3 files changed, 48 insertions(+), 16 deletions(-) diff --git a/common.c b/common.c index 886750ba..74f435bc 100644 --- a/common.c +++ b/common.c @@ -178,11 +178,26 @@ void debug(int level, const char *format, ...) { void inform(const char *format, ...) { char s[1024]; s[0] = 0; + uint64_t time_now = get_absolute_time_in_fp(); + uint64_t time_since_start = time_now - fp_time_at_startup; + uint64_t time_since_last_debug_message = time_now - fp_time_at_last_debug_message; + fp_time_at_last_debug_message = time_now; + uint64_t divisor = (uint64_t)1 << 32; + double tss = 1.0 * time_since_start / divisor; + double tsl = 1.0 * time_since_last_debug_message / divisor; va_list args; va_start(args, format); vsnprintf(s, sizeof(s), format, args); va_end(args); - daemon_log(LOG_INFO, "%s", s); + + if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) + daemon_log(LOG_INFO, "|% 20.9f|% 20.9f|%s", tss, tsl, s); + else if ((debuglev) && (config.debugger_show_relative_time)) + daemon_log(LOG_INFO, "% 20.9f|%s", tsl, s); + else if ((debuglev) && (config.debugger_show_elapsed_time)) + daemon_log(LOG_INFO, "% 20.9f|%s", tss, s); + else + daemon_log(LOG_INFO, "%s", s); } // The following two functions are adapted slightly and with thanks from Jonathan Leffler's sample @@ -1069,13 +1084,14 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, const char *debugmessage, int debuglevel) { +// this is not pthread_cancellation safe because is contains a cancellation point useconds_t time_to_wait = dally_time; int r = pthread_mutex_trylock(mutex); while ((r == EBUSY) && (time_to_wait > 0)) { useconds_t st = time_to_wait; if (st > 1000) st = 1000; - sps_nanosleep(0, st * 1000); + sps_nanosleep(0, st * 1000); // this contains a cancellation point time_to_wait -= st; r = pthread_mutex_trylock(mutex); } @@ -1097,6 +1113,8 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char *filename, const int line, int debuglevel) { + if (debuglevel > debuglev) + return pthread_mutex_lock(mutex); uint64_t time_at_start = get_absolute_time_in_fp(); char dstring[1000]; memset(dstring, 0, sizeof(dstring)); @@ -1117,6 +1135,8 @@ int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *filename, const int line, int debuglevel) { + if (debuglevel > debuglev) + return pthread_mutex_unlock(mutex); char dstring[1000]; char errstr[512]; memset(dstring, 0, sizeof(dstring)); diff --git a/player.c b/player.c index dd1982ce..b29ab496 100644 --- a/player.c +++ b/player.c @@ -1420,6 +1420,17 @@ void player_thread_cleanup_handler(void *arg) { debug(1, "player_thread_cleanup_handler called"); rtsp_conn_info *conn = (rtsp_conn_info *)arg; + debug(3, "Connection %d: player thread main loop exit.", conn->connection_number); + + if (config.statistics_requested) { + int rawSeconds = (int)difftime(time(NULL), conn->playstart); + int elapsedHours = rawSeconds / 3600; + int elapsedMin = (rawSeconds / 60) % 60; + int elapsedSec = rawSeconds % 60; + inform("Playback Stopped. Total playing time %02d:%02d:%02d.", elapsedHours, elapsedMin, + elapsedSec); + } + #ifdef HAVE_DACP_CLIENT relinquish_dacp_server_information(conn); // say it doesn't belong to this conversation thread any more... @@ -1574,7 +1585,7 @@ void *player_thread_func(void *arg) { int32_t minimum_buffer_occupancy = INT32_MAX; int32_t maximum_buffer_occupancy = INT32_MIN; - time_t playstart = time(NULL); + conn->playstart = time(NULL); conn->buffer_occupancy = 0; @@ -2264,7 +2275,7 @@ void *player_thread_func(void *arg) { if ((config.output->delay)) { if (config.no_sync == 0) { inform( - " %*.1f," /* Sync error in milliseconds */ + " %10.1f," /* Sync error in milliseconds */ "%*.1f," /* net correction in ppm */ "%*.1f," /* corrections in ppm */ "%*d," /* total packets */ @@ -2275,8 +2286,8 @@ void *player_thread_func(void *arg) { "%*lli," /* min DAC queue size */ "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ - 9, /* should be 10, but there's an explicit space at the start to ensure - alignment */ + //9, /* should be 10, but there's an explicit space at the start to ensure + // alignment */ 1000 * moving_average_sync_error / config.output_rate, 10, moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), 10, moving_average_insertions_plus_deletions * 1000000 / @@ -2285,7 +2296,7 @@ void *player_thread_func(void *arg) { conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy); } else { - inform(" %*.1f," /* Sync error in milliseconds */ + inform(" %10.1f," /* Sync error in milliseconds */ "%*d," /* total packets */ "%*llu," /* missing packets */ "%*llu," /* late packets */ @@ -2294,8 +2305,8 @@ void *player_thread_func(void *arg) { "%*lli," /* min DAC queue size */ "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ - 9, /* should be 10, but there's an explicit space at the start to ensure - alignment */ + // 9, /* should be 10, but there's an explicit space at the start to ensure + // alignment */ 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, conn->resend_requests, 7, @@ -2303,7 +2314,7 @@ void *player_thread_func(void *arg) { maximum_buffer_occupancy); } } else { - inform(" %*.1f," /* Sync error in milliseconds */ + inform(" %10.1f," /* Sync error in milliseconds */ "%*d," /* total packets */ "%*llu," /* missing packets */ "%*llu," /* late packets */ @@ -2311,8 +2322,8 @@ void *player_thread_func(void *arg) { "%*llu," /* resend requests */ "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ - 9, /* should be 10, but there's an explicit space at the start to ensure - alignment */ + // 9, /* should be 10, but there's an explicit space at the start to ensure + // alignment */ 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, conn->resend_requests, 5, @@ -2331,19 +2342,19 @@ void *player_thread_func(void *arg) { } } +/* all done in the cleanup... + debug(3, "Connection %d: player thread main loop exit.", conn->connection_number); if (config.statistics_requested) { - int rawSeconds = (int)difftime(time(NULL), playstart); + int rawSeconds = (int)difftime(time(NULL), conn->playstart); int elapsedHours = rawSeconds / 3600; int elapsedMin = (rawSeconds / 60) % 60; int elapsedSec = rawSeconds % 60; inform("Playback Stopped. Total playing time %02d:%02d:%02d.", elapsedHours, elapsedMin, elapsedSec); } - - /* all done in the cleanup... - + #ifdef HAVE_DACP_CLIENT relinquish_dacp_server_information(conn); // say it doesn't belong to this conversation thread any more... diff --git a/player.h b/player.h index 9d4774b7..d9b0a053 100644 --- a/player.h +++ b/player.h @@ -81,6 +81,7 @@ typedef struct { SOCKADDR remote, local; int stop; int running; + time_t playstart; pthread_t thread, timer_requester, rtp_audio_thread, rtp_control_thread, rtp_timing_thread; // pthread_t *ptp; From 525ff37c1ce2396ab72d8ee362aaf7a3ba1aa802 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 15 Jul 2018 12:25:38 +0100 Subject: [PATCH 009/569] Add a | to the statistics output to ensure the columns of numbers are aligned. Seems to be a bug in the daemon_log (?) --- player.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/player.c b/player.c index b29ab496..07716b76 100644 --- a/player.c +++ b/player.c @@ -1695,7 +1695,7 @@ void *player_thread_func(void *arg) { if (config.statistics_requested) { if ((config.output->delay)) { if (config.no_sync == 0) { - inform("sync error in milliseconds, " + inform("| sync error in milliseconds, " "net correction in ppm, " "corrections in ppm, " "total packets, " @@ -1707,7 +1707,7 @@ void *player_thread_func(void *arg) { "min buffer occupancy, " "max buffer occupancy"); } else { - inform("sync error in milliseconds, " + inform("| sync error in milliseconds, " "total packets, " "missing packets, " "late packets, " @@ -1718,7 +1718,7 @@ void *player_thread_func(void *arg) { "max buffer occupancy"); } } else { - inform("sync error in milliseconds, " + inform("| sync error in milliseconds, " "total packets, " "missing packets, " "late packets, " @@ -2275,7 +2275,7 @@ void *player_thread_func(void *arg) { if ((config.output->delay)) { if (config.no_sync == 0) { inform( - " %10.1f," /* Sync error in milliseconds */ + "|%*.1f," /* Sync error in milliseconds */ "%*.1f," /* net correction in ppm */ "%*.1f," /* corrections in ppm */ "%*d," /* total packets */ @@ -2286,8 +2286,7 @@ void *player_thread_func(void *arg) { "%*lli," /* min DAC queue size */ "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ - //9, /* should be 10, but there's an explicit space at the start to ensure - // alignment */ + 10, 1000 * moving_average_sync_error / config.output_rate, 10, moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), 10, moving_average_insertions_plus_deletions * 1000000 / @@ -2296,7 +2295,7 @@ void *player_thread_func(void *arg) { conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy); } else { - inform(" %10.1f," /* Sync error in milliseconds */ + inform("|%*.1f," /* Sync error in milliseconds */ "%*d," /* total packets */ "%*llu," /* missing packets */ "%*llu," /* late packets */ @@ -2305,8 +2304,7 @@ void *player_thread_func(void *arg) { "%*lli," /* min DAC queue size */ "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ - // 9, /* should be 10, but there's an explicit space at the start to ensure - // alignment */ + 10, 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, conn->resend_requests, 7, @@ -2314,7 +2312,7 @@ void *player_thread_func(void *arg) { maximum_buffer_occupancy); } } else { - inform(" %10.1f," /* Sync error in milliseconds */ + inform("|%*.1f," /* Sync error in milliseconds */ "%*d," /* total packets */ "%*llu," /* missing packets */ "%*llu," /* late packets */ @@ -2322,8 +2320,7 @@ void *player_thread_func(void *arg) { "%*llu," /* resend requests */ "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ - // 9, /* should be 10, but there's an explicit space at the start to ensure - // alignment */ + 10, 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, conn->resend_requests, 5, From 8ed8f808b8c952b246452ea8a262d92fb061e2d9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 15 Jul 2018 13:11:51 +0100 Subject: [PATCH 010/569] Add a cancellation stack to get_buffer_frame. --- player.c | 49 +++++++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/player.c b/player.c index 07716b76..4ba93f8f 100644 --- a/player.c +++ b/player.c @@ -771,6 +771,11 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t *outp += result; } +void buffer_get_frame_cleanup_handler(void *arg) { + rtsp_conn_info *conn = (rtsp_conn_info *)arg; + debug_mutex_unlock(&conn->ab_mutex, 3); +} + // get the next frame, when available. return 0 if underrun/stream reset. static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // int16_t buf_fill; @@ -780,8 +785,12 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { int notified_buffer_empty = 0; // diagnostic only debug_mutex_lock(&conn->ab_mutex, 30000, 1); + + int wait; long dac_delay = 0; // long because alsa returns a long + + pthread_cleanup_push(buffer_get_frame_cleanup_handler, (void *)conn); // undo what's been done so far do { // get the time local_time_now = get_absolute_time_in_fp(); // type okay @@ -820,8 +829,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { debug_mutex_lock(&conn->flush_mutex, 1000, 1); if (conn->flush_requested == 1) { if (config.output->flush) - config.output->flush(); - ab_resync(conn); + config.output->flush(); // no cancellation points + ab_resync(conn); // no cancellation points conn->first_packet_timestamp = 0; conn->first_packet_time_to_play = 0; conn->time_since_play_started = 0; @@ -1207,7 +1216,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { time_of_wakeup.tv_sec = sec; time_of_wakeup.tv_nsec = nsec; // pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex, &time_of_wakeup); - int rc = pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex, &time_of_wakeup); + int rc = pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex, &time_of_wakeup); // this is a pthread cancellation point if (rc != 0) debug(3, "pthread_cond_timedwait returned error code %d.", rc); #endif @@ -1222,24 +1231,24 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { } } while (wait); - if (conn->player_thread_please_stop) { - debug(3, "buffer_get_frame exiting due to thread stop request."); - debug_mutex_unlock(&conn->ab_mutex, 3); - return 0; - } - // seq_t read = conn->ab_read; - - if (!curframe->ready) { - // debug(1, "Supplying a silent frame for frame %u", read); - conn->missing_packets++; - curframe->timestamp = 0; // indicate a silent frame should be substituted + if (conn->player_thread_please_stop==0) { + if (!curframe->ready) { + // debug(1, "Supplying a silent frame for frame %u", read); + conn->missing_packets++; + curframe->timestamp = 0; // indicate a silent frame should be substituted + } + curframe->ready = 0; + curframe->resend_level = 0; + conn->ab_read = SUCCESSOR(conn->ab_read); } - curframe->ready = 0; - curframe->resend_level = 0; - conn->ab_read = SUCCESSOR(conn->ab_read); - debug_mutex_unlock(&conn->ab_mutex, 3); - return curframe; + + pthread_cleanup_pop(1); + + if (conn->player_thread_please_stop) + return 0; + else + return curframe; } static inline short shortmean(short a, short b) { @@ -1743,7 +1752,7 @@ void *player_thread_func(void *arg) { // debug(1, "Play begin"); while (!conn->player_thread_please_stop) { - abuf_t *inframe = buffer_get_frame(conn); + abuf_t *inframe = buffer_get_frame(conn); // this has cancellation point(s) if (inframe) { inbuf = inframe->data; inbuflength = inframe->length; From 01f2a374b99f2607e63f55b49028cb580e0b1e3c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 15 Jul 2018 13:54:33 +0100 Subject: [PATCH 011/569] Use pthread_cancel to stop a player thread rather than SIGUSR1 and pthread_kill. --- common.c | 6 +- dacp.h | 4 +- player.c | 242 ++++++++++++++++++++++++++++--------------------------- player.h | 4 +- rtsp.c | 6 +- 5 files changed, 136 insertions(+), 126 deletions(-) diff --git a/common.c b/common.c index 74f435bc..38d677c6 100644 --- a/common.c +++ b/common.c @@ -441,8 +441,8 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { BIO_free(bmem); } - debug(1,"RSA_size(rsa) is %d",RSA_size(rsa)); - + debug(1, "RSA_size(rsa) is %d", RSA_size(rsa)); + uint8_t *out = malloc(RSA_size(rsa)); switch (mode) { case RSA_MODE_AUTH: @@ -1084,7 +1084,7 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, const char *debugmessage, int debuglevel) { -// this is not pthread_cancellation safe because is contains a cancellation point + // this is not pthread_cancellation safe because is contains a cancellation point useconds_t time_to_wait = dally_time; int r = pthread_mutex_trylock(mutex); while ((r == EBUSY) && (time_to_wait > 0)) { diff --git a/dacp.h b/dacp.h index fe89d397..bde3ac51 100644 --- a/dacp.h +++ b/dacp.h @@ -26,7 +26,9 @@ int dacp_get_speaker_list(dacp_spkr_stuff *speaker_array, int max_size_of_array, void set_dacp_server_information(rtsp_conn_info *conn); // tell the DACP conversation thread that // the dacp server information has been set // or changed -void relinquish_dacp_server_information(rtsp_conn_info *conn); // tell the DACP conversation thread that the player thread is no longer associated with it. +void relinquish_dacp_server_information(rtsp_conn_info *conn); // tell the DACP conversation thread + // that the player thread is no + // longer associated with it. void dacp_monitor_port_update_callback( char *dacp_id, uint16_t port); // a callback to say the port is no longer in use int send_simple_dacp_command(const char *command); diff --git a/player.c b/player.c index 4ba93f8f..dec43886 100644 --- a/player.c +++ b/player.c @@ -785,12 +785,12 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { int notified_buffer_empty = 0; // diagnostic only debug_mutex_lock(&conn->ab_mutex, 30000, 1); - - + int wait; long dac_delay = 0; // long because alsa returns a long - - pthread_cleanup_push(buffer_get_frame_cleanup_handler, (void *)conn); // undo what's been done so far + + pthread_cleanup_push(buffer_get_frame_cleanup_handler, + (void *)conn); // undo what's been done so far do { // get the time local_time_now = get_absolute_time_in_fp(); // type okay @@ -830,7 +830,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (conn->flush_requested == 1) { if (config.output->flush) config.output->flush(); // no cancellation points - ab_resync(conn); // no cancellation points + ab_resync(conn); // no cancellation points conn->first_packet_timestamp = 0; conn->first_packet_time_to_play = 0; conn->time_since_play_started = 0; @@ -1216,7 +1216,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { time_of_wakeup.tv_sec = sec; time_of_wakeup.tv_nsec = nsec; // pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex, &time_of_wakeup); - int rc = pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex, &time_of_wakeup); // this is a pthread cancellation point + int rc = pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex, + &time_of_wakeup); // this is a pthread cancellation point if (rc != 0) debug(3, "pthread_cond_timedwait returned error code %d.", rc); #endif @@ -1232,7 +1233,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { } while (wait); // seq_t read = conn->ab_read; - if (conn->player_thread_please_stop==0) { + if (conn->player_thread_please_stop == 0) { if (!curframe->ready) { // debug(1, "Supplying a silent frame for frame %u", read); conn->missing_packets++; @@ -1242,7 +1243,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { curframe->resend_level = 0; conn->ab_read = SUCCESSOR(conn->ab_read); } - + pthread_cleanup_pop(1); if (conn->player_thread_please_stop) @@ -1442,7 +1443,8 @@ void player_thread_cleanup_handler(void *arg) { #ifdef HAVE_DACP_CLIENT - relinquish_dacp_server_information(conn); // say it doesn't belong to this conversation thread any more... + relinquish_dacp_server_information( + conn); // say it doesn't belong to this conversation thread any more... #else // stop watching for DACP port number stuff @@ -1475,11 +1477,11 @@ void player_thread_cleanup_handler(void *arg) { if (conn->outbuf) { free(conn->outbuf); conn->outbuf = NULL; - } + } if (conn->sbuf) { free(conn->sbuf); conn->sbuf = NULL; - } + } if (conn->tbuf) { free(conn->tbuf); conn->tbuf = NULL; @@ -1655,15 +1657,17 @@ void *player_thread_func(void *arg) { // if ((input_rate!=config.output_rate) || (input_bit_depth!=output_bit_depth)) { // debug(1,"Define tbuf of length // %d.",output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); - conn->tbuf = malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + - conn->max_frame_size_change)); + conn->tbuf = + malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + + conn->max_frame_size_change)); if (conn->tbuf == NULL) die("Failed to allocate memory for the transition buffer."); // initialise this, because soxr stuffing might be chosen later - conn->sbuf = malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + - conn->max_frame_size_change)); + conn->sbuf = + malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + + conn->max_frame_size_change)); if (conn->sbuf == NULL) die("Failed to allocate memory for the sbuf buffer."); @@ -1693,7 +1697,7 @@ void *player_thread_func(void *arg) { if (conn->dapo_private_storage) debug(1, "DACP monitor already initialised?"); else - // this does not have pthread cancellation points in it (assuming avahi doesn't) + // this does not have pthread cancellation points in it (assuming avahi doesn't) conn->dapo_private_storage = mdns_dacp_monitor(conn->dacp_id); // ?? #endif @@ -1742,12 +1746,12 @@ void *player_thread_func(void *arg) { debug(3, "Set initial volume to %f.", config.airplay_volume); player_volume(config.airplay_volume, conn); // ?? int64_t frames_to_drop = 0; - - // create and start the timing, control and audio receiver threads + + // create and start the timing, control and audio receiver threads pthread_create(&conn->rtp_audio_thread, NULL, &rtp_audio_receiver, (void *)conn); pthread_create(&conn->rtp_control_thread, NULL, &rtp_control_receiver, (void *)conn); pthread_create(&conn->rtp_timing_thread, NULL, &rtp_timing_receiver, (void *)conn); - + pthread_cleanup_push(player_thread_cleanup_handler, arg); // undo what's been done so far // debug(1, "Play begin"); @@ -2152,9 +2156,9 @@ void *player_thread_func(void *arg) { case ST_soxr: #ifdef HAVE_LIBSOXR // if (amount_to_stuff) debug(1,"Soxr stuff..."); - play_samples = stuff_buffer_soxr_32((int32_t *)conn->tbuf, (int32_t *)conn->sbuf, inbuflength, - config.output_format, conn->outbuf, amount_to_stuff, - enable_dither, conn); + play_samples = stuff_buffer_soxr_32((int32_t *)conn->tbuf, (int32_t *)conn->sbuf, + inbuflength, config.output_format, conn->outbuf, + amount_to_stuff, enable_dither, conn); #endif break; } @@ -2209,8 +2213,9 @@ void *player_thread_func(void *arg) { } else { // if there is no delay procedure, or it's not working or not allowed, there can be no // synchronising - play_samples = stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format, - conn->outbuf, 0, enable_dither, conn); + play_samples = + stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format, + conn->outbuf, 0, enable_dither, conn); if (conn->outbuf == NULL) debug(1, "NULL outbuf to play -- skipping it."); else @@ -2283,26 +2288,26 @@ void *player_thread_func(void *arg) { if (at_least_one_frame_seen) { if ((config.output->delay)) { if (config.no_sync == 0) { - inform( - "|%*.1f," /* Sync error in milliseconds */ - "%*.1f," /* net correction in ppm */ - "%*.1f," /* corrections in ppm */ - "%*d," /* total packets */ - "%*llu," /* missing packets */ - "%*llu," /* late packets */ - "%*llu," /* too late packets */ - "%*llu," /* resend requests */ - "%*lli," /* min DAC queue size */ - "%*d," /* min buffer occupancy */ - "%*d", /* max buffer occupancy */ - 10, - 1000 * moving_average_sync_error / config.output_rate, - 10, moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), - 10, moving_average_insertions_plus_deletions * 1000000 / - (352 * conn->output_sample_ratio), - 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, - conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, - 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy); + inform("|%*.1f," /* Sync error in milliseconds */ + "%*.1f," /* net correction in ppm */ + "%*.1f," /* corrections in ppm */ + "%*d," /* total packets */ + "%*llu," /* missing packets */ + "%*llu," /* late packets */ + "%*llu," /* too late packets */ + "%*llu," /* resend requests */ + "%*lli," /* min DAC queue size */ + "%*d," /* min buffer occupancy */ + "%*d", /* max buffer occupancy */ + 10, + 1000 * moving_average_sync_error / config.output_rate, 10, + moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), + 10, moving_average_insertions_plus_deletions * 1000000 / + (352 * conn->output_sample_ratio), + 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, + conn->too_late_packets, 7, conn->resend_requests, 7, + minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, + maximum_buffer_occupancy); } else { inform("|%*.1f," /* Sync error in milliseconds */ "%*d," /* total packets */ @@ -2314,11 +2319,10 @@ void *player_thread_func(void *arg) { "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ 10, - 1000 * moving_average_sync_error / config.output_rate, - 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, - conn->too_late_packets, 7, conn->resend_requests, 7, - minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, - maximum_buffer_occupancy); + 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, + conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, + conn->resend_requests, 7, minimum_dac_queue_size, 5, + minimum_buffer_occupancy, 5, maximum_buffer_occupancy); } } else { inform("|%*.1f," /* Sync error in milliseconds */ @@ -2330,10 +2334,10 @@ void *player_thread_func(void *arg) { "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ 10, - 1000 * moving_average_sync_error / config.output_rate, - 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); + 1000 * moving_average_sync_error / config.output_rate, 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); } } else { inform("No frames received in the last sampling interval."); @@ -2348,70 +2352,71 @@ void *player_thread_func(void *arg) { } } -/* all done in the cleanup... + /* all done in the cleanup... - debug(3, "Connection %d: player thread main loop exit.", conn->connection_number); + debug(3, "Connection %d: player thread main loop exit.", conn->connection_number); - if (config.statistics_requested) { - int rawSeconds = (int)difftime(time(NULL), conn->playstart); - int elapsedHours = rawSeconds / 3600; - int elapsedMin = (rawSeconds / 60) % 60; - int elapsedSec = rawSeconds % 60; - inform("Playback Stopped. Total playing time %02d:%02d:%02d.", elapsedHours, elapsedMin, - elapsedSec); - } - -#ifdef HAVE_DACP_CLIENT + if (config.statistics_requested) { + int rawSeconds = (int)difftime(time(NULL), conn->playstart); + int elapsedHours = rawSeconds / 3600; + int elapsedMin = (rawSeconds / 60) % 60; + int elapsedSec = rawSeconds % 60; + inform("Playback Stopped. Total playing time %02d:%02d:%02d.", elapsedHours, elapsedMin, + elapsedSec); + } - relinquish_dacp_server_information(conn); // say it doesn't belong to this conversation thread any more... + #ifdef HAVE_DACP_CLIENT -#else - // stop watching for DACP port number stuff - // this is only used for compatability, if dacp stuff isn't enabled. - if (conn->dapo_private_storage) { - mdns_dacp_dont_monitor(conn->dapo_private_storage); - conn->dapo_private_storage = NULL; - } else { - debug(2, "DACP Monitor already stopped"); - } -#endif + relinquish_dacp_server_information(conn); // say it doesn't belong to this conversation thread + any more... - debug(2, "Cancelling timing, control and audio threads..."); - debug(2, "Cancel timing thread."); - pthread_cancel(rtp_timing_thread); - debug(2, "Join timing thread."); - pthread_join(rtp_timing_thread, NULL); - debug(2, "Timing thread terminated."); - debug(2, "Cancel control thread."); - pthread_cancel(rtp_control_thread); - debug(2, "Join control thread."); - pthread_join(rtp_control_thread, NULL); - debug(2, "Control thread terminated."); - debug(2, "Cancel audio thread."); - pthread_cancel(rtp_audio_thread); - debug(2, "Join audio thread."); - pthread_join(rtp_audio_thread, NULL); - debug(2, "Audio thread terminated."); - clear_reference_timestamp(conn); - conn->rtp_running = 0; + #else + // stop watching for DACP port number stuff + // this is only used for compatability, if dacp stuff isn't enabled. + if (conn->dapo_private_storage) { + mdns_dacp_dont_monitor(conn->dapo_private_storage); + conn->dapo_private_storage = NULL; + } else { + debug(2, "DACP Monitor already stopped"); + } + #endif - debug(3, "Connection %d: stopping output device.", conn->connection_number); + debug(2, "Cancelling timing, control and audio threads..."); + debug(2, "Cancel timing thread."); + pthread_cancel(rtp_timing_thread); + debug(2, "Join timing thread."); + pthread_join(rtp_timing_thread, NULL); + debug(2, "Timing thread terminated."); + debug(2, "Cancel control thread."); + pthread_cancel(rtp_control_thread); + debug(2, "Join control thread."); + pthread_join(rtp_control_thread, NULL); + debug(2, "Control thread terminated."); + debug(2, "Cancel audio thread."); + pthread_cancel(rtp_audio_thread); + debug(2, "Join audio thread."); + pthread_join(rtp_audio_thread, NULL); + debug(2, "Audio thread terminated."); + clear_reference_timestamp(conn); + conn->rtp_running = 0; - if (config.output->stop) - config.output->stop(); + debug(3, "Connection %d: stopping output device.", conn->connection_number); - debug(2, "Freeing audio buffers and decoders."); + if (config.output->stop) + config.output->stop(); - free_audio_buffers(conn); - terminate_decoders(conn); - debug(2, "Connection %d: player thread terminated.", conn->connection_number); - if (outbuf) - free(outbuf); - if (tbuf) - free(tbuf); - if (sbuf) - free(sbuf); - */ + debug(2, "Freeing audio buffers and decoders."); + + free_audio_buffers(conn); + terminate_decoders(conn); + debug(2, "Connection %d: player thread terminated.", conn->connection_number); + if (outbuf) + free(outbuf); + if (tbuf) + free(tbuf); + if (sbuf) + free(sbuf); + */ pthread_cleanup_pop(1); pthread_exit(NULL); } @@ -2419,7 +2424,8 @@ void *player_thread_func(void *arg) { // takes the volume as specified by the airplay protocol void player_volume_without_notification(double airplay_volume, rtsp_conn_info *conn) { - // no cancellation points here if we assume that the mute call to the back end has no cancellation points + // no cancellation points here if we assume that the mute call to the back end has no cancellation + // points // The volume ranges -144.0 (mute) or -30 -- 0. See // http://git.zx2c4.com/Airtunes2/about/#setting-volume @@ -2584,9 +2590,9 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c if (config.ignore_volume_control == 1) scaled_attenuation = max_db; else if (config.volume_control_profile == VCP_standard) - scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); // no cancellation points + scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); // no cancellation points else if (config.volume_control_profile == VCP_flat) - scaled_attenuation = flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points + scaled_attenuation = flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points else debug(1, "Unrecognised volume control profile"); @@ -2621,7 +2627,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c // %f",software_attenuation,temp_fix_volume,airplay_volume); conn->fix_volume = temp_fix_volume; - memory_barrier(); // no cancellation points + memory_barrier(); // no cancellation points if (config.loudness) loudness_set_volume(software_attenuation / 100); @@ -2712,12 +2718,14 @@ int player_stop(rtsp_conn_info *conn) { // will only ever be called by the connection thread debug(3, "player_stop"); if (conn->player_thread) { - debug(3, "player_thread exists"); - conn->player_thread_please_stop = 1; - pthread_cond_signal(&conn->flowcontrol); // tell it to give up - pthread_kill(*conn->player_thread, SIGUSR1); - debug(3, "player_thread signalled"); + debug(3, "player_thread cancel..."); + // conn->player_thread_please_stop = 1; + // pthread_cond_signal(&conn->flowcontrol); // tell it to give up + // pthread_kill(*conn->player_thread, SIGUSR1); + pthread_cancel(*conn->player_thread); + debug(3, "player_thread cancelled."); pthread_join(*conn->player_thread, NULL); + debug(2, "player_thread joined."); free(conn->player_thread); conn->player_thread = NULL; #ifdef CONFIG_METADATA diff --git a/player.h b/player.h index d9b0a053..246e7b5e 100644 --- a/player.h +++ b/player.h @@ -84,11 +84,11 @@ typedef struct { time_t playstart; pthread_t thread, timer_requester, rtp_audio_thread, rtp_control_thread, rtp_timing_thread; // pthread_t *ptp; - + signed short *tbuf; int32_t *sbuf; char *outbuf; - + pthread_t *player_thread; abuf_t audio_buffer[BUFFER_FRAMES]; int max_frames_per_packet, input_num_channels, input_bit_depth, input_rate; diff --git a/rtsp.c b/rtsp.c index 872e8b7f..b3a965b8 100644 --- a/rtsp.c +++ b/rtsp.c @@ -181,7 +181,7 @@ int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) { rc = pthread_mutex_lock(&the_queue->pc_queue_lock); if (rc) debug(1, "Error locking for pc_queue_add_item"); - pthread_cleanup_push(pc_queue_cleanup_handler,(void *)the_queue); + pthread_cleanup_push(pc_queue_cleanup_handler, (void *)the_queue); 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) @@ -217,7 +217,7 @@ int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) { rc = pthread_mutex_lock(&the_queue->pc_queue_lock); if (rc) debug(1, "Error locking for pc_queue_get_item"); - pthread_cleanup_push(pc_queue_cleanup_handler,(void *)the_queue); + pthread_cleanup_push(pc_queue_cleanup_handler, (void *)the_queue); while (the_queue->count == 0) { rc = pthread_cond_wait(&the_queue->pc_queue_item_added_signal, &the_queue->pc_queue_lock); if (rc) @@ -2021,7 +2021,7 @@ static void *rtsp_conversation_thread_func(void *pconn) { playing_conn = NULL; pthread_mutex_unlock(&play_lock); } - + if (conn->dacp_id) { free(conn->dacp_id); conn->dacp_id = NULL; From 227f8ce4571ccbbfd3b228f69eb844dd1ecda6cd Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 15 Jul 2018 14:09:07 +0100 Subject: [PATCH 012/569] Remove player_thread_please_stop stuff and the pthread_kill codes, quieten some debug messages --- player.c | 254 ++++++++++++++++++++++++++----------------------------- 1 file changed, 119 insertions(+), 135 deletions(-) diff --git a/player.c b/player.c index dec43886..8cb09122 100644 --- a/player.c +++ b/player.c @@ -785,12 +785,12 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { int notified_buffer_empty = 0; // diagnostic only debug_mutex_lock(&conn->ab_mutex, 30000, 1); - + + int wait; long dac_delay = 0; // long because alsa returns a long - - pthread_cleanup_push(buffer_get_frame_cleanup_handler, - (void *)conn); // undo what's been done so far + + pthread_cleanup_push(buffer_get_frame_cleanup_handler, (void *)conn); // undo what's been done so far do { // get the time local_time_now = get_absolute_time_in_fp(); // type okay @@ -830,7 +830,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (conn->flush_requested == 1) { if (config.output->flush) config.output->flush(); // no cancellation points - ab_resync(conn); // no cancellation points + ab_resync(conn); // no cancellation points conn->first_packet_timestamp = 0; conn->first_packet_time_to_play = 0; conn->time_since_play_started = 0; @@ -1198,8 +1198,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { } do_wait = 1; } - wait = (conn->ab_buffering || (do_wait != 0) || (!conn->ab_synced)) && - (!conn->player_thread_please_stop); + wait = (conn->ab_buffering || (do_wait != 0) || (!conn->ab_synced)); if (wait) { uint64_t time_to_wait_for_wakeup_fp = @@ -1216,8 +1215,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { time_of_wakeup.tv_sec = sec; time_of_wakeup.tv_nsec = nsec; // pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex, &time_of_wakeup); - int rc = pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex, - &time_of_wakeup); // this is a pthread cancellation point + int rc = pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex, &time_of_wakeup); // this is a pthread cancellation point if (rc != 0) debug(3, "pthread_cond_timedwait returned error code %d.", rc); #endif @@ -1233,7 +1231,6 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { } while (wait); // seq_t read = conn->ab_read; - if (conn->player_thread_please_stop == 0) { if (!curframe->ready) { // debug(1, "Supplying a silent frame for frame %u", read); conn->missing_packets++; @@ -1242,13 +1239,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { curframe->ready = 0; curframe->resend_level = 0; conn->ab_read = SUCCESSOR(conn->ab_read); - } - - pthread_cleanup_pop(1); - - if (conn->player_thread_please_stop) - return 0; - else + + pthread_cleanup_pop(1); return curframe; } @@ -1443,8 +1435,7 @@ void player_thread_cleanup_handler(void *arg) { #ifdef HAVE_DACP_CLIENT - relinquish_dacp_server_information( - conn); // say it doesn't belong to this conversation thread any more... + relinquish_dacp_server_information(conn); // say it doesn't belong to this conversation thread any more... #else // stop watching for DACP port number stuff @@ -1477,11 +1468,11 @@ void player_thread_cleanup_handler(void *arg) { if (conn->outbuf) { free(conn->outbuf); conn->outbuf = NULL; - } + } if (conn->sbuf) { free(conn->sbuf); conn->sbuf = NULL; - } + } if (conn->tbuf) { free(conn->tbuf); conn->tbuf = NULL; @@ -1501,7 +1492,6 @@ void *player_thread_func(void *arg) { conn->packet_count = 0; conn->previous_random_number = 0; conn->input_bytes_per_frame = 4; - conn->player_thread_please_stop = 0; conn->decoder_in_use = 0; conn->ab_buffering = 1; conn->ab_synced = 0; @@ -1657,17 +1647,15 @@ void *player_thread_func(void *arg) { // if ((input_rate!=config.output_rate) || (input_bit_depth!=output_bit_depth)) { // debug(1,"Define tbuf of length // %d.",output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); - conn->tbuf = - malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + - conn->max_frame_size_change)); + conn->tbuf = malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + + conn->max_frame_size_change)); if (conn->tbuf == NULL) die("Failed to allocate memory for the transition buffer."); // initialise this, because soxr stuffing might be chosen later - conn->sbuf = - malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + - conn->max_frame_size_change)); + conn->sbuf = malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + + conn->max_frame_size_change)); if (conn->sbuf == NULL) die("Failed to allocate memory for the sbuf buffer."); @@ -1697,7 +1685,7 @@ void *player_thread_func(void *arg) { if (conn->dapo_private_storage) debug(1, "DACP monitor already initialised?"); else - // this does not have pthread cancellation points in it (assuming avahi doesn't) + // this does not have pthread cancellation points in it (assuming avahi doesn't) conn->dapo_private_storage = mdns_dacp_monitor(conn->dacp_id); // ?? #endif @@ -1746,16 +1734,16 @@ void *player_thread_func(void *arg) { debug(3, "Set initial volume to %f.", config.airplay_volume); player_volume(config.airplay_volume, conn); // ?? int64_t frames_to_drop = 0; - - // create and start the timing, control and audio receiver threads + + // create and start the timing, control and audio receiver threads pthread_create(&conn->rtp_audio_thread, NULL, &rtp_audio_receiver, (void *)conn); pthread_create(&conn->rtp_control_thread, NULL, &rtp_control_receiver, (void *)conn); pthread_create(&conn->rtp_timing_thread, NULL, &rtp_timing_receiver, (void *)conn); - + pthread_cleanup_push(player_thread_cleanup_handler, arg); // undo what's been done so far // debug(1, "Play begin"); - while (!conn->player_thread_please_stop) { + while (1) { abuf_t *inframe = buffer_get_frame(conn); // this has cancellation point(s) if (inframe) { inbuf = inframe->data; @@ -2012,7 +2000,7 @@ void *player_thread_func(void *arg) { abs_sync_error = -abs_sync_error; if ((config.no_sync == 0) && (inframe->timestamp != 0) && - (!conn->player_thread_please_stop) && (config.resyncthreshold > 0.0) && + (config.resyncthreshold > 0.0) && (abs_sync_error > config.resyncthreshold * config.output_rate)) { if (abs_sync_error > 3 * config.output_rate) { warn("Very large sync error: %" PRId64 " frames, with delay: %" PRId64 @@ -2156,9 +2144,9 @@ void *player_thread_func(void *arg) { case ST_soxr: #ifdef HAVE_LIBSOXR // if (amount_to_stuff) debug(1,"Soxr stuff..."); - play_samples = stuff_buffer_soxr_32((int32_t *)conn->tbuf, (int32_t *)conn->sbuf, - inbuflength, config.output_format, conn->outbuf, - amount_to_stuff, enable_dither, conn); + play_samples = stuff_buffer_soxr_32((int32_t *)conn->tbuf, (int32_t *)conn->sbuf, inbuflength, + config.output_format, conn->outbuf, amount_to_stuff, + enable_dither, conn); #endif break; } @@ -2191,7 +2179,7 @@ void *player_thread_func(void *arg) { // timestamp of zero means an inserted silent frame in place of a missing frame /* if ((config.no_sync == 0) && (inframe->timestamp != 0) && - (!conn->player_thread_please_stop) && (config.resyncthreshold > 0.0) && + && (config.resyncthreshold > 0.0) && (abs_sync_error > config.resyncthreshold * config.output_rate)) { sync_error_out_of_bounds++; // debug(1,"Sync error out of bounds: Error: %lld; previous error: %lld; DAC: %lld; @@ -2213,9 +2201,8 @@ void *player_thread_func(void *arg) { } else { // if there is no delay procedure, or it's not working or not allowed, there can be no // synchronising - play_samples = - stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format, - conn->outbuf, 0, enable_dither, conn); + play_samples = stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format, + conn->outbuf, 0, enable_dither, conn); if (conn->outbuf == NULL) debug(1, "NULL outbuf to play -- skipping it."); else @@ -2288,26 +2275,26 @@ void *player_thread_func(void *arg) { if (at_least_one_frame_seen) { if ((config.output->delay)) { if (config.no_sync == 0) { - inform("|%*.1f," /* Sync error in milliseconds */ - "%*.1f," /* net correction in ppm */ - "%*.1f," /* corrections in ppm */ - "%*d," /* total packets */ - "%*llu," /* missing packets */ - "%*llu," /* late packets */ - "%*llu," /* too late packets */ - "%*llu," /* resend requests */ - "%*lli," /* min DAC queue size */ - "%*d," /* min buffer occupancy */ - "%*d", /* max buffer occupancy */ - 10, - 1000 * moving_average_sync_error / config.output_rate, 10, - moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), - 10, moving_average_insertions_plus_deletions * 1000000 / - (352 * conn->output_sample_ratio), - 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, - conn->too_late_packets, 7, conn->resend_requests, 7, - minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, - maximum_buffer_occupancy); + inform( + "|%*.1f," /* Sync error in milliseconds */ + "%*.1f," /* net correction in ppm */ + "%*.1f," /* corrections in ppm */ + "%*d," /* total packets */ + "%*llu," /* missing packets */ + "%*llu," /* late packets */ + "%*llu," /* too late packets */ + "%*llu," /* resend requests */ + "%*lli," /* min DAC queue size */ + "%*d," /* min buffer occupancy */ + "%*d", /* max buffer occupancy */ + 10, + 1000 * moving_average_sync_error / config.output_rate, + 10, moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), + 10, moving_average_insertions_plus_deletions * 1000000 / + (352 * conn->output_sample_ratio), + 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, + conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, + 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy); } else { inform("|%*.1f," /* Sync error in milliseconds */ "%*d," /* total packets */ @@ -2319,10 +2306,11 @@ void *player_thread_func(void *arg) { "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ 10, - 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, - conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, - conn->resend_requests, 7, minimum_dac_queue_size, 5, - minimum_buffer_occupancy, 5, maximum_buffer_occupancy); + 1000 * moving_average_sync_error / config.output_rate, + 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, + conn->too_late_packets, 7, conn->resend_requests, 7, + minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, + maximum_buffer_occupancy); } } else { inform("|%*.1f," /* Sync error in milliseconds */ @@ -2334,10 +2322,10 @@ void *player_thread_func(void *arg) { "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ 10, - 1000 * moving_average_sync_error / config.output_rate, 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); + 1000 * moving_average_sync_error / config.output_rate, + 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); } } else { inform("No frames received in the last sampling interval."); @@ -2351,72 +2339,73 @@ void *player_thread_func(void *arg) { } } } + + debug(1,"This should never be called."); - /* all done in the cleanup... +/* all done in the cleanup... - debug(3, "Connection %d: player thread main loop exit.", conn->connection_number); + debug(3, "Connection %d: player thread main loop exit.", conn->connection_number); - if (config.statistics_requested) { - int rawSeconds = (int)difftime(time(NULL), conn->playstart); - int elapsedHours = rawSeconds / 3600; - int elapsedMin = (rawSeconds / 60) % 60; - int elapsedSec = rawSeconds % 60; - inform("Playback Stopped. Total playing time %02d:%02d:%02d.", elapsedHours, elapsedMin, - elapsedSec); - } + if (config.statistics_requested) { + int rawSeconds = (int)difftime(time(NULL), conn->playstart); + int elapsedHours = rawSeconds / 3600; + int elapsedMin = (rawSeconds / 60) % 60; + int elapsedSec = rawSeconds % 60; + inform("Playback Stopped. Total playing time %02d:%02d:%02d.", elapsedHours, elapsedMin, + elapsedSec); + } + +#ifdef HAVE_DACP_CLIENT - #ifdef HAVE_DACP_CLIENT + relinquish_dacp_server_information(conn); // say it doesn't belong to this conversation thread any more... - relinquish_dacp_server_information(conn); // say it doesn't belong to this conversation thread - any more... +#else + // stop watching for DACP port number stuff + // this is only used for compatability, if dacp stuff isn't enabled. + if (conn->dapo_private_storage) { + mdns_dacp_dont_monitor(conn->dapo_private_storage); + conn->dapo_private_storage = NULL; + } else { + debug(2, "DACP Monitor already stopped"); + } +#endif - #else - // stop watching for DACP port number stuff - // this is only used for compatability, if dacp stuff isn't enabled. - if (conn->dapo_private_storage) { - mdns_dacp_dont_monitor(conn->dapo_private_storage); - conn->dapo_private_storage = NULL; - } else { - debug(2, "DACP Monitor already stopped"); - } - #endif + debug(2, "Cancelling timing, control and audio threads..."); + debug(2, "Cancel timing thread."); + pthread_cancel(rtp_timing_thread); + debug(2, "Join timing thread."); + pthread_join(rtp_timing_thread, NULL); + debug(2, "Timing thread terminated."); + debug(2, "Cancel control thread."); + pthread_cancel(rtp_control_thread); + debug(2, "Join control thread."); + pthread_join(rtp_control_thread, NULL); + debug(2, "Control thread terminated."); + debug(2, "Cancel audio thread."); + pthread_cancel(rtp_audio_thread); + debug(2, "Join audio thread."); + pthread_join(rtp_audio_thread, NULL); + debug(2, "Audio thread terminated."); + clear_reference_timestamp(conn); + conn->rtp_running = 0; - debug(2, "Cancelling timing, control and audio threads..."); - debug(2, "Cancel timing thread."); - pthread_cancel(rtp_timing_thread); - debug(2, "Join timing thread."); - pthread_join(rtp_timing_thread, NULL); - debug(2, "Timing thread terminated."); - debug(2, "Cancel control thread."); - pthread_cancel(rtp_control_thread); - debug(2, "Join control thread."); - pthread_join(rtp_control_thread, NULL); - debug(2, "Control thread terminated."); - debug(2, "Cancel audio thread."); - pthread_cancel(rtp_audio_thread); - debug(2, "Join audio thread."); - pthread_join(rtp_audio_thread, NULL); - debug(2, "Audio thread terminated."); - clear_reference_timestamp(conn); - conn->rtp_running = 0; + debug(3, "Connection %d: stopping output device.", conn->connection_number); - debug(3, "Connection %d: stopping output device.", conn->connection_number); + if (config.output->stop) + config.output->stop(); - if (config.output->stop) - config.output->stop(); + debug(2, "Freeing audio buffers and decoders."); - debug(2, "Freeing audio buffers and decoders."); - - free_audio_buffers(conn); - terminate_decoders(conn); - debug(2, "Connection %d: player thread terminated.", conn->connection_number); - if (outbuf) - free(outbuf); - if (tbuf) - free(tbuf); - if (sbuf) - free(sbuf); - */ + free_audio_buffers(conn); + terminate_decoders(conn); + debug(2, "Connection %d: player thread terminated.", conn->connection_number); + if (outbuf) + free(outbuf); + if (tbuf) + free(tbuf); + if (sbuf) + free(sbuf); + */ pthread_cleanup_pop(1); pthread_exit(NULL); } @@ -2424,8 +2413,7 @@ void *player_thread_func(void *arg) { // takes the volume as specified by the airplay protocol void player_volume_without_notification(double airplay_volume, rtsp_conn_info *conn) { - // no cancellation points here if we assume that the mute call to the back end has no cancellation - // points + // no cancellation points here if we assume that the mute call to the back end has no cancellation points // The volume ranges -144.0 (mute) or -30 -- 0. See // http://git.zx2c4.com/Airtunes2/about/#setting-volume @@ -2590,9 +2578,9 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c if (config.ignore_volume_control == 1) scaled_attenuation = max_db; else if (config.volume_control_profile == VCP_standard) - scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); // no cancellation points + scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); // no cancellation points else if (config.volume_control_profile == VCP_flat) - scaled_attenuation = flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points + scaled_attenuation = flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points else debug(1, "Unrecognised volume control profile"); @@ -2627,7 +2615,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c // %f",software_attenuation,temp_fix_volume,airplay_volume); conn->fix_volume = temp_fix_volume; - memory_barrier(); // no cancellation points + memory_barrier(); // no cancellation points if (config.loudness) loudness_set_volume(software_attenuation / 100); @@ -2718,12 +2706,8 @@ int player_stop(rtsp_conn_info *conn) { // will only ever be called by the connection thread debug(3, "player_stop"); if (conn->player_thread) { - debug(3, "player_thread cancel..."); - // conn->player_thread_please_stop = 1; - // pthread_cond_signal(&conn->flowcontrol); // tell it to give up - // pthread_kill(*conn->player_thread, SIGUSR1); + debug(2, "player_thread cancel..."); pthread_cancel(*conn->player_thread); - debug(3, "player_thread cancelled."); pthread_join(*conn->player_thread, NULL); debug(2, "player_thread joined."); free(conn->player_thread); From b71ec06ac25eb07b987ee84ff09dbef9e0c73e36 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 15 Jul 2018 14:10:42 +0100 Subject: [PATCH 013/569] Move player_stop away from pthread_kill to pthread_cancel. Add in cancellation stacks. Check for hidden cancellation points. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 4ddb55f5..f2d0c8ba 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.2.2p0], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.2.2p1], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From dc84d87b7b98455523d4766d62d773776af60a1e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 15 Jul 2018 15:20:25 +0100 Subject: [PATCH 014/569] Make session interruption work properly. --- rtsp.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/rtsp.c b/rtsp.c index b3a965b8..e96c29ca 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1482,15 +1482,14 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag debug(1, "Playing connection is already shutting down; waiting for it..."); should_wait = 1; } else if (config.allow_session_interruption == 1) { - // some other thread has the player ... ask it to relinquish the thread + // some thread has the player ... ask it to relinquish the thread debug(1, "ANNOUNCE: playing connection %d being interrupted by connection %d.", playing_conn->connection_number, conn->connection_number); if (playing_conn == conn) { - debug(1, "ANNOUNCE asking to stop itself."); + debug(1, "ANNOUNCE asking to stop itself! Nothing done."); } else { + player_stop(playing_conn); playing_conn->stop = 1; - memory_barrier(); - pthread_kill(playing_conn->thread, SIGUSR1); should_wait = 1; } } From 9a8a86382539cac7ce79d2abea7dd3208713a61b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 15 Jul 2018 16:25:56 +0100 Subject: [PATCH 015/569] Clean out player_+thread_please_stop references. --- player.c | 3 ++- player.h | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/player.c b/player.c index 8cb09122..3f697c42 100644 --- a/player.c +++ b/player.c @@ -2703,7 +2703,8 @@ int player_play(rtsp_conn_info *conn) { } int player_stop(rtsp_conn_info *conn) { - // will only ever be called by the connection thread + // note -- this may be called from another connection thread or from the player thread itself + // as well as from the conn's own thread. debug(3, "player_stop"); if (conn->player_thread) { debug(2, "player_thread cancel..."); diff --git a/player.h b/player.h index 246e7b5e..793476e6 100644 --- a/player.h +++ b/player.h @@ -98,7 +98,6 @@ typedef struct { alac_file *decoder_info; uint64_t packet_count; int connection_state_to_output; - int player_thread_please_stop; uint64_t first_packet_time_to_play; int64_t time_since_play_started; // nanoseconds // stats From ebacd7f340069833aceb9aebb14c6c195a95020f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 15 Jul 2018 16:48:45 +0100 Subject: [PATCH 016/569] Add a little note to self. --- player.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/player.c b/player.c index 3f697c42..ff115064 100644 --- a/player.c +++ b/player.c @@ -2703,8 +2703,7 @@ int player_play(rtsp_conn_info *conn) { } int player_stop(rtsp_conn_info *conn) { - // note -- this may be called from another connection thread or from the player thread itself - // as well as from the conn's own thread. + // note -- this may be called from another connection thread. debug(3, "player_stop"); if (conn->player_thread) { debug(2, "player_thread cancel..."); From 624ac69a619aee1c68d26dd840d74371ef3a8ab4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 15 Jul 2018 18:30:06 +0100 Subject: [PATCH 017/569] Delete CAR SETUP ALTERNATIVE APPROACH.md Moved to a Discussion Thread --- CAR SETUP ALTERNATIVE APPROACH.md | 92 ------------------------------- 1 file changed, 92 deletions(-) delete mode 100644 CAR SETUP ALTERNATIVE APPROACH.md diff --git a/CAR SETUP ALTERNATIVE APPROACH.md b/CAR SETUP ALTERNATIVE APPROACH.md deleted file mode 100644 index bd128af4..00000000 --- a/CAR SETUP ALTERNATIVE APPROACH.md +++ /dev/null @@ -1,92 +0,0 @@ -# From [FnasBas](https://github.com/FnasBas) -This is based on [this comment](https://github.com/mikebrady/shairport-sync/issues/482#issuecomment-402543454). - -Here's a starting point. I hope it is useful to someone :) - -# Alternative car setup approach -This is offered in the hopes it will be useful to someone. The background is that, besides all the advantages of WiFi/AirPlay over Bluetooth, -when travelling my phone is my only internet connection, and I want to be able to share it with other devices. -Like an iPad or laptop. I may also want to be able to send audio to the car Shairport Sync service from my other devices while -simultaneously having internet access on them, via the phone. - -So the basic idea is just to turn the table on which device acts as the Access Point for the WiFi. Which works perfectly well. -The prerequisite is that your carrier/contract allows tethering, as otherwise you won't be able to have your phone act as a WiFi Access Point. -I also think this is a bit simpler to set up as you don't need to set up `hostapd` and a DHCP server on the Pi. -The drawback is that you need to do a gesture and tap a button on the phone to make its network discoverable when entering your car. - -## Phone configuration -Enable tethering on your iPhone with a secure passphrase. - -## Raspberry Pi configuration -Set up Shairport Sync according to your needs and instructions found elsewhere. - -### Network -Configure your Raspberry Pi with only one WiFi network. Through experimentation I found that if I was in the vicinity of my home network, -the Pi would not reliably choose the iPhone's WiFi network even though it was prioritized over the home network. -And it seems `wpa-supplicant` in general is a bit difficult to get to roam between different WiFi networks once it's decided on one. - -Example of `/etc/wpa_supplicant/wpa_supplicant.conf` - -``` -ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev -update_config=1 -country=SE -network={ - priority=3 - ssid="My unique clever wifi network name" - psk="My very secure wifi key" -} -``` - -### Read only root filesystem -By configuring your Pi to have a read only root filesystem, you need not ever worry about an improper shutdown of the OS -causing the Pi to fail to boot. You will also be sure that the Pi always boots to a predetermined state. -And you will eleminate wear on the SD card completely except for when modifying the system. -This will result in a very reliable AirPlay service. - -These instructions are a bit old but a good starting point: (https://openenergymonitor.org/forum-archive/node/5331.html) - -In addition to what's laid out there I've also: - -* Added the `/boot` fs to be mounted read-only and modified the rpi-rw/ro scripts and `/etc/fstab` accordingly -* in `/etc/systemd/journald.conf`, set `storage=volatile` -* As the `systemd` `ntp` client is hard-coded to use a specific path for its book keeping, I've disabled that and installed `ntpd` -and set the drift file to `/tmp/drift` -* Your DHCP client will complain that it can't write its lease to a file. You can work around this by setting it to write its lease -to a file in `/tmp` or disregard it as it will work fine in any case. - -In my case I do not use a car stereo but go straight Pi>Dac>Power amplifier. So I've also set up EQ with `alsa`, and a relay module to a -GPIO pin and some scripts that run on session start/stop that turns the power amplifier on and off accordingly. -But this could turn in to "How I set up my car audio" rather than offering an alternative to [what's currently suggested -for a car installation](https://github.com/mikebrady/shairport-sync/blob/master/CAR%20INSTALL.md). -So I stop. ;) - -## Using it -Make your phone's network discoverable. In iOS 11 there's a shortcut for this – swipe up and hold down on the connectivity section. -There's a button to turn on Internet Sharing. -In my experiments the Pi connects almost instantaneously to this network and a few moments later the AirPlay service offered by Shairport Sync -will become visible. - -## Changing and Maintaining it -Make your phone network discoverable. The Pi connects. Connect your laptop to the same network. -The iPhone does not appear to resolve host names from one tethered device to another so you need to `ssh` to the Pi's IP-address. -There's a number of ways to find the Pi's IP-address: - -* You can check your laptop's IP and try subtracting or adding from the last octet -* Use a `zeroconf/avahi` browser tool and locate the AirPlay service and find the IP address that way -* Use `nmap` or other similar utility to scan for the Pi - -`ssh` to the Pi -``` -rpi-rw -change stuff -rpi-ro -``` - -### Further thinking -Other Apple devices can wake the WiFi Access Point on the iPhone. My conclusion is that this must be accomplished via Bluetooth, -but Apple doesn't seem to be open spec on this subject as all I've been able to find is that certain devices, when paired with Bluetooth, -connected to iCloud etc. etc. will offer this functionality. -Being able to reverse-engineer what Bluetooth communication is actually done in order to turn on the WiFi Access Point on the phone -would mitigate the drawback of this method, as the Paspberry Pi could be constantly looking for the phone via Bluetooth and -enabling Internet Sharing that way. From a4eaace76b07027f5832071de4ed44c726b91786 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 17 Jul 2018 10:28:06 +0100 Subject: [PATCH 018/569] Begin to try to get Shairport Sync to quit cleanly, relinquishing memory and ports etc. properly so that valgrind can be more useful. --- dbus-service.c | 11 +++++ mpris-service.c | 11 ++++- org.gnome.ShairportSync.xml | 1 + player.h | 3 +- rtsp.c | 99 +++++++++++++++++++++---------------- 5 files changed, 81 insertions(+), 44 deletions(-) diff --git a/dbus-service.c b/dbus-service.c index 14ace35b..b16b22da 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -544,6 +544,14 @@ gboolean notify_loop_status_callback(ShairportSyncAdvancedRemoteControl *skeleto return TRUE; } +static gboolean on_handle_quit(ShairportSync *skeleton, GDBusMethodInvocation *invocation, + __attribute__((unused)) const gchar *command, + __attribute__((unused)) gpointer user_data) { + debug(1, "quit requested (native interface)"); + shairport_sync_complete_quit(skeleton, invocation); + return TRUE; +} + static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInvocation *invocation, const gchar *command, __attribute__((unused)) gpointer user_data) { @@ -589,6 +597,9 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name g_signal_connect(shairportSyncSkeleton, "notify::loudness-threshold", G_CALLBACK(notify_loudness_threshold_callback), NULL); + g_signal_connect(shairportSyncSkeleton, "handle-quit", + G_CALLBACK(on_handle_quit), NULL); + g_signal_connect(shairportSyncSkeleton, "handle-remote-command", G_CALLBACK(on_handle_remote_command), NULL); diff --git a/mpris-service.c b/mpris-service.c index 4901a819..982623e1 100644 --- a/mpris-service.c +++ b/mpris-service.c @@ -183,6 +183,13 @@ void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused) // media_player2_player_set_volume(mprisPlayerPlayerSkeleton, metadata_store.speaker_volume); } +static gboolean on_handle_quit(MediaPlayer2 *skeleton, GDBusMethodInvocation *invocation, + __attribute__((unused)) gpointer user_data) { + debug(1,"quit requested (MPRIS interface)."); + media_player2_complete_quit(skeleton, invocation); + return TRUE; +} + static gboolean on_handle_next(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { send_simple_dacp_command("nextitem"); @@ -243,7 +250,7 @@ static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *nam media_player2_set_desktop_entry(mprisPlayerSkeleton, "shairport-sync"); media_player2_set_identity(mprisPlayerSkeleton, "Shairport Sync"); - media_player2_set_can_quit(mprisPlayerSkeleton, FALSE); + media_player2_set_can_quit(mprisPlayerSkeleton, TRUE); media_player2_set_can_raise(mprisPlayerSkeleton, FALSE); media_player2_set_has_track_list(mprisPlayerSkeleton, FALSE); media_player2_set_supported_uri_schemes(mprisPlayerSkeleton, empty_string_array); @@ -261,6 +268,8 @@ static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *nam media_player2_player_set_can_seek(mprisPlayerPlayerSkeleton, FALSE); media_player2_player_set_can_control(mprisPlayerPlayerSkeleton, TRUE); + g_signal_connect(mprisPlayerSkeleton, "handle-quit", G_CALLBACK(on_handle_quit), NULL); + g_signal_connect(mprisPlayerPlayerSkeleton, "handle-play", G_CALLBACK(on_handle_play), NULL); g_signal_connect(mprisPlayerPlayerSkeleton, "handle-pause", G_CALLBACK(on_handle_pause), NULL); g_signal_connect(mprisPlayerPlayerSkeleton, "handle-play-pause", G_CALLBACK(on_handle_play_pause), diff --git a/org.gnome.ShairportSync.xml b/org.gnome.ShairportSync.xml index aa693fd5..1b734bff 100644 --- a/org.gnome.ShairportSync.xml +++ b/org.gnome.ShairportSync.xml @@ -1,6 +1,7 @@ + diff --git a/player.h b/player.h index 793476e6..295b0ccd 100644 --- a/player.h +++ b/player.h @@ -74,9 +74,10 @@ typedef struct { // otherwise int64_t maximum_latency; // set if an a=max-latency: line appears in the ANNOUNCE message; zero // otherwise - + int fd; int authorized; // set if a password is required and has been supplied + char *auth_nonce; // the session nonce, if needed stream_cfg stream; SOCKADDR remote, local; int stop; diff --git a/rtsp.c b/rtsp.c index e96c29ca..5b5a3428 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1723,7 +1723,7 @@ static void apple_challenge(int fd, rtsp_message *req, rtsp_message *resp) { if (padding) *padding = 0; - msg_add_header(resp, "Apple-Response", encoded); + msg_add_header(resp, "Apple-Response", encoded); // will be freed when the response is freed. free(challresp); free(encoded); } @@ -1737,7 +1737,7 @@ static char *make_nonce(void) { if (read(fd, random, sizeof(random)) != sizeof(random)) debug(1, "Error reading /dev/random"); close(fd); - return base64_enc(random, 8); + return base64_enc(random, 8); // returns a pointer to malloc'ed memory } static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { @@ -1888,6 +1888,48 @@ authenticate: return 1; } +void rtsp_conversation_thread_cleanup_function(void *arg) { + debug(1,"rtsp_conversation_thread_func_cleanup_function called."); + rtsp_conn_info *conn = (rtsp_conn_info *)arg; + if (conn->fd > 0) + close(conn->fd); + if (conn->auth_nonce) { + free(conn->auth_nonce); + conn->auth_nonce = NULL; + } + rtp_terminate(conn); + if (playing_conn == conn) { + debug(3, "Unlocking play lock on RTSP conversation thread %d.", conn->connection_number); + playing_conn = NULL; + pthread_mutex_unlock(&play_lock); + } + + if (conn->dacp_id) { + free(conn->dacp_id); + conn->dacp_id = NULL; + } + + debug(2, "Connection %d: RTSP thread terminated.", conn->connection_number); + conn->running = 0; + + // remove flow control and mutexes + int rc = pthread_cond_destroy(&conn->flowcontrol); + if (rc) + debug(1, "Connection %d: error %d destroying flow control condition variable.", + conn->connection_number, rc); + rc = pthread_mutex_destroy(&conn->ab_mutex); + if (rc) + debug(1, "Connection %d: error %d destroying ab_mutex.", conn->connection_number, rc); + rc = pthread_mutex_destroy(&conn->flush_mutex); + if (rc) + debug(1, "Connection %d: error %d destroying flush_mutex.", conn->connection_number, rc); + } + +void msg_cleanup_function(void *arg) { + debug(1,"msg_cleanup_function called."); + msg_free((rtsp_message *)arg); +} + static void *rtsp_conversation_thread_func(void *pconn) { rtsp_conn_info *conn = pconn; @@ -1912,26 +1954,29 @@ static void *rtsp_conversation_thread_func(void *pconn) { conn->connection_number, rc); rtp_initialise(conn); - - rtsp_message *req, *resp; - char *hdr, *auth_nonce = NULL; + char *hdr = NULL; enum rtsp_read_request_response reply; int rtsp_read_request_attempt_count = 1; // 1 means exit immediately + rtsp_message *req, *resp; + pthread_cleanup_push(rtsp_conversation_thread_cleanup_function,(void *)conn); while (conn->stop == 0) { int debug_level = 3; // for printing the request and response - reply = rtsp_read_request(conn, &req); + reply = rtsp_read_request(conn,&req); if (reply == rtsp_read_request_response_ok) { + pthread_cleanup_push(msg_cleanup_function,(void*)req); + resp = msg_init(); + pthread_cleanup_push(msg_cleanup_function,(void*)resp); + resp->respcode = 400; + 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, "RTSP thread %d received an RTSP Packet of type \"%s\":", conn->connection_number, req->method), debug_print_msg_headers(debug_level, req); - resp = msg_init(); - resp->respcode = 400; apple_challenge(conn->fd, req, resp); hdr = msg_get_header(req, "CSeq"); @@ -1940,7 +1985,7 @@ static void *rtsp_conversation_thread_func(void *pconn) { // msg_add_header(resp, "Audio-Jack-Status", "connected; type=analog"); msg_add_header(resp, "Server", "AirTunes/105.1"); - if ((conn->authorized == 1) || (rtsp_auth(&auth_nonce, req, resp)) == 0) { + if ((conn->authorized == 1) || (rtsp_auth(&conn->auth_nonce, req, resp)) == 0) { conn->authorized = 1; // it must have been authorized or didn't need a password struct method_handler *mh; int method_selected = 0; @@ -1967,8 +2012,8 @@ static void *rtsp_conversation_thread_func(void *pconn) { if (conn->stop == 0) { msg_write_response(conn->fd, resp); } - msg_free(req); - msg_free(resp); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); } else { int tstop = 0; if (reply == rtsp_read_request_response_immediate_shutdown_requested) @@ -2009,37 +2054,7 @@ static void *rtsp_conversation_thread_func(void *pconn) { } } } - - if (conn->fd > 0) - close(conn->fd); - if (auth_nonce) - free(auth_nonce); - rtp_terminate(conn); - if (playing_conn == conn) { - debug(3, "Unlocking play lock on RTSP conversation thread %d.", conn->connection_number); - playing_conn = NULL; - pthread_mutex_unlock(&play_lock); - } - - if (conn->dacp_id) { - free(conn->dacp_id); - conn->dacp_id = NULL; - } - - debug(2, "Connection %d: RTSP thread terminated.", conn->connection_number); - conn->running = 0; - - // remove flow control and mutexes - rc = pthread_cond_destroy(&conn->flowcontrol); - if (rc) - debug(1, "Connection %d: error %d destroying flow control condition variable.", - conn->connection_number, rc); - rc = pthread_mutex_destroy(&conn->ab_mutex); - if (rc) - debug(1, "Connection %d: error %d destroying ab_mutex.", conn->connection_number, rc); - rc = pthread_mutex_destroy(&conn->flush_mutex); - if (rc) - debug(1, "Connection %d: error %d destroying flush_mutex.", conn->connection_number, rc); + pthread_cleanup_pop(1); pthread_exit(NULL); } From c8c70b60160768fd69ce30759e9df089b6caadac Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 18 Jul 2018 15:32:07 +0100 Subject: [PATCH 019/569] Add quit commands to the MPRIS and native D-Bus command set. Not tested ecologically. --- common.h | 5 + dbus-service.c | 11 ++- mpris-service.c | 3 +- player.c | 252 +++++++++++++++++++++++++----------------------- player.h | 4 +- rtsp.c | 15 +-- shairport.c | 61 +++++++++--- 7 files changed, 198 insertions(+), 153 deletions(-) diff --git a/common.h b/common.h index 86cfa1a1..7d556b96 100644 --- a/common.h +++ b/common.h @@ -2,6 +2,7 @@ #define _COMMON_H #include +#include #include #include #include @@ -79,6 +80,7 @@ typedef struct { char *password; char *service_name; // the name for the shairport service, e.g. "Shairport Sync Version %v running // on host %h" + #ifdef CONFIG_PA char *pa_application_name; // the name under which Shairport Sync shows up as an "Application" in // the Sound Preferences in most desktop Linuxes. @@ -263,6 +265,9 @@ uint64_t fp_time_at_startup, fp_time_at_last_debug_message; long endianness; uint32_t uatoi(const char *nptr); +// this is for allowing us to cancel the whole program +pthread_t main_thread_id; + shairport_cfg config; config_t config_file_stuff; diff --git a/dbus-service.c b/dbus-service.c index b16b22da..e8d50c2f 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -545,9 +545,13 @@ gboolean notify_loop_status_callback(ShairportSyncAdvancedRemoteControl *skeleto } static gboolean on_handle_quit(ShairportSync *skeleton, GDBusMethodInvocation *invocation, - __attribute__((unused)) const gchar *command, - __attribute__((unused)) gpointer user_data) { + __attribute__((unused)) const gchar *command, + __attribute__((unused)) gpointer user_data) { debug(1, "quit requested (native interface)"); + if (main_thread_id) + debug(1, "Cancelling main thread results in %d.", pthread_cancel(main_thread_id)); + else + debug(1, "Main thread ID is NULL."); shairport_sync_complete_quit(skeleton, invocation); return TRUE; } @@ -597,8 +601,7 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name g_signal_connect(shairportSyncSkeleton, "notify::loudness-threshold", G_CALLBACK(notify_loudness_threshold_callback), NULL); - g_signal_connect(shairportSyncSkeleton, "handle-quit", - G_CALLBACK(on_handle_quit), NULL); + g_signal_connect(shairportSyncSkeleton, "handle-quit", G_CALLBACK(on_handle_quit), NULL); g_signal_connect(shairportSyncSkeleton, "handle-remote-command", G_CALLBACK(on_handle_remote_command), NULL); diff --git a/mpris-service.c b/mpris-service.c index 982623e1..668d776e 100644 --- a/mpris-service.c +++ b/mpris-service.c @@ -185,7 +185,8 @@ void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused) static gboolean on_handle_quit(MediaPlayer2 *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { - debug(1,"quit requested (MPRIS interface)."); + debug(1, "quit requested (MPRIS interface)."); + pthread_cancel(main_thread_id); media_player2_complete_quit(skeleton, invocation); return TRUE; } diff --git a/player.c b/player.c index ff115064..ff0f7392 100644 --- a/player.c +++ b/player.c @@ -785,12 +785,12 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { int notified_buffer_empty = 0; // diagnostic only debug_mutex_lock(&conn->ab_mutex, 30000, 1); - - + int wait; long dac_delay = 0; // long because alsa returns a long - - pthread_cleanup_push(buffer_get_frame_cleanup_handler, (void *)conn); // undo what's been done so far + + pthread_cleanup_push(buffer_get_frame_cleanup_handler, + (void *)conn); // undo what's been done so far do { // get the time local_time_now = get_absolute_time_in_fp(); // type okay @@ -830,7 +830,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (conn->flush_requested == 1) { if (config.output->flush) config.output->flush(); // no cancellation points - ab_resync(conn); // no cancellation points + ab_resync(conn); // no cancellation points conn->first_packet_timestamp = 0; conn->first_packet_time_to_play = 0; conn->time_since_play_started = 0; @@ -1215,7 +1215,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { time_of_wakeup.tv_sec = sec; time_of_wakeup.tv_nsec = nsec; // pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex, &time_of_wakeup); - int rc = pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex, &time_of_wakeup); // this is a pthread cancellation point + int rc = pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex, + &time_of_wakeup); // this is a pthread cancellation point if (rc != 0) debug(3, "pthread_cond_timedwait returned error code %d.", rc); #endif @@ -1231,17 +1232,17 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { } while (wait); // seq_t read = conn->ab_read; - if (!curframe->ready) { - // debug(1, "Supplying a silent frame for frame %u", read); - conn->missing_packets++; - curframe->timestamp = 0; // indicate a silent frame should be substituted - } - curframe->ready = 0; - curframe->resend_level = 0; - conn->ab_read = SUCCESSOR(conn->ab_read); - - pthread_cleanup_pop(1); - return curframe; + if (!curframe->ready) { + // debug(1, "Supplying a silent frame for frame %u", read); + conn->missing_packets++; + curframe->timestamp = 0; // indicate a silent frame should be substituted + } + curframe->ready = 0; + curframe->resend_level = 0; + conn->ab_read = SUCCESSOR(conn->ab_read); + + pthread_cleanup_pop(1); + return curframe; } static inline short shortmean(short a, short b) { @@ -1435,7 +1436,8 @@ void player_thread_cleanup_handler(void *arg) { #ifdef HAVE_DACP_CLIENT - relinquish_dacp_server_information(conn); // say it doesn't belong to this conversation thread any more... + relinquish_dacp_server_information( + conn); // say it doesn't belong to this conversation thread any more... #else // stop watching for DACP port number stuff @@ -1468,11 +1470,11 @@ void player_thread_cleanup_handler(void *arg) { if (conn->outbuf) { free(conn->outbuf); conn->outbuf = NULL; - } + } if (conn->sbuf) { free(conn->sbuf); conn->sbuf = NULL; - } + } if (conn->tbuf) { free(conn->tbuf); conn->tbuf = NULL; @@ -1647,15 +1649,17 @@ void *player_thread_func(void *arg) { // if ((input_rate!=config.output_rate) || (input_bit_depth!=output_bit_depth)) { // debug(1,"Define tbuf of length // %d.",output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); - conn->tbuf = malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + - conn->max_frame_size_change)); + conn->tbuf = + malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + + conn->max_frame_size_change)); if (conn->tbuf == NULL) die("Failed to allocate memory for the transition buffer."); // initialise this, because soxr stuffing might be chosen later - conn->sbuf = malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + - conn->max_frame_size_change)); + conn->sbuf = + malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + + conn->max_frame_size_change)); if (conn->sbuf == NULL) die("Failed to allocate memory for the sbuf buffer."); @@ -1685,7 +1689,7 @@ void *player_thread_func(void *arg) { if (conn->dapo_private_storage) debug(1, "DACP monitor already initialised?"); else - // this does not have pthread cancellation points in it (assuming avahi doesn't) + // this does not have pthread cancellation points in it (assuming avahi doesn't) conn->dapo_private_storage = mdns_dacp_monitor(conn->dacp_id); // ?? #endif @@ -1734,12 +1738,12 @@ void *player_thread_func(void *arg) { debug(3, "Set initial volume to %f.", config.airplay_volume); player_volume(config.airplay_volume, conn); // ?? int64_t frames_to_drop = 0; - - // create and start the timing, control and audio receiver threads + + // create and start the timing, control and audio receiver threads pthread_create(&conn->rtp_audio_thread, NULL, &rtp_audio_receiver, (void *)conn); pthread_create(&conn->rtp_control_thread, NULL, &rtp_control_receiver, (void *)conn); pthread_create(&conn->rtp_timing_thread, NULL, &rtp_timing_receiver, (void *)conn); - + pthread_cleanup_push(player_thread_cleanup_handler, arg); // undo what's been done so far // debug(1, "Play begin"); @@ -2144,9 +2148,9 @@ void *player_thread_func(void *arg) { case ST_soxr: #ifdef HAVE_LIBSOXR // if (amount_to_stuff) debug(1,"Soxr stuff..."); - play_samples = stuff_buffer_soxr_32((int32_t *)conn->tbuf, (int32_t *)conn->sbuf, inbuflength, - config.output_format, conn->outbuf, amount_to_stuff, - enable_dither, conn); + play_samples = stuff_buffer_soxr_32((int32_t *)conn->tbuf, (int32_t *)conn->sbuf, + inbuflength, config.output_format, conn->outbuf, + amount_to_stuff, enable_dither, conn); #endif break; } @@ -2201,8 +2205,9 @@ void *player_thread_func(void *arg) { } else { // if there is no delay procedure, or it's not working or not allowed, there can be no // synchronising - play_samples = stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format, - conn->outbuf, 0, enable_dither, conn); + play_samples = + stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format, + conn->outbuf, 0, enable_dither, conn); if (conn->outbuf == NULL) debug(1, "NULL outbuf to play -- skipping it."); else @@ -2275,26 +2280,26 @@ void *player_thread_func(void *arg) { if (at_least_one_frame_seen) { if ((config.output->delay)) { if (config.no_sync == 0) { - inform( - "|%*.1f," /* Sync error in milliseconds */ - "%*.1f," /* net correction in ppm */ - "%*.1f," /* corrections in ppm */ - "%*d," /* total packets */ - "%*llu," /* missing packets */ - "%*llu," /* late packets */ - "%*llu," /* too late packets */ - "%*llu," /* resend requests */ - "%*lli," /* min DAC queue size */ - "%*d," /* min buffer occupancy */ - "%*d", /* max buffer occupancy */ - 10, - 1000 * moving_average_sync_error / config.output_rate, - 10, moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), - 10, moving_average_insertions_plus_deletions * 1000000 / - (352 * conn->output_sample_ratio), - 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, - conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, - 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy); + inform("|%*.1f," /* Sync error in milliseconds */ + "%*.1f," /* net correction in ppm */ + "%*.1f," /* corrections in ppm */ + "%*d," /* total packets */ + "%*llu," /* missing packets */ + "%*llu," /* late packets */ + "%*llu," /* too late packets */ + "%*llu," /* resend requests */ + "%*lli," /* min DAC queue size */ + "%*d," /* min buffer occupancy */ + "%*d", /* max buffer occupancy */ + 10, + 1000 * moving_average_sync_error / config.output_rate, 10, + moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), + 10, moving_average_insertions_plus_deletions * 1000000 / + (352 * conn->output_sample_ratio), + 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, + conn->too_late_packets, 7, conn->resend_requests, 7, + minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, + maximum_buffer_occupancy); } else { inform("|%*.1f," /* Sync error in milliseconds */ "%*d," /* total packets */ @@ -2306,11 +2311,10 @@ void *player_thread_func(void *arg) { "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ 10, - 1000 * moving_average_sync_error / config.output_rate, - 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, - conn->too_late_packets, 7, conn->resend_requests, 7, - minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, - maximum_buffer_occupancy); + 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, + conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, + conn->resend_requests, 7, minimum_dac_queue_size, 5, + minimum_buffer_occupancy, 5, maximum_buffer_occupancy); } } else { inform("|%*.1f," /* Sync error in milliseconds */ @@ -2322,10 +2326,10 @@ void *player_thread_func(void *arg) { "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ 10, - 1000 * moving_average_sync_error / config.output_rate, - 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); + 1000 * moving_average_sync_error / config.output_rate, 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); } } else { inform("No frames received in the last sampling interval."); @@ -2339,73 +2343,74 @@ void *player_thread_func(void *arg) { } } } - - debug(1,"This should never be called."); -/* all done in the cleanup... + debug(1, "This should never be called."); - debug(3, "Connection %d: player thread main loop exit.", conn->connection_number); + /* all done in the cleanup... - if (config.statistics_requested) { - int rawSeconds = (int)difftime(time(NULL), conn->playstart); - int elapsedHours = rawSeconds / 3600; - int elapsedMin = (rawSeconds / 60) % 60; - int elapsedSec = rawSeconds % 60; - inform("Playback Stopped. Total playing time %02d:%02d:%02d.", elapsedHours, elapsedMin, - elapsedSec); - } - -#ifdef HAVE_DACP_CLIENT + debug(3, "Connection %d: player thread main loop exit.", conn->connection_number); - relinquish_dacp_server_information(conn); // say it doesn't belong to this conversation thread any more... + if (config.statistics_requested) { + int rawSeconds = (int)difftime(time(NULL), conn->playstart); + int elapsedHours = rawSeconds / 3600; + int elapsedMin = (rawSeconds / 60) % 60; + int elapsedSec = rawSeconds % 60; + inform("Playback Stopped. Total playing time %02d:%02d:%02d.", elapsedHours, elapsedMin, + elapsedSec); + } -#else - // stop watching for DACP port number stuff - // this is only used for compatability, if dacp stuff isn't enabled. - if (conn->dapo_private_storage) { - mdns_dacp_dont_monitor(conn->dapo_private_storage); - conn->dapo_private_storage = NULL; - } else { - debug(2, "DACP Monitor already stopped"); - } -#endif + #ifdef HAVE_DACP_CLIENT - debug(2, "Cancelling timing, control and audio threads..."); - debug(2, "Cancel timing thread."); - pthread_cancel(rtp_timing_thread); - debug(2, "Join timing thread."); - pthread_join(rtp_timing_thread, NULL); - debug(2, "Timing thread terminated."); - debug(2, "Cancel control thread."); - pthread_cancel(rtp_control_thread); - debug(2, "Join control thread."); - pthread_join(rtp_control_thread, NULL); - debug(2, "Control thread terminated."); - debug(2, "Cancel audio thread."); - pthread_cancel(rtp_audio_thread); - debug(2, "Join audio thread."); - pthread_join(rtp_audio_thread, NULL); - debug(2, "Audio thread terminated."); - clear_reference_timestamp(conn); - conn->rtp_running = 0; + relinquish_dacp_server_information(conn); // say it doesn't belong to this conversation thread + any more... - debug(3, "Connection %d: stopping output device.", conn->connection_number); + #else + // stop watching for DACP port number stuff + // this is only used for compatability, if dacp stuff isn't enabled. + if (conn->dapo_private_storage) { + mdns_dacp_dont_monitor(conn->dapo_private_storage); + conn->dapo_private_storage = NULL; + } else { + debug(2, "DACP Monitor already stopped"); + } + #endif - if (config.output->stop) - config.output->stop(); + debug(2, "Cancelling timing, control and audio threads..."); + debug(2, "Cancel timing thread."); + pthread_cancel(rtp_timing_thread); + debug(2, "Join timing thread."); + pthread_join(rtp_timing_thread, NULL); + debug(2, "Timing thread terminated."); + debug(2, "Cancel control thread."); + pthread_cancel(rtp_control_thread); + debug(2, "Join control thread."); + pthread_join(rtp_control_thread, NULL); + debug(2, "Control thread terminated."); + debug(2, "Cancel audio thread."); + pthread_cancel(rtp_audio_thread); + debug(2, "Join audio thread."); + pthread_join(rtp_audio_thread, NULL); + debug(2, "Audio thread terminated."); + clear_reference_timestamp(conn); + conn->rtp_running = 0; - debug(2, "Freeing audio buffers and decoders."); + debug(3, "Connection %d: stopping output device.", conn->connection_number); - free_audio_buffers(conn); - terminate_decoders(conn); - debug(2, "Connection %d: player thread terminated.", conn->connection_number); - if (outbuf) - free(outbuf); - if (tbuf) - free(tbuf); - if (sbuf) - free(sbuf); - */ + if (config.output->stop) + config.output->stop(); + + debug(2, "Freeing audio buffers and decoders."); + + free_audio_buffers(conn); + terminate_decoders(conn); + debug(2, "Connection %d: player thread terminated.", conn->connection_number); + if (outbuf) + free(outbuf); + if (tbuf) + free(tbuf); + if (sbuf) + free(sbuf); + */ pthread_cleanup_pop(1); pthread_exit(NULL); } @@ -2413,7 +2418,8 @@ void *player_thread_func(void *arg) { // takes the volume as specified by the airplay protocol void player_volume_without_notification(double airplay_volume, rtsp_conn_info *conn) { - // no cancellation points here if we assume that the mute call to the back end has no cancellation points + // no cancellation points here if we assume that the mute call to the back end has no cancellation + // points // The volume ranges -144.0 (mute) or -30 -- 0. See // http://git.zx2c4.com/Airtunes2/about/#setting-volume @@ -2578,9 +2584,9 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c if (config.ignore_volume_control == 1) scaled_attenuation = max_db; else if (config.volume_control_profile == VCP_standard) - scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); // no cancellation points + scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); // no cancellation points else if (config.volume_control_profile == VCP_flat) - scaled_attenuation = flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points + scaled_attenuation = flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points else debug(1, "Unrecognised volume control profile"); @@ -2615,7 +2621,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c // %f",software_attenuation,temp_fix_volume,airplay_volume); conn->fix_volume = temp_fix_volume; - memory_barrier(); // no cancellation points + memory_barrier(); // no cancellation points if (config.loudness) loudness_set_volume(software_attenuation / 100); diff --git a/player.h b/player.h index 295b0ccd..99dcecb8 100644 --- a/player.h +++ b/player.h @@ -74,9 +74,9 @@ typedef struct { // otherwise int64_t maximum_latency; // set if an a=max-latency: line appears in the ANNOUNCE message; zero // otherwise - + int fd; - int authorized; // set if a password is required and has been supplied + int authorized; // set if a password is required and has been supplied char *auth_nonce; // the session nonce, if needed stream_cfg stream; SOCKADDR remote, local; diff --git a/rtsp.c b/rtsp.c index 5b5a3428..2c056016 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1889,7 +1889,7 @@ authenticate: } void rtsp_conversation_thread_cleanup_function(void *arg) { - debug(1,"rtsp_conversation_thread_func_cleanup_function called."); + debug(1, "rtsp_conversation_thread_func_cleanup_function called."); rtsp_conn_info *conn = (rtsp_conn_info *)arg; if (conn->fd > 0) close(conn->fd); @@ -1923,10 +1923,10 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { rc = pthread_mutex_destroy(&conn->flush_mutex); if (rc) debug(1, "Connection %d: error %d destroying flush_mutex.", conn->connection_number, rc); - } +} void msg_cleanup_function(void *arg) { - debug(1,"msg_cleanup_function called."); + debug(1, "msg_cleanup_function called."); msg_free((rtsp_message *)arg); } @@ -1961,14 +1961,14 @@ static void *rtsp_conversation_thread_func(void *pconn) { int rtsp_read_request_attempt_count = 1; // 1 means exit immediately rtsp_message *req, *resp; - pthread_cleanup_push(rtsp_conversation_thread_cleanup_function,(void *)conn); + pthread_cleanup_push(rtsp_conversation_thread_cleanup_function, (void *)conn); while (conn->stop == 0) { int debug_level = 3; // for printing the request and response - reply = rtsp_read_request(conn,&req); + reply = rtsp_read_request(conn, &req); if (reply == rtsp_read_request_response_ok) { - pthread_cleanup_push(msg_cleanup_function,(void*)req); + pthread_cleanup_push(msg_cleanup_function, (void *)req); resp = msg_init(); - pthread_cleanup_push(msg_cleanup_function,(void*)resp); + pthread_cleanup_push(msg_cleanup_function, (void *)resp); resp->respcode = 400; if (strcmp(req->method, "OPTIONS") != @@ -2176,6 +2176,7 @@ void rtsp_listen_loop(void) { int acceptfd; struct timeval tv; do { + pthread_testcancel(); tv.tv_sec = 60; tv.tv_usec = 0; diff --git a/shairport.c b/shairport.c index 92521107..a2a049d9 100644 --- a/shairport.c +++ b/shairport.c @@ -60,6 +60,11 @@ #include #endif +#include "common.h" +#include "mdns.h" +#include "rtp.h" +#include "rtsp.h" + #if defined(HAVE_DACP_CLIENT) #include "dacp.h" #endif @@ -80,11 +85,6 @@ #include "mpris-service.h" #endif -#include "common.h" -#include "mdns.h" -#include "rtp.h" -#include "rtsp.h" - #include #include #include @@ -1131,7 +1131,7 @@ const char *pid_file_proc(void) { } void exit_function() { - // debug(1, "exit function called..."); + debug(1, "exit function called..."); if (config.cfg) config_destroy(config.cfg); if (config.appName) @@ -1139,7 +1139,19 @@ void exit_function() { // probably should be freeing malloc'ed memory here, including strdup-created strings... } +void main_cleanup_handler(__attribute__((unused)) void *arg) { + + debug(1, "main cleanup handler called."); + shairport_shutdown(); + daemon_log(LOG_NOTICE, "Unexpected exit..."); + daemon_retval_send(0); + daemon_pid_file_remove(); + daemon_signal_done(); + exit(0); +} + int main(int argc, char **argv) { + memset((void *)&main_thread_id, 0, sizeof(main_thread_id)); fp_time_at_startup = get_absolute_time_in_fp(); fp_time_at_last_debug_message = fp_time_at_startup; // debug(1,"startup"); @@ -1362,10 +1374,11 @@ int main(int argc, char **argv) { /* Close FDs */ if (daemon_close_all(-1) < 0) { daemon_log(LOG_ERR, "Failed to close all file descriptors: %s", strerror(errno)); - /* Send the error condition to the parent process */ daemon_retval_send(1); - goto finish; + + daemon_signal_done(); + return 0; } /* Create the PID file if required */ @@ -1376,12 +1389,16 @@ int main(int argc, char **argv) { if ((result != 0) && (result != -EEXIST)) { // error creating or accessing the PID file directory daemon_retval_send(3); - goto finish; + + daemon_signal_done(); + return 0; } if (daemon_pid_file_create() < 0) { daemon_log(LOG_ERR, "Could not create PID file (%s).", strerror(errno)); + daemon_retval_send(2); - goto finish; + daemon_signal_done(); + return 0; } } @@ -1391,11 +1408,22 @@ int main(int argc, char **argv) { /* end libdaemon stuff */ } + // int old_cancel_state = 0; + // pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); + // pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old_cancel_state); + main_thread_id = pthread_self(); + if (main_thread_id) + debug(1, "Main thread ID set up."); + else + debug(1, "Main thread is set up to be NULL!"); + signal_setup(); // make sure the program can create files that group and world can read umask(S_IWGRP | S_IWOTH); + pthread_cleanup_push(main_cleanup_handler, NULL); + config.output = audio_get_output(config.output_name); if (!config.output) { audio_ls_outputs(); @@ -1587,10 +1615,11 @@ int main(int argc, char **argv) { rtsp_listen_loop(); // should not reach this... - shairport_shutdown(); -finish: - daemon_log(LOG_NOTICE, "Unexpected exit..."); - daemon_retval_send(255); - daemon_pid_file_remove(); - return 1; + // shairport_shutdown(); + // daemon_log(LOG_NOTICE, "Unexpected exit..."); + // daemon_retval_send(0); + // daemon_pid_file_remove(); + pthread_cleanup_pop(1); + debug(1, "Odd exit point"); + pthread_exit(NULL); } From f1d450343135434d19296f3b728cb68f2f19698a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 20 Jul 2018 13:50:38 +0100 Subject: [PATCH 020/569] Try to add proper cancellation code to all threads -- not complete, buggy. --- audio_alsa.c | 10 ++-- audio_soundio.c | 5 +- common.c | 29 +++++------ common.h | 1 + dacp.c | 39 +++++++++++---- dacp.h | 1 + dbus-service.c | 16 ++++++- dbus-service.h | 1 + mdns_avahi.c | 12 +++-- mdns_dns_sd.c | 2 +- mdns_external.c | 2 +- metadata_hub.c | 50 +++++++++++++------ metadata_hub.h | 1 + player.c | 102 ++++++++++++++++++++------------------- rtp.c | 17 ++++--- rtsp.c | 124 +++++++++++++++++++++++++++++++++++------------- rtsp.h | 6 ++- shairport.c | 81 ++++++++++++++++++++----------- tinysvcmdns.c | 3 +- 19 files changed, 330 insertions(+), 172 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index e2a53ccd..1a61858e 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -407,8 +407,9 @@ static int init(int argc, char **argv) { (1.0 * alsa_mix_maxdb) / 100.0); } else { // use the linear scale and do the db conversion ourselves - debug(1, "note: the hardware mixer specified -- \"%s\" -- does not have " - "a dB volume scale.", + debug(1, + "note: the hardware mixer specified -- \"%s\" -- does not have " + "a dB volume scale.", alsa_mix_ctrl); if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) { @@ -661,8 +662,9 @@ int open_alsa_device(void) { buffer_size); } */ - debug(1, "The alsa buffer is smaller (%lu bytes) than the desired backend buffer " - "length (%ld) you have chosen.", + debug(1, + "The alsa buffer is smaller (%lu bytes) than the desired backend buffer " + "length (%ld) you have chosen.", actual_buffer_length, config.audio_backend_buffer_desired_length); } diff --git a/audio_soundio.c b/audio_soundio.c index c46e00d5..c744c3c1 100644 --- a/audio_soundio.c +++ b/audio_soundio.c @@ -27,8 +27,9 @@ static void write_callback(struct SoundIoOutStream *outstream, int frame_count_m int fill_bytes = soundio_ring_buffer_fill_count(ring_buffer); int fill_count = fill_bytes / outstream->bytes_per_frame; - debug(3, "[--->>] frame_count_min: %d , frame_count_max: %d , fill_bytes: %d , fill_count: %d , " - "outstream->bytes_per_frame: %d", + debug(3, + "[--->>] frame_count_min: %d , frame_count_max: %d , fill_bytes: %d , fill_count: %d , " + "outstream->bytes_per_frame: %d", frame_count_min, frame_count_max, fill_bytes, fill_count, outstream->bytes_per_frame); if (frame_count_min > fill_count) { diff --git a/common.c b/common.c index 38d677c6..20f0b5b9 100644 --- a/common.c +++ b/common.c @@ -120,7 +120,6 @@ void die(const char *format, ...) { daemon_log(LOG_EMERG, "% 20.9f|*fatal error: %s", tss, s); else daemon_log(LOG_EMERG, "fatal error: %s", s); - shairport_shutdown(); exit(1); } @@ -748,21 +747,17 @@ double flat_vol2attn(double vol, long max_db, long min_db) { double vol2attn(double vol, long max_db, long min_db) { -// We use a little coordinate geometry to build a transfer function from the volume passed in to the -// device's dynamic range. -// (See the diagram in the documents folder.) -// The x axis is the "volume in" which will be from -30 to 0. The y axis will be the "volume out" -// which will be from the bottom of the range to the top. -// We build the transfer function from one or more lines. We characterise each line with two -// numbers: -// the first is where on x the line starts when y=0 (x can be from 0 to -30); the second is where on -// y the line stops when when x is -30. -// thus, if the line was characterised as {0,-30}, it would be an identity transfer. -// Assuming, for example, a dynamic range of lv=-60 to hv=0 -// Typically we'll use three lines -- a three order transfer function -// First: {0,30} giving a gentle slope -- the 30 comes from half the dynamic range -// Second: {-5,-30-(lv+30)/2} giving a faster slope from y=0 at x=-12 to y=-42.5 at x=-30 -// Third: {-17,lv} giving a fast slope from y=0 at x=-19 to y=-60 at x=-30 + // We use a little coordinate geometry to build a transfer function from the volume passed in to + // the device's dynamic range. (See the diagram in the documents folder.) The x axis is the + // "volume in" which will be from -30 to 0. The y axis will be the "volume out" which will be from + // the bottom of the range to the top. We build the transfer function from one or more lines. We + // characterise each line with two numbers: the first is where on x the line starts when y=0 (x + // can be from 0 to -30); the second is where on y the line stops when when x is -30. thus, if the + // line was characterised as {0,-30}, it would be an identity transfer. Assuming, for example, a + // dynamic range of lv=-60 to hv=0 Typically we'll use three lines -- a three order transfer + // function First: {0,30} giving a gentle slope -- the 30 comes from half the dynamic range + // Second: {-5,-30-(lv+30)/2} giving a faster slope from y=0 at x=-12 to y=-42.5 at x=-30 + // Third: {-17,lv} giving a fast slope from y=0 at x=-19 to y=-60 at x=-30 #define order 3 @@ -966,8 +961,6 @@ int64_t r64i() { return (ranval(&rx) >> 1); } /* generate an array of 64-bit random numbers */ const int ranarraylength = 1009; // these will be 8-byte numbers. -uint64_t *ranarray; - int ranarraynext; void ranarrayinit() { diff --git a/common.h b/common.h index 7d556b96..f0a2031d 100644 --- a/common.h +++ b/common.h @@ -229,6 +229,7 @@ void r64init(uint64_t seed); uint64_t r64u(); int64_t r64i(); +uint64_t *ranarray; void r64arrayinit(); uint64_t ranarray64u(); int64_t ranarray64i(); diff --git a/dacp.c b/dacp.c index 53b76616..81a6f4d2 100644 --- a/dacp.c +++ b/dacp.c @@ -110,7 +110,10 @@ static void response_code(void *opaque, int code) { } static const struct http_funcs responseFuncs = { - response_realloc, response_body, response_header, response_code, + response_realloc, + response_body, + response_header, + response_code, }; // static pthread_mutex_t dacp_conversation_lock = PTHREAD_MUTEX_INITIALIZER; @@ -261,8 +264,9 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // debug(1,"Sent command\"%s\" with a response body of size %d.",command,response.size); // debug(1,"dacp_conversation_lock released."); } else { - debug(3, "Could not acquire a lock on the dacp transmit/receive section when attempting to " - "send the command \"%s\". Possible timeout?", + debug(3, + "Could not acquire a lock on the dacp transmit/receive section when attempting to " + "send the command \"%s\". Possible timeout?", command); response.code = 494; // This client is already busy } @@ -384,6 +388,12 @@ void dacp_monitor_port_update_callback(char *dacp_id, uint16_t port) { pthread_cond_signal(&dacp_server_information_cv); pthread_mutex_unlock(&dacp_server_information_lock); } + +void dacp_monitor_thread_code_cleanup(__attribute__((unused)) void *arg) { + debug(1, "dacp_monitor_thread_code_cleanup called."); + pthread_mutex_unlock(&dacp_server_information_lock); +} + void *dacp_monitor_thread_code(__attribute__((unused)) void *na) { int scan_index = 0; // char server_reply[10000]; @@ -397,14 +407,18 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) { sps_pthread_mutex_timedlock( &dacp_server_information_lock, 500000, "dacp_monitor_thread_code couldn't get DACP server information lock in 0.5 second!.", 2); + int32_t the_volume; + + pthread_cleanup_push(dacp_monitor_thread_code_cleanup, NULL); if (dacp_server.scan_enable == 0) { metadata_hub_modify_prolog(); int ch = (metadata_store.dacp_server_active != 0) || (metadata_store.advanced_dacp_server_active != 0); metadata_store.dacp_server_active = 0; metadata_store.advanced_dacp_server_active = 0; - debug(2, "setting dacp_server_active and advanced_dacp_server_active to 0 with an update " - "flag value of %d", + debug(2, + "setting dacp_server_active and advanced_dacp_server_active to 0 with an update " + "flag value of %d", ch); metadata_hub_modify_epilog(ch); while (dacp_server.scan_enable == 0) { @@ -416,7 +430,6 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) { idle_scan_count = 0; } scan_index++; - int32_t the_volume; result = dacp_get_volume(&the_volume); // just want the http code if ((result == 496) || (result == 403) || (result == 501)) { @@ -438,7 +451,9 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) { debug(1, "DACP server status scanning stopped."); dacp_server.scan_enable = 0; } - pthread_mutex_unlock(&dacp_server_information_lock); + pthread_cleanup_pop(1); + + // pthread_mutex_unlock(&dacp_server_information_lock); // debug(1, "DACP Server ID \"%u\" at \"%s:%u\", scan %d.", dacp_server.active_remote_id, // dacp_server.ip_string, dacp_server.port, scan_index); @@ -764,7 +779,7 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) { sleep(config.scan_interval_when_inactive); } } - debug(1, "DACP monitor thread exiting."); + debug(1, "DACP monitor thread exiting -- should never happen."); pthread_exit(NULL); } @@ -816,6 +831,14 @@ void dacp_monitor_start() { pthread_create(&dacp_monitor_thread, NULL, dacp_monitor_thread_code, NULL); } +void dacp_monitor_stop() { + debug(1, "dacp_monitor_stop"); + pthread_cancel(dacp_monitor_thread); + pthread_join(dacp_monitor_thread, NULL); + pthread_mutex_destroy(&dacp_server_information_lock); + pthread_mutex_destroy(&dacp_conversation_lock); +} + uint32_t dacp_tlv_crawl(char **p, int32_t *length) { char typecode[5]; memcpy(typecode, *p, 4); diff --git a/dacp.h b/dacp.h index bde3ac51..5379d069 100644 --- a/dacp.h +++ b/dacp.h @@ -14,6 +14,7 @@ typedef struct dacp_speaker_stuff { } dacp_spkr_stuff; void dacp_monitor_start(); +void dacp_monitor_stop(); uint32_t dacp_tlv_crawl( char **p, diff --git a/dbus-service.c b/dbus-service.c index e8d50c2f..5fc78c25 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -19,6 +19,8 @@ ShairportSyncDiagnostics *shairportSyncDiagnosticsSkeleton = NULL; ShairportSyncRemoteControl *shairportSyncRemoteControlSkeleton = NULL; ShairportSyncAdvancedRemoteControl *shairportSyncAdvancedRemoteControlSkeleton = NULL; +guint ownerID = 0; + void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)) void *userdata) { char response[100]; const char *th; @@ -744,6 +746,7 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name static void on_dbus_name_lost_again(__attribute__((unused)) GDBusConnection *connection, __attribute__((unused)) const gchar *name, __attribute__((unused)) gpointer user_data) { + debug(1, "Name lost again."); warn("Could not acquire a Shairport Sync native D-Bus interface \"%s\" on the %s bus.", name, (config.dbus_service_bus_type == DBT_session) ? "session" : "system"); } @@ -751,6 +754,7 @@ static void on_dbus_name_lost_again(__attribute__((unused)) GDBusConnection *con static void on_dbus_name_lost(__attribute__((unused)) GDBusConnection *connection, __attribute__((unused)) const gchar *name, __attribute__((unused)) gpointer user_data) { + debug(1, "Name lost."); // debug(1, "Could not acquire a Shairport Sync native D-Bus interface \"%s\" on the %s bus -- // will try adding the process " // "number to the end of it.", @@ -774,7 +778,15 @@ int start_dbus_service() { dbus_bus_type = G_BUS_TYPE_SESSION; // debug(1, "Looking for a Shairport Sync native D-Bus interface \"org.gnome.ShairportSync\" on // the %s bus.",(config.dbus_service_bus_type == DBT_session) ? "session" : "system"); - g_bus_own_name(dbus_bus_type, "org.gnome.ShairportSync", G_BUS_NAME_OWNER_FLAGS_NONE, NULL, - on_dbus_name_acquired, on_dbus_name_lost, NULL, NULL); + ownerID = g_bus_own_name(dbus_bus_type, "org.gnome.ShairportSync", G_BUS_NAME_OWNER_FLAGS_NONE, + NULL, on_dbus_name_acquired, on_dbus_name_lost, NULL, NULL); return 0; // this is just to quieten a compiler warning } + +void stop_dbus_service() { + debug(1, "stopping dbus service"); + if (ownerID) + g_bus_unown_name(ownerID); + else + debug(1, "Zero OwnerID for \"org.gnome.ShairportSync\"."); +} diff --git a/dbus-service.h b/dbus-service.h index ea6e9765..a00d06e0 100644 --- a/dbus-service.h +++ b/dbus-service.h @@ -6,5 +6,6 @@ ShairportSync *shairportSyncSkeleton; int start_dbus_service(); +void stop_dbus_service(); #endif /* #ifndef DBUS_SERVICE_H */ diff --git a/mdns_avahi.c b/mdns_avahi.c index bdca81e3..c9b8ae00 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -385,21 +385,27 @@ static int avahi_register(char *srvname, int srvport) { static void avahi_unregister(void) { debug(1, "avahi: avahi_unregister."); if (tpoll) { - avahi_threaded_poll_stop(tpoll); + // debug(1, "avahi: stop the threaded poll."); + // avahi_threaded_poll_stop(tpoll); if (client) { + debug(1, "avahi: free the client."); avahi_client_free(client); client = NULL; } else { debug(1, "avahi attempting to unregister a NULL client"); } + debug(1, "avahi: free the threaded poll."); avahi_threaded_poll_free(tpoll); tpoll = NULL; + } else { + debug(1, "No avahi threaded poll."); } - if (service_name) + if (service_name) { + debug(1, "avahi: free the service name."); free(service_name); - else + } else debug(1, "avahi attempt to free NULL service name"); service_name = NULL; } diff --git a/mdns_dns_sd.c b/mdns_dns_sd.c index 722aadcf..f8b755ec 100644 --- a/mdns_dns_sd.c +++ b/mdns_dns_sd.c @@ -24,8 +24,8 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include "mdns.h" #include "common.h" +#include "mdns.h" #include #include #include diff --git a/mdns_external.c b/mdns_external.c index e3a241fd..a71ce101 100644 --- a/mdns_external.c +++ b/mdns_external.c @@ -24,8 +24,8 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include "mdns.h" #include "common.h" +#include "mdns.h" #include #include #include diff --git a/metadata_hub.c b/metadata_hub.c index 9d648fcd..3892e30a 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -90,12 +90,30 @@ void metadata_hub_release_track_metadata(struct track_metadata_bundle *track_met } } +void metadata_hub_release_track_artwork(void) { + // debug(1,"release track artwork"); + release_char_string(&metadata_store.cover_art_pathname); +} + void metadata_hub_init(void) { // debug(1, "Metadata bundle initialisation."); memset(&metadata_store, 0, sizeof(metadata_store)); track_metadata = NULL; } +void metadata_hub_stop(void) { + debug(1, "metadata_hub_stop."); + metadata_hub_release_track_artwork(); + if (metadata_store.track_metadata) { + metadata_hub_release_track_metadata(metadata_store.track_metadata); + metadata_store.track_metadata = NULL; + } + if (track_metadata) { + metadata_hub_release_track_metadata(track_metadata); + track_metadata = NULL; + } +} + void add_metadata_watcher(metadata_watcher fn, void *userdata) { int i; for (i = 0; i < number_of_watchers; i++) { @@ -108,32 +126,34 @@ void add_metadata_watcher(metadata_watcher fn, void *userdata) { } } -void metadata_hub_modify_prolog(void) { - // always run this before changing an entry or a sequence of entries in the metadata_hub - // debug(1, "locking metadata hub for writing"); - if (pthread_rwlock_trywrlock(&metadata_hub_re_lock) != 0) { - debug(2, "Metadata_hub write lock is already taken -- must wait."); - pthread_rwlock_wrlock(&metadata_hub_re_lock); - debug(2, "Okay -- acquired the metadata_hub write lock."); - } -} - -void metadata_hub_release_track_artwork(void) { - // debug(1,"release track artwork"); - release_char_string(&metadata_store.cover_art_pathname); +void metadata_hub_unlock_hub_mutex_cleanup(__attribute__((unused)) void *arg) { + debug(1, "metadata_hub_unlock_hub_mutex_cleanup called."); + pthread_rwlock_wrlock(&metadata_hub_re_lock); } void run_metadata_watchers(void) { int i; // debug(1, "locking metadata hub for reading"); pthread_rwlock_rdlock(&metadata_hub_re_lock); + pthread_cleanup_push(metadata_hub_unlock_hub_mutex_cleanup, NULL); for (i = 0; i < number_of_watchers; i++) { if (metadata_store.watchers[i]) { metadata_store.watchers[i](&metadata_store, metadata_store.watchers_data[i]); } } // debug(1, "unlocking metadata hub for reading"); - pthread_rwlock_unlock(&metadata_hub_re_lock); + // pthread_rwlock_unlock(&metadata_hub_re_lock); + pthread_cleanup_pop(1); +} + +void metadata_hub_modify_prolog(void) { + // always run this before changing an entry or a sequence of entries in the metadata_hub + // debug(1, "locking metadata hub for writing"); + if (pthread_rwlock_trywrlock(&metadata_hub_re_lock) != 0) { + debug(2, "Metadata_hub write lock is already taken -- must wait."); + pthread_rwlock_unlock(&metadata_hub_re_lock); + debug(2, "Okay -- acquired the metadata_hub write lock."); + } } void metadata_hub_modify_epilog(int modified) { @@ -197,7 +217,7 @@ char *metadata_write_image_file(const char *buf, int len) { char *path = NULL; // this will be what is returned uint8_t img_md5[16]; -// uint8_t ap_md5[16]; + // uint8_t ap_md5[16]; #ifdef HAVE_LIBSSL MD5_CTX ctx; diff --git a/metadata_hub.h b/metadata_hub.h index b2084c12..65e93a23 100644 --- a/metadata_hub.h +++ b/metadata_hub.h @@ -92,6 +92,7 @@ struct metadata_bundle metadata_store; void add_metadata_watcher(metadata_watcher fn, void *userdata); void metadata_hub_init(void); +void metadata_hub_stop(void); void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t length); void metadata_hub_reset_track_metadata(void); void metadata_hub_release_track_artwork(void); diff --git a/player.c b/player.c index ff0f7392..6a2f40aa 100644 --- a/player.c +++ b/player.c @@ -315,16 +315,18 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_co } if (outsize > toutsize) { - debug(2, "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- " - "truncated, but buffer overflow possible! Encrypted = %d.", + debug(2, + "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- " + "truncated, but buffer overflow possible! Encrypted = %d.", outsize, toutsize, conn->stream.encrypted); reply = -1; // output packet is the wrong size } *destlen = outsize / conn->input_bytes_per_frame; if ((outsize % conn->input_bytes_per_frame) != 0) - debug(1, "Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) " - "and the audio frame size (%d).", + debug(1, + "Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) " + "and the audio frame size (%d).", *destlen, outsize, conn->input_bytes_per_frame); return reply; } @@ -614,8 +616,9 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp if (config.disable_resend_requests == 0) { if (((int)(resend_interval * pow(j + 1, step_exponent)) + k) >= seq_diff(conn->ab_read, conn->ab_write, conn->ab_read)) - debug(3, "Last-ditch (#%d) resend request for packet %u in range %u to %u. " - "Looking back %d packets.", + debug(3, + "Last-ditch (#%d) resend request for packet %u in range %u to %u. " + "Looking back %d packets.", j, next, conn->ab_read, conn->ab_write, back_step + k); debug_mutex_unlock(&conn->ab_mutex, 3); rtp_request_resend(next, 1, conn); @@ -807,8 +810,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // if (conn->packet_count>500) { //for testing -- about 4 seconds of play first if ((local_time_now > conn->time_of_last_audio_packet) && (local_time_now - conn->time_of_last_audio_packet >= ct << 32)) { - debug(1, "As Yeats almost said, \"Too long a silence / can make a stone of the heart\" " - "from RTSP conversation %d.", + debug(1, + "As Yeats almost said, \"Too long a silence / can make a stone of the heart\" " + "from RTSP conversation %d.", conn->connection_number); conn->stop = 1; pthread_kill(conn->thread, SIGUSR1); @@ -1649,17 +1653,17 @@ void *player_thread_func(void *arg) { // if ((input_rate!=config.output_rate) || (input_bit_depth!=output_bit_depth)) { // debug(1,"Define tbuf of length // %d.",output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); - conn->tbuf = - malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + - conn->max_frame_size_change)); + conn->tbuf = malloc( + sizeof(int32_t) * 2 * + (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change)); if (conn->tbuf == NULL) die("Failed to allocate memory for the transition buffer."); // initialise this, because soxr stuffing might be chosen later - conn->sbuf = - malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + - conn->max_frame_size_change)); + conn->sbuf = malloc( + sizeof(int32_t) * 2 * + (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change)); if (conn->sbuf == NULL) die("Failed to allocate memory for the sbuf buffer."); @@ -1770,8 +1774,9 @@ void *player_thread_func(void *arg) { } else { // the player may change the contents of the buffer, so it has to be zeroed each time; // might as well malloc and freee it locally - memset(silence, 0, conn->output_bytes_per_frame * conn->max_frames_per_packet * - conn->output_sample_ratio); + memset(silence, 0, + conn->output_bytes_per_frame * conn->max_frames_per_packet * + conn->output_sample_ratio); config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio); free(silence); } @@ -1791,8 +1796,9 @@ void *player_thread_func(void *arg) { } else { // the player may change the contents of the buffer, so it has to be zeroed each time; // might as well malloc and freee it locally - memset(silence, 0, conn->output_bytes_per_frame * conn->max_frames_per_packet * - conn->output_sample_ratio); + memset(silence, 0, + conn->output_bytes_per_frame * conn->max_frames_per_packet * + conn->output_sample_ratio); config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio); free(silence); } @@ -1939,8 +1945,9 @@ void *player_thread_func(void *arg) { SUCCESSOR(conn->last_seqno_read); // int32_t from seq_t, i.e. uint16_t, so okay. if (inframe->sequence_number != conn->last_seqno_read) { // seq_t, ei.e. uint16_t and int32_t, so okay - debug(2, "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u " - "and ab_write: %u.", + debug(2, + "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u " + "and ab_write: %u.", conn->last_seqno_read, inframe->sequence_number, conn->ab_read, conn->ab_write); conn->last_seqno_read = inframe->sequence_number; // reset warning... } @@ -2091,7 +2098,7 @@ void *player_thread_func(void *arg) { #ifdef CONFIG_CONVOLUTION || config.convolution #endif - ) { + ) { int32_t *tbuf32 = (int32_t *)conn->tbuf; float fbuf_l[inbuflength]; float fbuf_r[inbuflength]; @@ -2280,26 +2287,25 @@ void *player_thread_func(void *arg) { if (at_least_one_frame_seen) { if ((config.output->delay)) { if (config.no_sync == 0) { - inform("|%*.1f," /* Sync error in milliseconds */ - "%*.1f," /* net correction in ppm */ - "%*.1f," /* corrections in ppm */ - "%*d," /* total packets */ - "%*llu," /* missing packets */ - "%*llu," /* late packets */ - "%*llu," /* too late packets */ - "%*llu," /* resend requests */ - "%*lli," /* min DAC queue size */ - "%*d," /* min buffer occupancy */ - "%*d", /* max buffer occupancy */ - 10, - 1000 * moving_average_sync_error / config.output_rate, 10, - moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), - 10, moving_average_insertions_plus_deletions * 1000000 / - (352 * conn->output_sample_ratio), - 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, - conn->too_late_packets, 7, conn->resend_requests, 7, - minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, - maximum_buffer_occupancy); + inform( + "|%*.1f," /* Sync error in milliseconds */ + "%*.1f," /* net correction in ppm */ + "%*.1f," /* corrections in ppm */ + "%*d," /* total packets */ + "%*llu," /* missing packets */ + "%*llu," /* late packets */ + "%*llu," /* too late packets */ + "%*llu," /* resend requests */ + "%*lli," /* min DAC queue size */ + "%*d," /* min buffer occupancy */ + "%*d", /* max buffer occupancy */ + 10, 1000 * moving_average_sync_error / config.output_rate, 10, + moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), 10, + moving_average_insertions_plus_deletions * 1000000 / + (352 * conn->output_sample_ratio), + 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, + conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, + 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy); } else { inform("|%*.1f," /* Sync error in milliseconds */ "%*d," /* total packets */ @@ -2310,10 +2316,9 @@ void *player_thread_func(void *arg) { "%*lli," /* min DAC queue size */ "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ - 10, - 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, - conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, - conn->resend_requests, 7, minimum_dac_queue_size, 5, + 10, 1000 * moving_average_sync_error / config.output_rate, 12, play_number, + 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, + 7, conn->resend_requests, 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy); } } else { @@ -2325,10 +2330,9 @@ void *player_thread_func(void *arg) { "%*llu," /* resend requests */ "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ - 10, - 1000 * moving_average_sync_error / config.output_rate, 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, + 10, 1000 * moving_average_sync_error / config.output_rate, 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); } } else { diff --git a/rtp.c b/rtp.c index c86d3fa0..c4e705c0 100644 --- a/rtp.c +++ b/rtp.c @@ -108,8 +108,9 @@ void *rtp_audio_receiver(void *arg) { stat_mean += stat_delta / stat_n; stat_M2 += stat_delta * (time_interval_us - stat_mean); if (stat_n % 2500 == 0) { - debug(2, "Packet reception interval stats: mean, standard deviation and max for the last " - "2,500 packets in microseconds: %10.1f, %10.1f, %10.1f.", + debug(2, + "Packet reception interval stats: mean, standard deviation and max for the last " + "2,500 packets in microseconds: %10.1f, %10.1f, %10.1f.", stat_mean, sqrtf(stat_M2 / (stat_n - 1)), longest_packet_time_interval_us); stat_n = 0; stat_mean = 0.0; @@ -325,9 +326,10 @@ void *rtp_control_receiver(void *arg) { if (la != conn->latency) { conn->latency = la; - debug(3, "New latency detected: %" PRId64 ", sync latency: %" PRId64 - ", minimum latency: %" PRId64 ", maximum " - "latency: %" PRId64 ", fixed offset: %" PRId64 ".", + debug(3, + "New latency detected: %" PRId64 ", sync latency: %" PRId64 + ", minimum latency: %" PRId64 ", maximum " + "latency: %" PRId64 ", fixed offset: %" PRId64 ".", la, sync_rtp_timestamp - rtp_timestamp_less_latency, conn->minimum_latency, conn->maximum_latency, config.fixedLatencyOffset); } @@ -957,8 +959,9 @@ void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) { (struct sockaddr *)&conn->rtp_client_control_socket, msgsize) == -1) { char em[1024]; strerror_r(errno, em, sizeof(em)); - debug(1, "Error %d using sendto to an audio socket: \"%s\". Backing off for 1/16th of a " - "second.", + debug(1, + "Error %d using sendto to an audio socket: \"%s\". Backing off for 1/16th of a " + "second.", errno, em); conn->rtp_time_of_last_resend_request_error_fp = time_of_sending_fp; } else { diff --git a/rtsp.c b/rtsp.c index 2c056016..95b68522 100644 --- a/rtsp.c +++ b/rtsp.c @@ -101,8 +101,6 @@ static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER; // static int please_shutdown = 0; // static pthread_t playing_thread = 0; -static rtsp_conn_info **conns = NULL; - int RTSP_connection_index = 1; #ifdef CONFIG_METADATA @@ -156,6 +154,12 @@ void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t the_queue->eoq = 0; } +void pc_queue_delete(pc_queue *the_queue) { + pthread_cond_destroy(&the_queue->pc_queue_item_removed_signal); + pthread_cond_destroy(&the_queue->pc_queue_item_added_signal); + pthread_mutex_destroy(&the_queue->pc_queue_lock); +} + int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier, int block); @@ -266,6 +270,21 @@ static void track_thread(rtsp_conn_info *conn) { } } +void cancel_all_RTSP_threads(void) { + int i; + for (i = 0; i < nconns; i++) { + debug(1, "Connection %d: cancelling.", conns[i]->connection_number); + pthread_cancel(conns[i]->thread); + } + for (i = 0; i < nconns; i++) { + debug(1, "Connection %d: joining.", conns[i]->connection_number); + pthread_join(conns[i]->thread, NULL); + if (conns[i] == playing_conn) + playing_conn = NULL; + free(conns[i]); + } +} + static void cleanup_threads(void) { void *retval; int i; @@ -719,9 +738,10 @@ static void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_me 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"); } static void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, @@ -853,10 +873,11 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message * } char resphdr[256] = ""; - snprintf(resphdr, sizeof(resphdr), "RTP/AVP/" - "UDP;unicast;interleaved=0-1;mode=record;control_port=%d;" - "timing_port=%d;server_" - "port=%d", + snprintf(resphdr, sizeof(resphdr), + "RTP/AVP/" + "UDP;unicast;interleaved=0-1;mode=record;control_port=%d;" + "timing_port=%d;server_" + "port=%d", conn->local_control_port, conn->local_timing_port, conn->local_audio_port); msg_add_header(resp, "Transport", resphdr); @@ -1051,9 +1072,9 @@ static char *metadata_sockmsg; #define metadata_queue_size 500 metadata_package metadata_queue_items[metadata_queue_size]; -static pthread_t metadata_thread; +pthread_t metadata_thread; -void metadata_create(void) { +void metadata_create_multicast_socket(void) { if (config.metadata_enabled == 0) return; @@ -1075,7 +1096,7 @@ void metadata_create(void) { if (metadata_sockmsg) { memset(metadata_sockmsg, 0, config.metadata_sockmsglength); } else { - die("Could not malloc metadata socket buffer"); + die("Could not malloc metadata multicast socket buffer"); } } } @@ -1094,6 +1115,15 @@ void metadata_create(void) { umask(oldumask); } +void metadata_delete_multicast_socket(void) { + if (config.metadata_enabled == 0) + return; + shutdown(metadata_sock, SHUT_RDWR); // we want to immediately deallocate the buffer + close(metadata_sock); + if (metadata_sockmsg) + free(metadata_sockmsg); +} + void metadata_open(void) { if (config.metadata_enabled == 0) return; @@ -1111,12 +1141,12 @@ void metadata_open(void) { free(path); } -/* static void metadata_close(void) { + if (fd < 0) + return; close(fd); fd = -1; } -*/ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) { // debug(2, "Process metadata with type %x, code %x and length %u.", type, code, length); @@ -1238,11 +1268,32 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) } } +void metadata_thread_cleanup_function(__attribute__((unused)) void *arg) { + debug(1, "metadata_thread_cleanup_function called"); + metadata_delete_multicast_socket(); + metadata_close(); + pc_queue_delete(&metadata_queue); +} + +void metadata_pack_cleanup_function(void *arg) { + debug(1, "metadata_pack_cleanup_function called"); + metadata_package *pack = (metadata_package *)arg; + if (pack->carrier) + msg_free(pack->carrier); // release the message + else if (pack->data) + free(pack->data); +} + void *metadata_thread_function(__attribute__((unused)) void *ignore) { - metadata_create(); + // create a pc_queue for passing information to a threaded metadata handler + pc_queue_init(&metadata_queue, (char *)&metadata_queue_items, sizeof(metadata_package), + metadata_queue_size); + metadata_create_multicast_socket(); metadata_package pack; + pthread_cleanup_push(metadata_thread_cleanup_function, NULL); while (1) { pc_queue_get_item(&metadata_queue, &pack); + pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack); if (config.metadata_enabled) { metadata_process(pack.type, pack.code, pack.data, pack.length); #ifdef HAVE_METADATA_HUB @@ -1254,23 +1305,24 @@ void *metadata_thread_function(__attribute__((unused)) void *ignore) { } #endif } - if (pack.carrier) - msg_free(pack.carrier); // release the message - else if (pack.data) - free(pack.data); + pthread_cleanup_pop(1); } + pthread_cleanup_pop(1); // will never happen pthread_exit(NULL); } void metadata_init(void) { - // create a pc_queue for passing information to a threaded metadata handler - pc_queue_init(&metadata_queue, (char *)&metadata_queue_items, sizeof(metadata_package), - metadata_queue_size); int ret = pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL); if (ret) debug(1, "Failed to create metadata thread!"); } +void metadata_stop(void) { + debug(1, "metadata_stop called."); + pthread_cancel(metadata_thread); + pthread_join(metadata_thread, NULL); +} + int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier, int block) { @@ -1379,7 +1431,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 @@ -1889,8 +1941,9 @@ authenticate: } void rtsp_conversation_thread_cleanup_function(void *arg) { - debug(1, "rtsp_conversation_thread_func_cleanup_function called."); rtsp_conn_info *conn = (rtsp_conn_info *)arg; + debug(1, "Connection %d: rtsp_conversation_thread_func_cleanup_function called.", + conn->connection_number); if (conn->fd > 0) close(conn->fd); if (conn->auth_nonce) { @@ -1974,8 +2027,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, "RTSP thread %d received an RTSP Packet of type \"%s\":", - conn->connection_number, req->method), + debug(debug_level, + "RTSP thread %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); @@ -2077,6 +2131,14 @@ static const char *format_address(struct sockaddr *fsa) { } */ +void rtsp_listen_loop_cleanup_handler(__attribute__((unused)) void *arg) { + debug(1, "rtsp_listen_loop_cleanup_handler called."); + int *sockfd = (int *)arg; + mdns_unregister(); + if (sockfd) + free(sockfd); +} + void rtsp_listen_loop(void) { struct addrinfo hints, *info, *p; char portstr[6]; @@ -2175,6 +2237,7 @@ void rtsp_listen_loop(void) { int acceptfd; struct timeval tv; + pthread_cleanup_push(rtsp_listen_loop_cleanup_handler, (void *)sockfd); do { pthread_testcancel(); tv.tv_sec = 60; @@ -2272,11 +2335,6 @@ void rtsp_listen_loop(void) { } } while (1); - mdns_unregister(); - - if (sockfd) - free(sockfd); - - // perror("select"); - // die("fell out of the RTSP select loop"); + pthread_cleanup_pop(0); + debug(1, "Oops -- fell out of the RTSP select loop"); } diff --git a/rtsp.h b/rtsp.h index eae28dfe..71e5b22c 100644 --- a/rtsp.h +++ b/rtsp.h @@ -4,14 +4,18 @@ #include "player.h" rtsp_conn_info *playing_conn; +rtsp_conn_info **conns; void rtsp_listen_loop(void); // void rtsp_shutdown_stream(void); void rtsp_request_shutdown_stream(void); -// initialise the metadata stuff +void cancel_all_RTSP_threads(void); + +// initialise and completely delete the metadata stuff void metadata_init(void); +void metadata_stop(void); // sends metadata out to the metadata pipe, if enabled. // It is sent with the type 'ssnc' the given code, data and length diff --git a/shairport.c b/shairport.c index a2a049d9..1b4b4250 100644 --- a/shairport.c +++ b/shairport.c @@ -36,7 +36,6 @@ #include #include #include -#include #include #include #include @@ -117,22 +116,11 @@ static int shutting_down = 0; char configuration_file_path[4096 + 1]; char actual_configuration_file_path[4096 + 1]; -void shairport_shutdown() { - if (shutting_down) - return; - shutting_down = 1; - mdns_unregister(); - rtsp_request_shutdown_stream(); - if (config.output) - config.output->deinit(); -} - static void sig_ignore(__attribute__((unused)) int foo, __attribute__((unused)) siginfo_t *bar, __attribute__((unused)) void *baz) {} static void sig_shutdown(__attribute__((unused)) int foo, __attribute__((unused)) siginfo_t *bar, __attribute__((unused)) void *baz) { debug(1, "shutdown requested..."); - shairport_shutdown(); // daemon_log(LOG_NOTICE, "exit..."); daemon_retval_send(255); daemon_pid_file_remove(); @@ -392,6 +380,7 @@ int parse_options(int argc, char **argv) { debug(2, "Looking for configuration file at full path \"%s\"", config_file_real_path); /* Read the file. If there is an error, report it and exit. */ if (config_read_file(&config_file_stuff, config_file_real_path)) { + free(config_file_real_path); // make config.cfg point to it config.cfg = &config_file_stuff; /* Get the Service Name. */ @@ -938,7 +927,6 @@ int parse_options(int argc, char **argv) { config_set_lookup_bool(config.cfg, "mqtt.publish_cover", &config.mqtt_publish_cover); config_set_lookup_bool(config.cfg, "mqtt.enable_remote", &config.mqtt_enable_remote); #endif - free(config_file_real_path); } // now, do the command line options again, but this time do them fully -- it's a unix convention @@ -1063,13 +1051,14 @@ int parse_options(int argc, char **argv) { } #if defined(HAVE_DBUS) || defined(HAVE_MPRIS) -GMainLoop *loop; +GMainLoop *g_main_loop; pthread_t dbus_thread; void *dbus_thread_func(__attribute__((unused)) void *arg) { - loop = g_main_loop_new(NULL, FALSE); - g_main_loop_run(loop); - return NULL; + g_main_loop = g_main_loop_new(NULL, FALSE); + g_main_loop_run(g_main_loop); + debug(1, "g_main_loop thread exit"); + pthread_exit(NULL); } #endif @@ -1132,6 +1121,17 @@ const char *pid_file_proc(void) { void exit_function() { debug(1, "exit function called..."); + cancel_all_RTSP_threads(); + if (conns) + free(conns); // make sure the connections have been deleted first + if (config.service_name) + free(config.service_name); + if (config.regtype) + free(config.regtype); + if (config.computed_piddir) + free(config.computed_piddir); + if (ranarray) + free((void *)ranarray); if (config.cfg) config_destroy(config.cfg); if (config.appName) @@ -1142,7 +1142,38 @@ void exit_function() { void main_cleanup_handler(__attribute__((unused)) void *arg) { debug(1, "main cleanup handler called."); - shairport_shutdown(); +#ifdef HAVE_MQTT + if (config.mqtt_enabled) { + // terminate_mqtt(); + } +#endif + +#if defined(HAVE_DBUS) || defined(HAVE_MPRIS) +#ifdef HAVE_MPRIS + // stop_mpris_service(); +#endif +#ifdef HAVE_DBUS + stop_dbus_service(); +#endif + debug(1, "Stopping DBUS Loop Thread"); + g_main_loop_quit(g_main_loop); + pthread_join(dbus_thread, NULL); +#endif + +#ifdef HAVE_DACP_CLIENT + debug(1, "Stopping DACP Monitor"); + dacp_monitor_stop(); +#endif + +#ifdef HAVE_METADATA_HUB + debug(1, "Stopping metadata hub"); + metadata_hub_stop(); +#endif + +#ifdef CONFIG_METADATA + metadata_stop(); // close down the metadata pipe +#endif + daemon_log(LOG_NOTICE, "Unexpected exit..."); daemon_retval_send(0); daemon_pid_file_remove(); @@ -1151,6 +1182,7 @@ void main_cleanup_handler(__attribute__((unused)) void *arg) { } int main(int argc, char **argv) { + conns = NULL; // no connections active memset((void *)&main_thread_id, 0, sizeof(main_thread_id)); fp_time_at_startup = get_absolute_time_in_fp(); fp_time_at_last_debug_message = fp_time_at_startup; @@ -1408,13 +1440,8 @@ int main(int argc, char **argv) { /* end libdaemon stuff */ } - // int old_cancel_state = 0; - // pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); - // pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old_cancel_state); main_thread_id = pthread_self(); - if (main_thread_id) - debug(1, "Main thread ID set up."); - else + if (!main_thread_id) debug(1, "Main thread is set up to be NULL!"); signal_setup(); @@ -1552,8 +1579,9 @@ int main(int argc, char **argv) { debug(1, "loudness is %d.", config.loudness); debug(1, "loudness reference level is %f", config.loudness_reference_volume_db); debug(1, "disable resend requests is %s.", config.disable_resend_requests ? "on" : "off"); - debug(1, "diagnostic_drop_packet_fraction is %f. A value of 0.0 means no packets will be dropped " - "deliberately.", + debug(1, + "diagnostic_drop_packet_fraction is %f. A value of 0.0 means no packets will be dropped " + "deliberately.", config.diagnostic_drop_packet_fraction); uint8_t ap_md5[16]; @@ -1615,7 +1643,6 @@ int main(int argc, char **argv) { rtsp_listen_loop(); // should not reach this... - // shairport_shutdown(); // daemon_log(LOG_NOTICE, "Unexpected exit..."); // daemon_retval_send(0); // daemon_pid_file_remove(); diff --git a/tinysvcmdns.c b/tinysvcmdns.c index ebf6fea7..bdce032c 100644 --- a/tinysvcmdns.c +++ b/tinysvcmdns.c @@ -1713,7 +1713,8 @@ void mdnsd_stop(struct mdnsd *s) { assert(s != NULL); struct timeval tv = { - .tv_sec = 0, .tv_usec = 500 * 1000, + .tv_sec = 0, + .tv_usec = 500 * 1000, }; s->stop_flag = 1; From d70d7e5ec5c7782f52ba27c192deb4b82db11cad Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 21 Jul 2018 11:05:04 +0100 Subject: [PATCH 021/569] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0fb02c9b..802fc671 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ The following procedures will build and install the `shairport-sync` application **Remove Old Versions Of Shairport Sync** -You should check to see if `shairport-sync` is already installed – you can use the command `$ which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere. +You should check to see if `shairport-sync` is already installed – you can use the command `$ which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere. You should also remove the file `/etc/systemd/system/shairport-sync.service` if it exists. **FreeBSD** From 2eaaa9cdb3fdd25ba96f165fdf68c813cfacf25f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 21 Jul 2018 15:35:07 +0100 Subject: [PATCH 022/569] Fix a bug where the dacp notification handlers are called --- dacp.c | 23 ++++++++--------------- metadata_hub.c | 4 ++-- player.c | 2 ++ rtsp.c | 6 ++++-- shairport.c | 2 +- 5 files changed, 17 insertions(+), 20 deletions(-) diff --git a/dacp.c b/dacp.c index 81a6f4d2..667ac19c 100644 --- a/dacp.c +++ b/dacp.c @@ -294,12 +294,10 @@ void relinquish_dacp_server_information(rtsp_conn_info *conn) { // as the conn's connection number // this is to signify that the player has stopped, but only if another thread (with a different // index) hasn't already taken over the dacp service - sps_pthread_mutex_timedlock( - &dacp_server_information_lock, 500000, - "set_dacp_server_information couldn't get DACP server information lock in 0.5 second!.", 2); + debug_mutex_lock(&dacp_server_information_lock, 500000, 2); if (dacp_server.players_connection_thread_index == conn->connection_number) dacp_server.players_connection_thread_index = 0; - pthread_mutex_unlock(&dacp_server_information_lock); + debug_mutex_unlock(&dacp_server_information_lock, 3); } // this will be running on the thread of its caller, not of the conversation thread... @@ -309,9 +307,7 @@ void relinquish_dacp_server_information(rtsp_conn_info *conn) { // Thus, we can keep the DACP port that might have previously been discovered void set_dacp_server_information(rtsp_conn_info *conn) { // debug(1, "set_dacp_server_information"); - sps_pthread_mutex_timedlock( - &dacp_server_information_lock, 500000, - "set_dacp_server_information couldn't get DACP server information lock in 0.5 second!.", 2); + debug_mutex_lock(&dacp_server_information_lock, 500000, 2); dacp_server.players_connection_thread_index = conn->connection_number; if ((conn->dacp_id == NULL) || (strcmp(conn->dacp_id, dacp_server.dacp_id) != 0)) { @@ -360,23 +356,20 @@ void set_dacp_server_information(rtsp_conn_info *conn) { debug(2, "set_dacp_server_information set active-remote id to %" PRIu32 ".", dacp_server.active_remote_id); pthread_cond_signal(&dacp_server_information_cv); - pthread_mutex_unlock(&dacp_server_information_lock); + debug_mutex_unlock(&dacp_server_information_lock, 3); } void dacp_monitor_port_update_callback(char *dacp_id, uint16_t port) { debug(2, "dacp_monitor_port_update_callback with Remote ID \"%s\" and port number %d.", dacp_id, port); - sps_pthread_mutex_timedlock( - &dacp_server_information_lock, 500000, - "dacp_monitor_port_update_callback couldn't get DACP server information lock in 0.5 second!.", - 2); + debug_mutex_lock(&dacp_server_information_lock, 500000, 2); 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"); + // debug(2, "dacp_monitor_port_update_callback enables scan"); } // metadata_hub_modify_prolog(); // int ch = metadata_store.dacp_server_active != dacp_server.scan_enable; @@ -386,11 +379,11 @@ void dacp_monitor_port_update_callback(char *dacp_id, uint16_t port) { debug(1, "dacp port monitor reporting on an out-of-use remote."); } pthread_cond_signal(&dacp_server_information_cv); - pthread_mutex_unlock(&dacp_server_information_lock); + debug_mutex_unlock(&dacp_server_information_lock, 3); } void dacp_monitor_thread_code_cleanup(__attribute__((unused)) void *arg) { - debug(1, "dacp_monitor_thread_code_cleanup called."); + // debug(1, "dacp_monitor_thread_code_cleanup called."); pthread_mutex_unlock(&dacp_server_information_lock); } diff --git a/metadata_hub.c b/metadata_hub.c index 3892e30a..8c957b76 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -127,8 +127,8 @@ void add_metadata_watcher(metadata_watcher fn, void *userdata) { } void metadata_hub_unlock_hub_mutex_cleanup(__attribute__((unused)) void *arg) { - debug(1, "metadata_hub_unlock_hub_mutex_cleanup called."); - pthread_rwlock_wrlock(&metadata_hub_re_lock); + // debug(1, "metadata_hub_unlock_hub_mutex_cleanup called."); + pthread_rwlock_unlock(&metadata_hub_re_lock); } void run_metadata_watchers(void) { diff --git a/player.c b/player.c index 6a2f40aa..5b1fc419 100644 --- a/player.c +++ b/player.c @@ -1683,6 +1683,7 @@ void *player_thread_func(void *arg) { // stop looking elsewhere for DACP stuff #ifdef HAVE_DACP_CLIENT + debug(1, "Set dacp server info"); // this does not have pthread cancellation points in it (assuming avahi doesn't) set_dacp_server_information(conn); // this will start scanning when a port is registered by the // code initiated by the mdns_dacp_monitor @@ -1696,6 +1697,7 @@ void *player_thread_func(void *arg) { // this does not have pthread cancellation points in it (assuming avahi doesn't) conn->dapo_private_storage = mdns_dacp_monitor(conn->dacp_id); // ?? #endif + debug(1, "Get radio"); conn->framesProcessedInThisEpoch = 0; conn->framesGeneratedInThisEpoch = 0; diff --git a/rtsp.c b/rtsp.c index 95b68522..435c4e6e 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1276,7 +1276,7 @@ void metadata_thread_cleanup_function(__attribute__((unused)) void *arg) { } void metadata_pack_cleanup_function(void *arg) { - debug(1, "metadata_pack_cleanup_function called"); + // debug(1, "metadata_pack_cleanup_function called"); metadata_package *pack = (metadata_package *)arg; if (pack->carrier) msg_free(pack->carrier); // release the message @@ -1944,6 +1944,7 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; debug(1, "Connection %d: rtsp_conversation_thread_func_cleanup_function called.", conn->connection_number); + player_stop(conn); if (conn->fd > 0) close(conn->fd); if (conn->auth_nonce) { @@ -1979,7 +1980,7 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { } void msg_cleanup_function(void *arg) { - debug(1, "msg_cleanup_function called."); + // debug(1, "msg_cleanup_function called."); msg_free((rtsp_message *)arg); } @@ -2133,6 +2134,7 @@ static const char *format_address(struct sockaddr *fsa) { void rtsp_listen_loop_cleanup_handler(__attribute__((unused)) void *arg) { debug(1, "rtsp_listen_loop_cleanup_handler called."); + cancel_all_RTSP_threads(); int *sockfd = (int *)arg; mdns_unregister(); if (sockfd) diff --git a/shairport.c b/shairport.c index 1b4b4250..b4136314 100644 --- a/shairport.c +++ b/shairport.c @@ -1121,7 +1121,7 @@ const char *pid_file_proc(void) { void exit_function() { debug(1, "exit function called..."); - cancel_all_RTSP_threads(); + // cancel_all_RTSP_threads(); if (conns) free(conns); // make sure the connections have been deleted first if (config.service_name) From 5f750ba9b727c384b992711a95a32fe0a48dcecd Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 22 Jul 2018 16:12:52 +0100 Subject: [PATCH 023/569] Fix a few memory leaks. --- audio_alsa.c | 1 + common.c | 3 ++- dacp.c | 61 ++++++++++++++++++++++++++++++++++++++++++++-------- dacp.h | 2 +- mdns_avahi.c | 4 ++-- shairport.c | 8 +++++-- 6 files changed, 64 insertions(+), 15 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 1a61858e..3f00fddd 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -908,6 +908,7 @@ static void flush(void) { debug_mutex_lock(&alsa_mutex, 10000, 1); int derr; do_mute(1); + if (alsa_handle) { if ((derr = snd_pcm_drop(alsa_handle))) diff --git a/common.c b/common.c index 20f0b5b9..0bfdebb8 100644 --- a/common.c +++ b/common.c @@ -399,7 +399,8 @@ uint8_t *base64_dec(char *input, int *outlen) { nread = BIO_read(b64, buf, bufsize); - BIO_free_all(bmem); + // BIO_free_all(bmem); + BIO_free_all(b64); *outlen = nread; return buf; diff --git a/dacp.c b/dacp.c index 667ac19c..80ccb2ed 100644 --- a/dacp.c +++ b/dacp.c @@ -122,6 +122,35 @@ static pthread_mutex_t dacp_conversation_lock; static pthread_mutex_t dacp_server_information_lock; static pthread_cond_t dacp_server_information_cv = PTHREAD_COND_INITIALIZER; +void addrinfo_cleanup(void *arg) { + debug(1, "addrinfo cleanup called."); + struct addrinfo **info = (struct addrinfo **)arg; + freeaddrinfo(*info); +} + +void mutex_lock_cleanup(void *arg) { + debug(1, "mutex lock cleanup called."); + pthread_mutex_t *m = (pthread_mutex_t *)arg; + pthread_mutex_unlock(m); +} + +void connect_cleanup(void *arg) { + debug(1, "connect cleanup called."); + int *fd = (int *)arg; + close(*fd); +} + +void http_cleanup(void *arg) { + debug(1, "http cleanup called."); + struct http_roundtripper *rt = (struct http_roundtripper *)arg; + http_free(rt); +} + +void malloc_cleanup(void *arg) { + debug(1, "malloc cleanup called."); + free(arg); +} + int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // will malloc space for the body or set it to NULL -- the caller should free it. @@ -166,12 +195,13 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // debug(1,"Error %d \"%s\" at getaddrinfo.",ires,gai_strerror(ires)); response.code = 498; // Bad Address information for the DACP server } else { - + pthread_cleanup_push(addrinfo_cleanup, (void *)&res); // only do this one at a time -- not sure it is necessary, but better safe than sorry int mutex_reply = sps_pthread_mutex_timedlock(&dacp_conversation_lock, 2000000, command, 1); // int mutex_reply = pthread_mutex_lock(&dacp_conversation_lock); if (mutex_reply == 0) { + pthread_cleanup_push(mutex_lock_cleanup, (void *)&dacp_conversation_lock); // debug(1,"dacp_conversation_lock acquired for command \"%s\".",command); // make a socket: @@ -195,6 +225,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { debug(3, "DACP connect failed with errno %d.", errno); response.code = 496; // Can't connect to the DACP server } else { + pthread_cleanup_push(connect_cleanup, (void *)&sockfd); // debug(1,"DACP connect succeeded."); snprintf(message, sizeof(message), @@ -213,9 +244,11 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { 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; http_init(&rt, responseFuncs, &response); + pthread_cleanup_push(http_cleanup, &rt); int needmore = 1; int looperror = 0; @@ -254,13 +287,18 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { response.size = 0; } // debug(1,"Size of response body is %d",response.size); - http_free(&rt); + pthread_cleanup_pop(1); // this should call http_cleanup + // http_free(&rt); + pthread_cleanup_pop( + 0); // this should *not* free the malloced buffer -- just pop the malloc cleanup } + pthread_cleanup_pop(1); // this should close the socket + // close(sockfd); + // debug(1,"DACP socket closed."); } - close(sockfd); - // debug(1,"DACP socket closed."); } - pthread_mutex_unlock(&dacp_conversation_lock); + pthread_cleanup_pop(1); // this should unlock the dacp_conversation_lock); + // pthread_mutex_unlock(&dacp_conversation_lock); // debug(1,"Sent command\"%s\" with a response body of size %d.",command,response.size); // debug(1,"dacp_conversation_lock released."); } else { @@ -270,6 +308,8 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { command); response.code = 494; // This client is already busy } + pthread_cleanup_pop(1); // this should free the addrinfo + // freeaddrinfo(res); } *body = response.body; *bodysize = response.size; @@ -930,12 +970,13 @@ int dacp_get_speaker_list(dacp_spkr_stuff *speaker_info, int max_size_of_array, sp -= item_size; le -= 8; speaker_index++; - if (speaker_index == max_size_of_array) + if (speaker_index == max_size_of_array) { return 413; // Payload Too Large -- too many speakers + } speaker_info[speaker_index].active = 0; speaker_info[speaker_index].speaker_number = 0; speaker_info[speaker_index].volume = 0; - speaker_info[speaker_index].name = NULL; + speaker_info[speaker_index].name[0] = '\0'; } else { le -= item_size + 8; char *t; @@ -945,8 +986,10 @@ int dacp_get_speaker_list(dacp_spkr_stuff *speaker_info, int max_size_of_array, switch (type) { case 'minm': t = sp - item_size; - speaker_info[speaker_index].name = strndup(t, item_size); - // debug(1," \"%s\"",speaker_info[speaker_index].name); + strncpy((char *)&speaker_info[speaker_index].name, t, + sizeof(speaker_info[speaker_index].name)); + speaker_info[speaker_index].name[sizeof(speaker_info[speaker_index].name) - 1] = + '\0'; // just in case break; case 'cmvo': t = sp - item_size; diff --git a/dacp.h b/dacp.h index 5379d069..ce1e1278 100644 --- a/dacp.h +++ b/dacp.h @@ -10,7 +10,7 @@ typedef struct dacp_speaker_stuff { int64_t speaker_number; int active; int32_t volume; - char *name; // this is really just for debugging + char name[128]; // this is really just for debugging } dacp_spkr_stuff; void dacp_monitor_start(); diff --git a/mdns_avahi.c b/mdns_avahi.c index c9b8ae00..d9eb4b02 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -385,8 +385,8 @@ static int avahi_register(char *srvname, int srvport) { static void avahi_unregister(void) { debug(1, "avahi: avahi_unregister."); if (tpoll) { - // debug(1, "avahi: stop the threaded poll."); - // avahi_threaded_poll_stop(tpoll); + debug(1, "avahi: stop the threaded poll."); + avahi_threaded_poll_stop(tpoll); if (client) { debug(1, "avahi: free the client."); diff --git a/shairport.c b/shairport.c index b4136314..3c843845 100644 --- a/shairport.c +++ b/shairport.c @@ -1173,7 +1173,10 @@ void main_cleanup_handler(__attribute__((unused)) void *arg) { #ifdef CONFIG_METADATA metadata_stop(); // close down the metadata pipe #endif - + if (config.output->deinit) { + debug(1,"Deinitialise the audio backend."); + config.output->deinit(); + } daemon_log(LOG_NOTICE, "Unexpected exit..."); daemon_retval_send(0); daemon_pid_file_remove(); @@ -1449,7 +1452,6 @@ int main(int argc, char **argv) { // make sure the program can create files that group and world can read umask(S_IWGRP | S_IWOTH); - pthread_cleanup_push(main_cleanup_handler, NULL); config.output = audio_get_output(config.output_name); if (!config.output) { @@ -1458,6 +1460,8 @@ int main(int argc, char **argv) { } config.output->init(argc - audio_arg, argv + audio_arg); + pthread_cleanup_push(main_cleanup_handler, NULL); + // daemon_log(LOG_NOTICE, "startup"); switch (endianness) { From 703ab8e5562d57db2e1bb08c357804627b3eea53 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 22 Jul 2018 16:13:49 +0100 Subject: [PATCH 024/569] Quieten a few debug messages --- dacp.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dacp.c b/dacp.c index 80ccb2ed..090bb526 100644 --- a/dacp.c +++ b/dacp.c @@ -123,31 +123,31 @@ static pthread_mutex_t dacp_server_information_lock; static pthread_cond_t dacp_server_information_cv = PTHREAD_COND_INITIALIZER; void addrinfo_cleanup(void *arg) { - debug(1, "addrinfo cleanup called."); + // debug(1, "addrinfo cleanup called."); struct addrinfo **info = (struct addrinfo **)arg; freeaddrinfo(*info); } void mutex_lock_cleanup(void *arg) { - debug(1, "mutex lock cleanup called."); + // debug(1, "mutex lock cleanup called."); pthread_mutex_t *m = (pthread_mutex_t *)arg; pthread_mutex_unlock(m); } void connect_cleanup(void *arg) { - debug(1, "connect cleanup called."); + // debug(1, "connect cleanup called."); int *fd = (int *)arg; close(*fd); } void http_cleanup(void *arg) { - debug(1, "http cleanup called."); + // debug(1, "http cleanup called."); struct http_roundtripper *rt = (struct http_roundtripper *)arg; http_free(rt); } void malloc_cleanup(void *arg) { - debug(1, "malloc cleanup called."); + // debug(1, "malloc cleanup called."); free(arg); } From f153a1f57013738e8cad9122351c6aef8b35a922 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 22 Jul 2018 16:26:47 +0100 Subject: [PATCH 025/569] Remove some unwanted debug messages --- common.c | 2 -- player.c | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/common.c b/common.c index 0bfdebb8..f43c207f 100644 --- a/common.c +++ b/common.c @@ -441,8 +441,6 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { BIO_free(bmem); } - debug(1, "RSA_size(rsa) is %d", RSA_size(rsa)); - uint8_t *out = malloc(RSA_size(rsa)); switch (mode) { case RSA_MODE_AUTH: diff --git a/player.c b/player.c index 5b1fc419..266262d0 100644 --- a/player.c +++ b/player.c @@ -1683,7 +1683,7 @@ void *player_thread_func(void *arg) { // stop looking elsewhere for DACP stuff #ifdef HAVE_DACP_CLIENT - debug(1, "Set dacp server info"); + // debug(1, "Set dacp server info"); // this does not have pthread cancellation points in it (assuming avahi doesn't) set_dacp_server_information(conn); // this will start scanning when a port is registered by the // code initiated by the mdns_dacp_monitor @@ -1697,7 +1697,6 @@ void *player_thread_func(void *arg) { // this does not have pthread cancellation points in it (assuming avahi doesn't) conn->dapo_private_storage = mdns_dacp_monitor(conn->dacp_id); // ?? #endif - debug(1, "Get radio"); conn->framesProcessedInThisEpoch = 0; conn->framesGeneratedInThisEpoch = 0; From 865c71b1d010c0bce0b08cbc988ae553e5e873d2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 22 Jul 2018 16:29:36 +0100 Subject: [PATCH 026/569] Remove an unwanted debug messages --- rtsp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rtsp.c b/rtsp.c index 435c4e6e..df34314b 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1942,8 +1942,8 @@ authenticate: void rtsp_conversation_thread_cleanup_function(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; - debug(1, "Connection %d: rtsp_conversation_thread_func_cleanup_function called.", - conn->connection_number); + //debug(1, "Connection %d: rtsp_conversation_thread_func_cleanup_function called.", + // conn->connection_number); player_stop(conn); if (conn->fd > 0) close(conn->fd); From dc4e577d8f0338ab4e29bd808e78cc3415fb220f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 23 Jul 2018 13:56:54 +0100 Subject: [PATCH 027/569] Improve some debug messages. --- rtsp.c | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/rtsp.c b/rtsp.c index df34314b..1961a18f 100644 --- a/rtsp.c +++ b/rtsp.c @@ -919,9 +919,9 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *r #ifdef CONFIG_METADATA if (!strncmp(cp, "progress: ", 10)) { char *progress = cp + 10; - // debug(2, "progress: \"%s\"\n", - // progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per second + // debug(2, "progress: \"%s\"\n",progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per second send_ssnc_metadata('prgr', strdup(progress), strlen(progress), 1); + } else #endif { @@ -1528,7 +1528,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag if (!playing_conn) die("Non existent playing_conn with play_lock enabled."); - debug(1, "RTSP Conversation thread %d already playing when asked by thread %d.", + debug(1, "Connection %d: already playing when asked by connection %d.", playing_conn->connection_number, conn->connection_number); if (playing_conn->stop) { debug(1, "Playing connection is already shutting down; waiting for it..."); @@ -1553,7 +1553,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag if (pthread_mutex_trylock(&play_lock) == 0) have_the_player = 1; else - debug(1, "ANNOUNCE failed to get the player"); + debug(1, "Connection %d: ANNOUNCE failed to get the player", conn->connection_number); } if (have_the_player) { @@ -1694,7 +1694,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag resp->respcode = 200; } else { resp->respcode = 453; - debug(1, "Already playing."); + debug(1, "Connection %d: failed because a connection is already playing.", conn->connection_number); } out: @@ -1942,11 +1942,14 @@ authenticate: void rtsp_conversation_thread_cleanup_function(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; - //debug(1, "Connection %d: rtsp_conversation_thread_func_cleanup_function called.", - // conn->connection_number); + debug(1, "Connection %d: rtsp_conversation_thread_func_cleanup_function called.", + conn->connection_number); player_stop(conn); - if (conn->fd > 0) + if (conn->fd > 0) { + debug(1, "Connection %d: closing fd %d.", + conn->connection_number,conn->fd); close(conn->fd); + } if (conn->auth_nonce) { free(conn->auth_nonce); conn->auth_nonce = NULL; @@ -2276,7 +2279,7 @@ void rtsp_listen_loop(void) { conn->fd = accept(acceptfd, (struct sockaddr *)&conn->remote, &slen); if (conn->fd < 0) { - debug(1, "New RTSP connection on port %d not accepted:", config.port); + debug(1, "Connection %d: New connection on port %d not accepted:", conn->connection_number, config.port); perror("failed to accept connection"); free(conn); } else { @@ -2295,8 +2298,8 @@ void rtsp_listen_loop(void) { sa = (struct sockaddr_in *)&conn->remote; inet_ntop(AF_INET, &(sa->sin_addr), remote_ip4, INET_ADDRSTRLEN); unsigned short int rport = ntohs(sa->sin_port); - debug(2, "New RTSP connection from %s:%u to self at %s:%u on conversation thread %d.", - remote_ip4, rport, ip4, tport, conn->connection_number); + debug(1, "Connection %d: new connection from %s:%u to self at %s:%u.", + conn->connection_number,remote_ip4, rport, ip4, tport); } #ifdef AF_INET6 if (local_info->SAFAMILY == AF_INET6) { @@ -2312,8 +2315,8 @@ void rtsp_listen_loop(void) { sa6 = (struct sockaddr_in6 *)&conn->remote; // pretend this is loaded with something inet_ntop(AF_INET6, &(sa6->sin6_addr), remote_ip6, INET6_ADDRSTRLEN); u_int16_t rport = ntohs(sa6->sin6_port); - debug(2, "New RTSP connection from [%s]:%u to self at [%s]:%u on conversation thread %d.", - remote_ip6, rport, ip6, tport, conn->connection_number); + debug(1, "Connection %d: new connection from [%s]:%u to self at [%s]:%u.", + conn->connection_number, remote_ip6, rport, ip6, tport); } #endif From a30c254bb6d6784e22a7e498262981f96eda75ed Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 23 Jul 2018 14:26:48 +0100 Subject: [PATCH 028/569] Quieten some debug messages. --- rtsp.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rtsp.c b/rtsp.c index 1961a18f..16c6836a 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1942,12 +1942,12 @@ authenticate: void rtsp_conversation_thread_cleanup_function(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; - debug(1, "Connection %d: rtsp_conversation_thread_func_cleanup_function called.", - conn->connection_number); + //debug(1, "Connection %d: rtsp_conversation_thread_func_cleanup_function called.", + // conn->connection_number); player_stop(conn); if (conn->fd > 0) { - debug(1, "Connection %d: closing fd %d.", - conn->connection_number,conn->fd); + // debug(1, "Connection %d: closing fd %d.", + // conn->connection_number,conn->fd); close(conn->fd); } if (conn->auth_nonce) { @@ -2298,7 +2298,7 @@ void rtsp_listen_loop(void) { sa = (struct sockaddr_in *)&conn->remote; inet_ntop(AF_INET, &(sa->sin_addr), remote_ip4, INET_ADDRSTRLEN); unsigned short int rport = ntohs(sa->sin_port); - debug(1, "Connection %d: new connection from %s:%u to self at %s:%u.", + 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 @@ -2315,7 +2315,7 @@ void rtsp_listen_loop(void) { sa6 = (struct sockaddr_in6 *)&conn->remote; // pretend this is loaded with something inet_ntop(AF_INET6, &(sa6->sin6_addr), remote_ip6, INET6_ADDRSTRLEN); u_int16_t rport = ntohs(sa6->sin6_port); - debug(1, "Connection %d: new connection from [%s]:%u to self at [%s]:%u.", + debug(2, "Connection %d: new connection from [%s]:%u to self at [%s]:%u.", conn->connection_number, remote_ip6, rport, ip6, tport); } #endif From aa13fd9849472e4966a5c546e1e0de5404ea1845 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 23 Jul 2018 17:37:43 +0100 Subject: [PATCH 029/569] Remove hack to try to avoid whitespace removal by rsyslogd. It turns out that this has been fixed in recent versions of rsyslogd. Not on Raspbian Stretch yet though... --- player.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/player.c b/player.c index 266262d0..bc479ed8 100644 --- a/player.c +++ b/player.c @@ -1705,7 +1705,7 @@ void *player_thread_func(void *arg) { if (config.statistics_requested) { if ((config.output->delay)) { if (config.no_sync == 0) { - inform("| sync error in milliseconds, " + inform("sync error in milliseconds, " "net correction in ppm, " "corrections in ppm, " "total packets, " @@ -1717,7 +1717,7 @@ void *player_thread_func(void *arg) { "min buffer occupancy, " "max buffer occupancy"); } else { - inform("| sync error in milliseconds, " + inform("sync error in milliseconds, " "total packets, " "missing packets, " "late packets, " @@ -1728,7 +1728,7 @@ void *player_thread_func(void *arg) { "max buffer occupancy"); } } else { - inform("| sync error in milliseconds, " + inform("sync error in milliseconds, " "total packets, " "missing packets, " "late packets, " @@ -2289,7 +2289,7 @@ void *player_thread_func(void *arg) { if ((config.output->delay)) { if (config.no_sync == 0) { inform( - "|%*.1f," /* Sync error in milliseconds */ + "%*.1f," /* Sync error in milliseconds */ "%*.1f," /* net correction in ppm */ "%*.1f," /* corrections in ppm */ "%*d," /* total packets */ @@ -2308,7 +2308,7 @@ void *player_thread_func(void *arg) { conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy); } else { - inform("|%*.1f," /* Sync error in milliseconds */ + inform("%*.1f," /* Sync error in milliseconds */ "%*d," /* total packets */ "%*llu," /* missing packets */ "%*llu," /* late packets */ @@ -2323,7 +2323,7 @@ void *player_thread_func(void *arg) { minimum_buffer_occupancy, 5, maximum_buffer_occupancy); } } else { - inform("|%*.1f," /* Sync error in milliseconds */ + inform("%*.1f," /* Sync error in milliseconds */ "%*d," /* total packets */ "%*llu," /* missing packets */ "%*llu," /* late packets */ From 577449dffaaf5cf8b71396dc55965d39dc6e93eb Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 27 Jul 2018 10:58:36 +0100 Subject: [PATCH 030/569] Remove use of SIGUSR1 and pthread_kill to stop threads. Not debugged! --- player.c | 3 ++- rtsp.c | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/player.c b/player.c index bc479ed8..9bec9893 100644 --- a/player.c +++ b/player.c @@ -815,7 +815,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { "from RTSP conversation %d.", conn->connection_number); conn->stop = 1; - pthread_kill(conn->thread, SIGUSR1); + pthread_cancel(conn->thread); + // pthread_kill(conn->thread, SIGUSR1); } } int rco = get_requested_connection_state_to_output(); diff --git a/rtsp.c b/rtsp.c index 16c6836a..f0e072c6 100644 --- a/rtsp.c +++ b/rtsp.c @@ -316,8 +316,11 @@ void ask_other_rtsp_conversation_threads_to_stop(pthread_t except_this_thread) { for (i = 0; i < nconns; i++) { if (((except_this_thread == 0) || (pthread_equal(conns[i]->thread, except_this_thread) == 0)) && (conns[i]->running != 0)) { - conns[i]->stop = 1; - pthread_kill(conns[i]->thread, SIGUSR1); + pthread_cancel(conns[i]->thread); + pthread_join(conns[i]->thread,NULL); + debug(1,"Connection %d: asked to stop.",conns[i]->connection_number); + // conns[i]->stop = 1; + // pthread_kill(conns[i]->thread, SIGUSR1); } } } From c85e7e05d1255624cfc2df0542dc9154cf065610 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 28 Jul 2018 19:02:02 +0100 Subject: [PATCH 031/569] Add a frames per second output. This should be exact if the machine is connected up to, say, an NTP time service. It uses CLOCK_MONOTONIC time. --- audio.h | 1 + audio_alsa.c | 49 +++++++++++++++++++++++++++++++++++++++---------- dbus-service.c | 2 -- player.c | 29 ++++++++++++++++++++++++----- player.h | 6 ++++++ 5 files changed, 70 insertions(+), 17 deletions(-) diff --git a/audio.h b/audio.h index dc7eb873..2a15587c 100644 --- a/audio.h +++ b/audio.h @@ -33,6 +33,7 @@ typedef struct { // will change dynamically, so keep watching it. Implemented in ALSA only. // returns a negative error code if there's a problem int (*delay)(long *the_delay); // snd_pcm_sframes_t is a signed long + int (*rate_info)(uint64_t *elapsed_time, uint64_t *frames_played); // use this to get the true rate of the DAC // may be NULL, in which case soft volume is applied void (*volume)(double vol); diff --git a/audio_alsa.c b/audio_alsa.c index 3f00fddd..556fd957 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -35,6 +35,7 @@ #include #include #include +#include static void help(void); static int init(int argc, char **argv); @@ -45,6 +46,7 @@ static void stop(void); static void flush(void); int delay(long *the_delay); void do_mute(int request); +int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played); static void volume(double vol); void do_volume(double vol); @@ -64,6 +66,7 @@ audio_output audio_alsa = { .flush = &flush, .delay = &delay, .play = &play, + .rate_info = &get_rate_information, .mute = NULL, // a function will be provided if it can, and is allowed to, do hardware mute .volume = NULL, // a function will be provided if it can do hardware volume .parameters = ¶meters}; @@ -110,6 +113,9 @@ int alsa_characteristics_already_listed = 0; static snd_pcm_uframes_t period_size_requested, buffer_size_requested; static int set_period_size_request, set_buffer_size_request; +static uint64_t time_of_first_frame; +static uint64_t frames_sent_for_playing; + static void help(void) { printf(" -d output-device set the output device [default*|...]\n" " -m mixer-device set the mixer device ['output-device'*|...]\n" @@ -810,6 +816,9 @@ static void start(int i_sample_rate, int i_sample_format) { sample_format = SPS_FORMAT_S16; // default else sample_format = i_sample_format; + + time_of_first_frame = 0; + frames_sent_for_playing = 0; } int delay(long *the_delay) { @@ -829,20 +838,23 @@ int delay(long *the_delay) { snd_strerror(reply), *the_delay); snd_pcm_recover(alsa_handle, reply, 1); } - } else if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) { - *the_delay = 0; - reply = 0; // no error } else { - if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) { + time_of_first_frame = 0; + if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) { *the_delay = 0; reply = 0; // no error } else { - reply = -EIO; - debug(1, "Error -- ALSA delay(): bad state: %d.", snd_pcm_state(alsa_handle)); - } - if ((derr = snd_pcm_prepare(alsa_handle))) { - snd_pcm_recover(alsa_handle, derr, 1); - debug(1, "Error preparing after delay error: \"%s\".", snd_strerror(derr)); + if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) { + *the_delay = 0; + reply = 0; // no error + } else { + reply = -EIO; + debug(1, "Error -- ALSA delay(): bad state: %d.", snd_pcm_state(alsa_handle)); + } + if ((derr = snd_pcm_prepare(alsa_handle))) { + snd_pcm_recover(alsa_handle, derr, 1); + debug(1, "Error preparing after delay error: \"%s\".", snd_strerror(derr)); + } } } debug_mutex_unlock(&alsa_mutex, 3); @@ -853,6 +865,16 @@ int delay(long *the_delay) { } } +int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) { + long frames_left = 0; + int result = delay(&frames_left); + if (result >= 0) { + *elapsed_time = get_absolute_time_in_fp()-time_of_first_frame; + *frames_played = frames_sent_for_playing - frames_left; + } + return result; +} + static void play(void *buf, int samples) { // debug(3,"audio_alsa play called."); int ret = 0; @@ -876,6 +898,7 @@ static void play(void *buf, int samples) { snd_pcm_recover(alsa_handle, err, 1); debug(1, "Error preparing after underrun: \"%s\".", snd_strerror(err)); } + time_of_first_frame = 0; } if ((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) || (snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING)) { @@ -885,11 +908,16 @@ static void play(void *buf, int samples) { debug(1, "empty buffer being passed to pcm_writei -- skipping it"); if ((samples != 0) && (buf != NULL)) { err = alsa_pcm_write(alsa_handle, buf, samples); + if (time_of_first_frame == 0) { + time_of_first_frame = get_absolute_time_in_fp(); + frames_sent_for_playing = 0; + } if (err < 0) { debug(1, "Error %d writing %d samples in play(): \"%s\".", err, samples, snd_strerror(err)); snd_pcm_recover(alsa_handle, err, 1); } + frames_sent_for_playing += samples; } } else { debug(1, "Error -- ALSA device in incorrect state (%d) for play.", @@ -898,6 +926,7 @@ static void play(void *buf, int samples) { snd_pcm_recover(alsa_handle, err, 1); debug(1, "Error preparing after play error: \"%s\".", snd_strerror(err)); } + time_of_first_frame = 0; } debug_mutex_unlock(&alsa_mutex, 3); } diff --git a/dbus-service.c b/dbus-service.c index 5fc78c25..90e68a08 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -746,7 +746,6 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name static void on_dbus_name_lost_again(__attribute__((unused)) GDBusConnection *connection, __attribute__((unused)) const gchar *name, __attribute__((unused)) gpointer user_data) { - debug(1, "Name lost again."); warn("Could not acquire a Shairport Sync native D-Bus interface \"%s\" on the %s bus.", name, (config.dbus_service_bus_type == DBT_session) ? "session" : "system"); } @@ -754,7 +753,6 @@ static void on_dbus_name_lost_again(__attribute__((unused)) GDBusConnection *con static void on_dbus_name_lost(__attribute__((unused)) GDBusConnection *connection, __attribute__((unused)) const gchar *name, __attribute__((unused)) gpointer user_data) { - debug(1, "Name lost."); // debug(1, "Could not acquire a Shairport Sync native D-Bus interface \"%s\" on the %s bus -- // will try adding the process " // "number to the end of it.", diff --git a/player.c b/player.c index 9bec9893..1a457334 100644 --- a/player.c +++ b/player.c @@ -1435,8 +1435,12 @@ void player_thread_cleanup_handler(void *arg) { int elapsedHours = rawSeconds / 3600; int elapsedMin = (rawSeconds / 60) % 60; int elapsedSec = rawSeconds % 60; - inform("Playback Stopped. Total playing time %02d:%02d:%02d.", elapsedHours, elapsedMin, - elapsedSec); + if (conn->frame_rate_status == 0) + inform("Playback Stopped. Total playing time %02d:%02d:%02d at %0.2f frames per second.", elapsedHours, elapsedMin, + elapsedSec,conn->frame_rate); + else + inform("Playback Stopped. Total playing time %02d:%02d:%02d.", elapsedHours, elapsedMin, + elapsedSec); } #ifdef HAVE_DACP_CLIENT @@ -1594,6 +1598,9 @@ void *player_thread_func(void *arg) { int32_t maximum_buffer_occupancy = INT32_MIN; conn->playstart = time(NULL); + + conn->frame_rate = 0.0; + conn->frame_rate_status = -1; // zero means okay conn->buffer_occupancy = 0; @@ -1716,7 +1723,8 @@ void *player_thread_func(void *arg) { "resend requests, " "min DAC queue size, " "min buffer occupancy, " - "max buffer occupancy"); + "max buffer occupancy, " + "frames per second"); } else { inform("sync error in milliseconds, " "total packets, " @@ -2289,6 +2297,16 @@ void *player_thread_func(void *arg) { if (at_least_one_frame_seen) { if ((config.output->delay)) { if (config.no_sync == 0) { + if (config.output->rate_info) { + uint64_t elapsed_play_time, frames_played; + conn->frame_rate_status = config.output->rate_info(&elapsed_play_time,&frames_played); + if (conn->frame_rate_status==0) { + uint64_t elapsed_play_time_microseconds_fp, elapsed_play_time_microseconds; + elapsed_play_time_microseconds_fp = elapsed_play_time * 1000000; + elapsed_play_time_microseconds = elapsed_play_time_microseconds_fp >> 32; + conn->frame_rate = frames_played*1000000.0/elapsed_play_time_microseconds; + } + } inform( "%*.1f," /* Sync error in milliseconds */ "%*.1f," /* net correction in ppm */ @@ -2300,14 +2318,15 @@ void *player_thread_func(void *arg) { "%*llu," /* resend requests */ "%*lli," /* min DAC queue size */ "%*d," /* min buffer occupancy */ - "%*d", /* max buffer occupancy */ + "%*d," /* max buffer occupancy */ + "%*.2f", /* frame rate */ 10, 1000 * moving_average_sync_error / config.output_rate, 10, moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), 10, moving_average_insertions_plus_deletions * 1000000 / (352 * conn->output_sample_ratio), 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, - 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy); + 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy, 10, conn->frame_rate); } else { inform("%*.1f," /* Sync error in milliseconds */ "%*d," /* total packets */ diff --git a/player.h b/player.h index 99dcecb8..c92b4889 100644 --- a/player.h +++ b/player.h @@ -86,9 +86,15 @@ typedef struct { pthread_t thread, timer_requester, rtp_audio_thread, rtp_control_thread, rtp_timing_thread; // pthread_t *ptp; + // buffers to delete on exit signed short *tbuf; int32_t *sbuf; char *outbuf; + + // for holding the rate information until printed out at the end of a session + double frame_rate; + int frame_rate_status; + pthread_t *player_thread; abuf_t audio_buffer[BUFFER_FRAMES]; From c03f6d8133542933cd69849decadaa38a1dcc0d6 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 29 Jul 2018 08:35:06 +0100 Subject: [PATCH 032/569] Ensure flush resets everything. --- audio_alsa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_alsa.c b/audio_alsa.c index 556fd957..cefd6665 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -949,7 +949,7 @@ static void flush(void) { // flush also closes the device if ((derr = snd_pcm_close(alsa_handle))) debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr)); - + time_of_first_frame = 0; alsa_handle = NULL; } debug_mutex_unlock(&alsa_mutex, 3); From 17793bd0005114b6e8546b7d2decd6feb69f3c8b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 29 Jul 2018 09:47:53 +0100 Subject: [PATCH 033/569] Change measurement of frames and timing to mimimise effects of paging, cache loading, asynchronous access to the DAC. --- audio_alsa.c | 76 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index cefd6665..b122a63f 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -113,8 +113,15 @@ int alsa_characteristics_already_listed = 0; static snd_pcm_uframes_t period_size_requested, buffer_size_requested; static int set_period_size_request, set_buffer_size_request; -static uint64_t time_of_first_frame; +static uint64_t measurement_start_time; +static uint64_t frames_played_at_measurement_start_time; + +static uint64_t measurement_time; +static uint64_t frames_played_at_measurement_time; + static uint64_t frames_sent_for_playing; +static uint64_t frame_index; +static int measurement_data_is_valid; static void help(void) { printf(" -d output-device set the output device [default*|...]\n" @@ -817,8 +824,8 @@ static void start(int i_sample_rate, int i_sample_format) { else sample_format = i_sample_format; - time_of_first_frame = 0; - frames_sent_for_playing = 0; + frame_index = 0; + measurement_data_is_valid = 0; } int delay(long *the_delay) { @@ -839,7 +846,9 @@ int delay(long *the_delay) { snd_pcm_recover(alsa_handle, reply, 1); } } else { - time_of_first_frame = 0; + frame_index = 0; //we'll be starting over... + measurement_data_is_valid = 0; + if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) { *the_delay = 0; reply = 0; // no error @@ -866,13 +875,14 @@ int delay(long *the_delay) { } int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) { - long frames_left = 0; - int result = delay(&frames_left); - if (result >= 0) { - *elapsed_time = get_absolute_time_in_fp()-time_of_first_frame; - *frames_played = frames_sent_for_playing - frames_left; + if (measurement_data_is_valid) { + *elapsed_time = measurement_time - measurement_start_time; + *frames_played = frames_played_at_measurement_time - frames_played_at_measurement_start_time; + } else { + *elapsed_time = 0; + *frames_played = 0; } - return result; + return 0; } static void play(void *buf, int samples) { @@ -892,13 +902,14 @@ static void play(void *buf, int samples) { if (ret == 0) { debug_mutex_lock(&alsa_mutex, 10000, 1); // snd_pcm_sframes_t current_delay = 0; - int err; + int err, err2; if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) { if ((err = snd_pcm_prepare(alsa_handle))) { snd_pcm_recover(alsa_handle, err, 1); debug(1, "Error preparing after underrun: \"%s\".", snd_strerror(err)); } - time_of_first_frame = 0; + frame_index = 0; // we'll be starting over + measurement_data_is_valid = 0; } if ((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) || (snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING)) { @@ -908,16 +919,41 @@ static void play(void *buf, int samples) { debug(1, "empty buffer being passed to pcm_writei -- skipping it"); if ((samples != 0) && (buf != NULL)) { err = alsa_pcm_write(alsa_handle, buf, samples); - if (time_of_first_frame == 0) { - time_of_first_frame = get_absolute_time_in_fp(); - frames_sent_for_playing = 0; - } if (err < 0) { + frame_index = 0; + measurement_data_is_valid = 0; debug(1, "Error %d writing %d samples in play(): \"%s\".", err, samples, snd_strerror(err)); snd_pcm_recover(alsa_handle, err, 1); } - frames_sent_for_playing += samples; + if (frame_index==0) { + frames_sent_for_playing = samples; + } else { + frames_sent_for_playing += samples; + } + const uint64_t start_measurement_from_this_frame = (2*44100)/352; // two seconds of frames… + frame_index++; + if ((frame_index == start_measurement_from_this_frame) || (frame_index % 32 == 0)) { + long fl = 0; + err2 = snd_pcm_delay(alsa_handle,&fl); + if (err2 != 0) { + frame_index = 0; + measurement_data_is_valid = 0; + debug(1, "Error %d in delay(): \"%s\". Delay reported is %d frames.", err2, + snd_strerror(err2), fl); + snd_pcm_recover(alsa_handle, err2, 1); + } + uint64_t tf = get_absolute_time_in_fp(); + frames_played_at_measurement_time = frames_sent_for_playing - fl; + if (frame_index == start_measurement_from_this_frame) { + frames_played_at_measurement_start_time = frames_played_at_measurement_time; + measurement_start_time = tf; + } else { + frames_played_at_measurement_time = frames_played_at_measurement_time; + measurement_time = tf; + measurement_data_is_valid = 1; + } + } } } else { debug(1, "Error -- ALSA device in incorrect state (%d) for play.", @@ -926,7 +962,8 @@ static void play(void *buf, int samples) { snd_pcm_recover(alsa_handle, err, 1); debug(1, "Error preparing after play error: \"%s\".", snd_strerror(err)); } - time_of_first_frame = 0; + frame_index = 0; + measurement_data_is_valid = 0; } debug_mutex_unlock(&alsa_mutex, 3); } @@ -949,7 +986,8 @@ static void flush(void) { // flush also closes the device if ((derr = snd_pcm_close(alsa_handle))) debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr)); - time_of_first_frame = 0; + frame_index = 0; + measurement_data_is_valid = 0; alsa_handle = NULL; } debug_mutex_unlock(&alsa_mutex, 3); From c91a991edda4a6ea17c5333b6312c637e72b558c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 29 Jul 2018 10:45:55 +0100 Subject: [PATCH 034/569] Prevent arithmetic overflow after 2^32 microseconds, i.e. about one hoour and 11 minutes... --- audio_alsa.c | 3 +-- player.c | 5 +---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index b122a63f..01884362 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -910,8 +910,7 @@ static void play(void *buf, int samples) { } frame_index = 0; // we'll be starting over measurement_data_is_valid = 0; - } - if ((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) || + } else if ((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) || (snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING)) { if (buf == NULL) debug(1, "NULL buffer passed to pcm_writei -- skipping it"); diff --git a/player.c b/player.c index 1a457334..3356cb67 100644 --- a/player.c +++ b/player.c @@ -2301,10 +2301,7 @@ void *player_thread_func(void *arg) { uint64_t elapsed_play_time, frames_played; conn->frame_rate_status = config.output->rate_info(&elapsed_play_time,&frames_played); if (conn->frame_rate_status==0) { - uint64_t elapsed_play_time_microseconds_fp, elapsed_play_time_microseconds; - elapsed_play_time_microseconds_fp = elapsed_play_time * 1000000; - elapsed_play_time_microseconds = elapsed_play_time_microseconds_fp >> 32; - conn->frame_rate = frames_played*1000000.0/elapsed_play_time_microseconds; + conn->frame_rate = 1.0*(frames_played*(uint64_t)0x100000000)/elapsed_play_time; } } inform( From ea20840d6c4f493f3d8c1c93a0eaa9ca671123ec Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 29 Jul 2018 13:45:42 +0100 Subject: [PATCH 035/569] Make the play function return a status (ignored for the present). Clean up audio_alsa by removing all the die calls and placing the alsa_mutex calls in pthread cleanup routines. --- audio.h | 2 +- audio_alsa.c | 342 ++++++++++++++++++++++++++---------------------- audio_ao.c | 4 +- audio_dummy.c | 2 +- audio_pa.c | 3 +- audio_pipe.c | 3 +- audio_sndio.c | 5 +- audio_soundio.c | 3 +- audio_stdout.c | 3 +- common.c | 4 + common.h | 5 + 11 files changed, 207 insertions(+), 169 deletions(-) diff --git a/audio.h b/audio.h index 2a15587c..323dbc4f 100644 --- a/audio.h +++ b/audio.h @@ -22,7 +22,7 @@ typedef struct { void (*start)(int sample_rate, int sample_format); // block of samples - void (*play)(void *buf, int samples); + int (*play)(void *buf, int samples); void (*stop)(void); // may be null if not implemented diff --git a/audio_alsa.c b/audio_alsa.c index 01884362..8fd18c37 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -41,7 +41,7 @@ static void help(void); static int init(int argc, char **argv); static void deinit(void); static void start(int i_sample_rate, int i_sample_format); -static void play(void *buf, int samples); +static int play(void *buf, int samples); static void stop(void); static void flush(void); int delay(long *the_delay); @@ -134,6 +134,7 @@ static void help(void) { void set_alsa_out_dev(char *dev) { alsa_out_dev = dev; } int open_mixer() { + int response = 0; if (hardware_mixer) { debug(3, "Open Mixer"); int ret = 0; @@ -141,25 +142,38 @@ int open_mixer() { snd_mixer_selem_id_set_index(alsa_mix_sid, alsa_mix_index); snd_mixer_selem_id_set_name(alsa_mix_sid, alsa_mix_ctrl); - if ((snd_mixer_open(&alsa_mix_handle, 0)) < 0) - die("Failed to open mixer"); - debug(3, "Mixer device name is \"%s\".", alsa_mix_dev); - if ((snd_mixer_attach(alsa_mix_handle, alsa_mix_dev)) < 0) - die("Failed to attach mixer"); - if ((snd_mixer_selem_register(alsa_mix_handle, NULL, NULL)) < 0) - die("Failed to register mixer element"); - - ret = snd_mixer_load(alsa_mix_handle); - if (ret < 0) - die("Failed to load mixer element"); - debug(3, "Mixer Control name is \"%s\".", alsa_mix_ctrl); - alsa_mix_elem = snd_mixer_find_selem(alsa_mix_handle, alsa_mix_sid); - if (!alsa_mix_elem) - die("Failed to find mixer element"); - return 1; - } else { - return 0; - } + if ((snd_mixer_open(&alsa_mix_handle, 0)) < 0) { + debug(1,"Failed to open mixer"); + response = -1; + } else { + debug(3, "Mixer device name is \"%s\".", alsa_mix_dev); + if ((snd_mixer_attach(alsa_mix_handle, alsa_mix_dev)) < 0) { + debug(1,"Failed to attach mixer"); + response = -2; + } else { + if ((snd_mixer_selem_register(alsa_mix_handle, NULL, NULL)) < 0) { + debug(1,"Failed to register mixer element"); + response = -3; + } else { + ret = snd_mixer_load(alsa_mix_handle); + if (ret < 0) { + debug(1,"Failed to load mixer element"); + response = -4; + } else { + debug(3, "Mixer Control name is \"%s\".", alsa_mix_ctrl); + alsa_mix_elem = snd_mixer_find_selem(alsa_mix_handle, alsa_mix_sid); + if (!alsa_mix_elem) { + debug(1,"Failed to find mixer element"); + response = -5; + } else { + response = 1; // we found a hardware mixer and successfully opened it + } + } + } + } + } + } + return response; } void close_mixer() { @@ -179,8 +193,8 @@ void do_snd_mixer_selem_set_playback_dB_all(snd_mixer_elem_t *mix_elem, double v } static int init(int argc, char **argv) { - debug_mutex_lock(&alsa_mutex, 1000, 1); // debug(2,"audio_alsa init called."); + int response = 0; // this will be what we return to the caller. const char *str; int value; // double dvalue; @@ -232,8 +246,8 @@ static int init(int argc, char **argv) { else if (strcasecmp(str, "yes") == 0) config.no_sync = 1; else { - debug_mutex_unlock(&alsa_mutex, 3); - die("Invalid disable_synchronization option choice \"%s\". It should be \"yes\" or \"no\""); + warn("Invalid disable_synchronization option choice \"%s\". It should be \"yes\" or \"no\". It is set to \"no\"."); + config.no_sync = 0; } } @@ -246,9 +260,9 @@ static int init(int argc, char **argv) { else if (strcasecmp(str, "yes") == 0) config.alsa_use_hardware_mute = 1; else { - debug_mutex_unlock(&alsa_mutex, 3); - die("Invalid mute_using_playback_switch option choice \"%s\". It should be \"yes\" or " - "\"no\""); + warn("Invalid mute_using_playback_switch option choice \"%s\". It should be \"yes\" or " + "\"no\". It is set to \"no\"."); + config.alsa_use_hardware_mute = 0; } } @@ -259,9 +273,9 @@ static int init(int argc, char **argv) { else if (strcasecmp(str, "yes") == 0) config.alsa_use_hardware_mute = 1; else { - debug_mutex_unlock(&alsa_mutex, 3); - die("Invalid use_hardware_mute_if_available option choice \"%s\". It should be \"yes\" or " - "\"no\""); + warn("Invalid use_hardware_mute_if_available option choice \"%s\". It should be \"yes\" or " + "\"no\". It is set to \"no\"."); + config.alsa_use_hardware_mute = 0; } } @@ -282,11 +296,11 @@ static int init(int argc, char **argv) { else if (strcasecmp(str, "S8") == 0) config.output_format = SPS_FORMAT_S8; else { - debug_mutex_unlock(&alsa_mutex, 3); - die("Invalid output format \"%s\". It should be \"U8\", \"S8\", \"S16\", \"S24\", " + warn("Invalid output format \"%s\". It should be \"U8\", \"S8\", \"S16\", \"S24\", " "\"S24_3LE\", \"S24_3BE\" or " - "\"S32\"", + "\"S32\". It is set to \"S16\".", str); + config.output_format = SPS_FORMAT_S16; } } @@ -301,8 +315,8 @@ static int init(int argc, char **argv) { config.output_rate = value; break; default: - debug_mutex_unlock(&alsa_mutex, 3); - die("Invalid output rate \"%d\". It should be a multiple of 44,100 up to 352,800", value); + warn("Invalid output rate \"%d\". It should be a multiple of 44,100 up to 352,800. It is set to 44,100", value); + config.output_rate = 44100; } } @@ -313,8 +327,8 @@ static int init(int argc, char **argv) { else if (strcasecmp(str, "yes") == 0) config.no_mmap = 0; else { - debug_mutex_unlock(&alsa_mutex, 3); - die("Invalid use_mmap_if_available option choice \"%s\". It should be \"yes\" or \"no\""); + warn("Invalid use_mmap_if_available option choice \"%s\". It should be \"yes\" or \"no\". It is set to \"yes\"."); + config.no_mmap = 0; } } /* Get the optional period size value */ @@ -322,10 +336,10 @@ static int init(int argc, char **argv) { set_period_size_request = 1; debug(1, "Value read for period size is %d.", value); if (value < 0) { - debug_mutex_unlock(&alsa_mutex, 3); - die("Invalid alsa period size setting \"%d\". It " - "must be greater than 0.", + warn("Invalid alsa period size setting \"%d\". It " + "must be greater than 0. No setting is made.", value); + set_period_size_request = 0; } else { period_size_requested = value; } @@ -336,10 +350,10 @@ static int init(int argc, char **argv) { set_buffer_size_request = 1; debug(1, "Value read for buffer size is %d.", value); if (value < 0) { - debug_mutex_unlock(&alsa_mutex, 3); - die("Invalid alsa buffer size setting \"%d\". It " - "must be greater than 0.", + warn("Invalid alsa buffer size setting \"%d\". It " + "must be greater than 0. No setting is made.", value); + set_buffer_size_request = 0; } else { buffer_size_requested = value; } @@ -374,15 +388,13 @@ static int init(int argc, char **argv) { alsa_mix_index = strtol(optarg, NULL, 10); break; default: + warn("Invalid audio option \"-%c\" specified -- ignored.", opt); help(); - debug_mutex_unlock(&alsa_mutex, 3); - die("Invalid audio option -%c specified", opt); } } if (optind < argc) { - debug_mutex_unlock(&alsa_mutex, 3); - die("Invalid audio argument: %s", argv[optind]); + warn("Invalid audio argument: \"%s\" -- ignored", argv[optind]); } debug(1, "alsa output device name is \"%s\".", alsa_out_dev); @@ -392,92 +404,93 @@ static int init(int argc, char **argv) { if (alsa_mix_dev == NULL) alsa_mix_dev = alsa_out_dev; - // Open mixer + // Now, start trying to initialise the alsa device with the settings obtained + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 1000, 1); + if (open_mixer()==1) { + if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) < + 0) + debug(1, "Can't read mixer's [linear] min and max volumes."); + else { + if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb, &alsa_mix_maxdb) == + 0) { - open_mixer(); - - if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) < - 0) - debug(1, "Can't read mixer's [linear] min and max volumes."); - else { - if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb, &alsa_mix_maxdb) == - 0) { - - audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff - audio_alsa.parameters = ¶meters; // likewise the parameters stuff - if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) { - // For instance, the Raspberry Pi does this - debug(1, "Lowest dB value is a mute"); - mixer_volume_setting_gives_mute = 1; - alsa_mix_mute = SND_CTL_TLV_DB_GAIN_MUTE; // this may not be necessary -- it's always - // going to be SND_CTL_TLV_DB_GAIN_MUTE, right? - // debug(1, "Try minimum volume + 1 as lowest true attenuation value"); - if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1, - &alsa_mix_mindb) != 0) - debug(1, "Can't get dB value corresponding to a minimum volume + 1."); - } - debug(1, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0, - (1.0 * alsa_mix_maxdb) / 100.0); - } else { - // use the linear scale and do the db conversion ourselves - debug(1, - "note: the hardware mixer specified -- \"%s\" -- does not have " - "a dB volume scale.", - alsa_mix_ctrl); - - if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) { - debug_mutex_unlock(&alsa_mutex, 3); - die("Cannot open control \"%s\"", alsa_mix_dev); - } - if (snd_ctl_elem_id_malloc(&elem_id) < 0) { - debug_mutex_unlock(&alsa_mutex, 3); - die("Cannot allocate memory for control \"%s\"", alsa_mix_dev); - } - snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_MIXER); - snd_ctl_elem_id_set_name(elem_id, alsa_mix_ctrl); - - if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) { - debug(1, "Volume control \"%s\" has dB volume from %f to %f.", alsa_mix_ctrl, - (1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0); - has_softvol = 1; audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff audio_alsa.parameters = ¶meters; // likewise the parameters stuff + if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) { + // For instance, the Raspberry Pi does this + debug(1, "Lowest dB value is a mute"); + mixer_volume_setting_gives_mute = 1; + alsa_mix_mute = SND_CTL_TLV_DB_GAIN_MUTE; // this may not be necessary -- it's always + // going to be SND_CTL_TLV_DB_GAIN_MUTE, right? + // debug(1, "Try minimum volume + 1 as lowest true attenuation value"); + if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1, + &alsa_mix_mindb) != 0) + debug(1, "Can't get dB value corresponding to a minimum volume + 1."); + } + debug(1, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0, + (1.0 * alsa_mix_maxdb) / 100.0); } else { - debug(1, "Cannot get the dB range from the volume control \"%s\"", alsa_mix_ctrl); - } + // use the linear scale and do the db conversion ourselves + warn("The hardware mixer specified -- \"%s\" -- does not have " + "a dB volume scale.", + alsa_mix_ctrl); - /* - debug(1, "Min and max volumes are %d and - %d.",alsa_mix_minv,alsa_mix_maxv); - alsa_mix_maxdb = 0; - if ((alsa_mix_maxv!=0) && (alsa_mix_minv!=0)) - alsa_mix_mindb = - -20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0)); - else if (alsa_mix_maxv!=0) - alsa_mix_mindb = -20*100*log10(alsa_mix_maxv*1.0); - audio_alsa.volume = &linear_volume; // insert the linear volume function - audio_alsa.parameters = ¶meters; // likewise the parameters stuff - debug(1,"Max and min dB calculated are %d and - %d.",alsa_mix_maxdb,alsa_mix_mindb); - */ + if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) { + warn("Cannot open control \"%s\"", alsa_mix_dev); + response = -1; + } + if (snd_ctl_elem_id_malloc(&elem_id) < 0) { + debug(1,"Cannot allocate memory for control \"%s\"", alsa_mix_dev); + elem_id = NULL; + response = -2; + } else { + snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_MIXER); + snd_ctl_elem_id_set_name(elem_id, alsa_mix_ctrl); + + if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) { + debug(1, "Volume control \"%s\" has dB volume from %f to %f.", alsa_mix_ctrl, + (1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0); + has_softvol = 1; + audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff + audio_alsa.parameters = ¶meters; // likewise the parameters stuff + } else { + debug(1, "Cannot get the dB range from the volume control \"%s\"", alsa_mix_ctrl); + } + } + /* + debug(1, "Min and max volumes are %d and + %d.",alsa_mix_minv,alsa_mix_maxv); + alsa_mix_maxdb = 0; + if ((alsa_mix_maxv!=0) && (alsa_mix_minv!=0)) + alsa_mix_mindb = + -20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0)); + else if (alsa_mix_maxv!=0) + alsa_mix_mindb = -20*100*log10(alsa_mix_maxv*1.0); + audio_alsa.volume = &linear_volume; // insert the linear volume function + audio_alsa.parameters = ¶meters; // likewise the parameters stuff + debug(1,"Max and min dB calculated are %d and + %d.",alsa_mix_maxdb,alsa_mix_mindb); + */ + } } + if (((config.alsa_use_hardware_mute == 1) && + (snd_mixer_selem_has_playback_switch(alsa_mix_elem))) || + mixer_volume_setting_gives_mute) { + audio_alsa.mute = &mute; // insert the mute function now we know it can do muting stuff + // debug(1, "Has mixer and mute ability we will use."); + } else { + // debug(1, "Has mixer but not using hardware mute."); + } + close_mixer(); } - if (((config.alsa_use_hardware_mute == 1) && - (snd_mixer_selem_has_playback_switch(alsa_mix_elem))) || - mixer_volume_setting_gives_mute) { - audio_alsa.mute = &mute; // insert the mute function now we know it can do muting stuff - // debug(1, "Has mixer and mute ability we will use."); - } else { - // debug(1, "Has mixer but not using hardware mute."); - } - close_mixer(); + debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(0); // release the mutex } else { // debug(1, "Has no mixer and thus no hardware mute."); } alsa_mix_handle = NULL; - debug_mutex_unlock(&alsa_mutex, 3); - return 0; + return response; } static void deinit(void) { @@ -487,7 +500,6 @@ static void deinit(void) { int open_alsa_device(void) { // the alsa mutex is already acquired when this is called - const snd_pcm_uframes_t minimal_buffer_headroom = 352 * 2; // we accept this much headroom in the hardware buffer, but we'll // accept less @@ -515,17 +527,16 @@ int open_alsa_device(void) { ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, 0); if (ret < 0) - return (ret); + return (-10); snd_pcm_hw_params_alloca(&alsa_params); ret = snd_pcm_hw_params_any(alsa_handle, alsa_params); if (ret < 0) { - debug_mutex_unlock(&alsa_mutex, 3); - ; - die("audio_alsa: Broken configuration for device \"%s\": no configurations " + warn("audio_alsa: Broken configuration for device \"%s\": no configurations " "available", alsa_out_dev); + return -11; } if ((config.no_mmap == 0) && @@ -548,9 +559,9 @@ int open_alsa_device(void) { ret = snd_pcm_hw_params_set_access(alsa_handle, alsa_params, access); if (ret < 0) { - debug_mutex_unlock(&alsa_mutex, 3); - die("audio_alsa: Access type not available for device \"%s\": %s", alsa_out_dev, + warn("audio_alsa: Access type not available for device \"%s\": %s", alsa_out_dev, snd_strerror(ret)); + return -12; } snd_pcm_format_t sf; switch (sample_format) { @@ -576,28 +587,29 @@ int open_alsa_device(void) { sf = SND_PCM_FORMAT_S32; break; default: - debug_mutex_unlock(&alsa_mutex, 3); sf = SND_PCM_FORMAT_S16; // this is just to quieten a compiler warning - die("Unsupported output format at audio_alsa.c"); + debug(1,"Unsupported output format at audio_alsa.c"); + return -1; } ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sf); if (ret < 0) { - debug_mutex_unlock(&alsa_mutex, 3); - die("audio_alsa: Sample format %d not available for device \"%s\": %s", sample_format, + warn("audio_alsa: Sample format %d not available for device \"%s\": %s", sample_format, alsa_out_dev, snd_strerror(ret)); + return -2; } ret = snd_pcm_hw_params_set_channels(alsa_handle, alsa_params, 2); if (ret < 0) { - debug_mutex_unlock(&alsa_mutex, 3); - die("audio_alsa: Channels count (2) not available for device \"%s\": %s", alsa_out_dev, + warn("audio_alsa: Channels count (2) not available for device \"%s\": %s", alsa_out_dev, snd_strerror(ret)); + return -3; } ret = snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params, &my_sample_rate, &dir); if (ret < 0) { - die("audio_alsa: Rate %iHz not available for playback: %s", desired_sample_rate, + warn("audio_alsa: Rate %iHz not available for playback: %s", desired_sample_rate, snd_strerror(ret)); + return -4; } if (set_period_size_request != 0) { @@ -605,9 +617,10 @@ int open_alsa_device(void) { ret = snd_pcm_hw_params_set_period_size_near(alsa_handle, alsa_params, &period_size_requested, &dir); if (ret < 0) { - debug_mutex_unlock(&alsa_mutex, 3); - die("audio_alsa: cannot set period size of %lu: %s", period_size_requested, + warn("audio_alsa: cannot set period size of %lu: %s", period_size_requested, snd_strerror(ret)); + return -5; + } else { snd_pcm_uframes_t actual_period_size; snd_pcm_hw_params_get_period_size(alsa_params, &actual_period_size, &dir); if (actual_period_size != period_size_requested) @@ -621,35 +634,36 @@ int open_alsa_device(void) { debug(1, "Attempting to set the buffer size to %lu", buffer_size_requested); ret = snd_pcm_hw_params_set_buffer_size_near(alsa_handle, alsa_params, &buffer_size_requested); if (ret < 0) { - debug_mutex_unlock(&alsa_mutex, 3); - die("audio_alsa: cannot set buffer size of %lu: %s", buffer_size_requested, + warn("audio_alsa: cannot set buffer size of %lu: %s", buffer_size_requested, snd_strerror(ret)); + return -6; + } else { + snd_pcm_uframes_t actual_buffer_size; + snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_size); + if (actual_buffer_size != buffer_size_requested) + inform("Actual period size set to a different value than requested. Requested: %lu, actual " + "setting: %lu", + buffer_size_requested, actual_buffer_size); } - snd_pcm_uframes_t actual_buffer_size; - snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_size); - if (actual_buffer_size != buffer_size_requested) - inform("Actual period size set to a different value than requested. Requested: %lu, actual " - "setting: %lu", - buffer_size_requested, actual_buffer_size); } ret = snd_pcm_hw_params(alsa_handle, alsa_params); if (ret < 0) { - debug_mutex_unlock(&alsa_mutex, 3); - die("audio_alsa: Unable to set hw parameters for device \"%s\": %s.", alsa_out_dev, + warn("audio_alsa: Unable to set hw parameters for device \"%s\": %s.", alsa_out_dev, snd_strerror(ret)); + return -7; } if (my_sample_rate != desired_sample_rate) { - debug_mutex_unlock(&alsa_mutex, 3); - die("Can't set the D/A converter to %d.", desired_sample_rate); + warn("Can't set the D/A converter to %d.", desired_sample_rate); + return -8; } ret = snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_length); if (ret < 0) { - debug_mutex_unlock(&alsa_mutex, 3); - die("audio_alsa: Unable to get hw buffer length for device \"%s\": %s.", alsa_out_dev, + warn("audio_alsa: Unable to get hw buffer length for device \"%s\": %s.", alsa_out_dev, snd_strerror(ret)); + return -9; } if (actual_buffer_length < config.audio_backend_buffer_desired_length + minimal_buffer_headroom) { @@ -809,7 +823,7 @@ int open_alsa_device(void) { } } - return (0); + return 0; } static void start(int i_sample_rate, int i_sample_format) { @@ -835,7 +849,7 @@ int delay(long *the_delay) { if (alsa_handle == NULL) { return -ENODEV; } else { - debug_mutex_lock(&alsa_mutex, 10000, 1); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); int derr; if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING) { *the_delay = 0; // just to see what happens @@ -867,6 +881,7 @@ int delay(long *the_delay) { } } debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(0); // here, occasionally pretend there's a problem with pcm_get_delay() // if ((random() % 100000) < 3) // keep it pretty rare // reply = -EPERM; // pretend something bad has happened @@ -885,11 +900,11 @@ int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) { return 0; } -static void play(void *buf, int samples) { +static int play(void *buf, int samples) { // debug(3,"audio_alsa play called."); int ret = 0; if (alsa_handle == NULL) { - debug_mutex_lock(&alsa_mutex, 10000, 1); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); ret = open_alsa_device(); if (ret == 0) { if (audio_alsa.volume) @@ -897,10 +912,12 @@ static void play(void *buf, int samples) { if (audio_alsa.mute) do_mute(0); } + debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(0); // release the mutex } if (ret == 0) { - debug_mutex_lock(&alsa_mutex, 10000, 1); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); // snd_pcm_sframes_t current_delay = 0; int err, err2; if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) { @@ -965,12 +982,14 @@ static void play(void *buf, int samples) { measurement_data_is_valid = 0; } debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(0); // release the mutex } + return ret; } static void flush(void) { // debug(2,"audio_alsa flush called."); - debug_mutex_lock(&alsa_mutex, 10000, 1); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); int derr; do_mute(1); @@ -990,6 +1009,7 @@ static void flush(void) { alsa_handle = NULL; } debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(0); // release the mutex } static void stop(void) { @@ -1010,7 +1030,7 @@ static void parameters(audio_parameters *info) { void do_volume(double vol) { // caller is assumed to have the alsa_mutex when using this function debug(3, "Setting volume db to %f.", vol); set_volume = vol; - if (volume_set_request && open_mixer()) { + if (volume_set_request && (open_mixer()==1)) { if (has_softvol) { if (ctl && elem_id) { snd_ctl_elem_value_t *value; @@ -1042,10 +1062,11 @@ void do_volume(double vol) { // caller is assumed to have the alsa_mutex when us } void volume(double vol) { - debug_mutex_lock(&alsa_mutex, 1000, 1); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 1000, 1); volume_set_request = 1; // an external request has been made to set the volume do_volume(vol); debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(0); // release the mutex } /* @@ -1068,11 +1089,12 @@ static void linear_volume(double vol) { static void mute(int mute_state_requested) { // debug(1,"External Mute Request: %d",mute_state_requested); - debug_mutex_lock(&alsa_mutex, 10000, 1); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); mute_request_pending = 1; overriding_mute_state_requested = mute_state_requested; do_mute(mute_state_requested); debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(0); // release the mutex } void do_mute(int mute_state_requested) { @@ -1093,7 +1115,7 @@ void do_mute(int mute_state_requested) { if (config.alsa_use_hardware_mute == 1) { if (mute_request_pending == 0) local_mute_state_requested = mute_state_requested; - if (open_mixer()) { + if (open_mixer()==1) { if (local_mute_state_requested) { // debug(1,"Playback Switch mute actually done"); if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) diff --git a/audio_ao.c b/audio_ao.c index 8a78e880..de8861e8 100644 --- a/audio_ao.c +++ b/audio_ao.c @@ -121,7 +121,9 @@ static void deinit(void) { static void start(__attribute__((unused)) int sample_rate, __attribute__((unused)) int sample_format) {} -static void play(void *buf, int samples) { ao_play(dev, buf, samples * 4); } +static int play(void *buf, int samples) { + return ao_play(dev, buf, samples * 4); +} static void stop(void) {} diff --git a/audio_dummy.c b/audio_dummy.c index 5f55bab1..e29d389b 100644 --- a/audio_dummy.c +++ b/audio_dummy.c @@ -48,7 +48,7 @@ static void start(int sample_rate, __attribute__((unused)) int sample_format) { debug(1, "dummy audio output started at Fs=%d Hz\n", sample_rate); } -static void play(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) {} +static int play(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) { return 0; } static void stop(void) { debug(1, "dummy audio stopped\n"); } diff --git a/audio_pa.c b/audio_pa.c index ac1570e0..aa4251bd 100644 --- a/audio_pa.c +++ b/audio_pa.c @@ -198,7 +198,7 @@ static void start(__attribute__((unused)) int sample_rate, pa_threaded_mainloop_unlock(mainloop); } -static void play(void *buf, int samples) { +static int play(void *buf, int samples) { // debug(1,"pa_play of %d samples.",samples); // copy the samples into the queue size_t bytes_to_transfer = samples * 2 * 2; @@ -224,6 +224,7 @@ static void play(void *buf, int samples) { pa_stream_cork(stream, 0, stream_success_cb, mainloop); pa_threaded_mainloop_unlock(mainloop); } + return 0; } int pa_delay(long *the_delay) { diff --git a/audio_pipe.c b/audio_pipe.c index c080277d..057a975f 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -54,7 +54,7 @@ static void start(__attribute__((unused)) int sample_rate, } } -static void play(void *buf, int samples) { +static int play(void *buf, int samples) { // if the file is not open, try to open it. char errorstring[1024]; if (fd == -1) { @@ -73,6 +73,7 @@ static void play(void *buf, int samples) { warn("Error %d opening the pipe named \"%s\": \"%s\".", errno, pipename, errorstring); warned = 1; } + return warned; } static void stop(void) { diff --git a/audio_sndio.c b/audio_sndio.c index d9cef65e..203a7dbc 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -33,7 +33,7 @@ static int init(int, char **); static void onmove_cb(void *, int); static void deinit(void); static void start(int, int); -static void play(void *, int); +static int play(void *, int); static void stop(void); static void onmove_cb(void *, int); static int delay(long *); @@ -222,12 +222,13 @@ static void start(__attribute__((unused)) int sample_rate, pthread_mutex_unlock(&sndio_mutex); } -static void play(void *buf, int frames) { +static int play(void *buf, int frames) { if (frames > 0) { pthread_mutex_lock(&sndio_mutex); written += sio_write(hdl, buf, frames * framesize); pthread_mutex_unlock(&sndio_mutex); } + return 0; } static void stop() { diff --git a/audio_soundio.c b/audio_soundio.c index c744c3c1..08eec773 100644 --- a/audio_soundio.c +++ b/audio_soundio.c @@ -168,7 +168,7 @@ static void start(int sample_rate, int sample_format) { debug(1, "libsoundio output started\n"); } -static void play(void *buf, int samples) { +static int play(void *buf, int samples) { // int err; int free_bytes = soundio_ring_buffer_free_count(ring_buffer); int written_bytes = 0; @@ -187,6 +187,7 @@ static void play(void *buf, int samples) { soundio_ring_buffer_advance_write_ptr(ring_buffer, write_bytes); debug(3, "[<<---] Written to buffer : %d\n", written_bytes); } + return 0; } static void parameters(audio_parameters *info) { diff --git a/audio_stdout.c b/audio_stdout.c index 3e27ad15..72f10c85 100644 --- a/audio_stdout.c +++ b/audio_stdout.c @@ -43,7 +43,7 @@ static void start(__attribute__((unused)) int sample_rate, fd = STDOUT_FILENO; } -static void play(void *buf, int samples) { +static int play(void *buf, int samples) { char errorstring[1024]; int warned = 0; int rc = write(fd, buf, samples * 4); @@ -52,6 +52,7 @@ static void play(void *buf, int samples) { warn("Error %d writing to stdout: \"%s\".", errno, errorstring); warned = 1; } + return rc; } static void stop(void) { diff --git a/common.c b/common.c index f43c207f..cd3d5139 100644 --- a/common.c +++ b/common.c @@ -1141,6 +1141,10 @@ int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *filename, const int return r; } +void pthread_cleanup_debug_mutex_unlock(void *arg) { + pthread_mutex_unlock((pthread_mutex_t* )arg); +} + char *get_version_string() { char *version_string = malloc(200); if (version_string) { diff --git a/common.h b/common.h index f0a2031d..51eb29e6 100644 --- a/common.h +++ b/common.h @@ -298,6 +298,11 @@ int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *filename, const int #define debug_mutex_unlock(mu, d) _debug_mutex_unlock(mu, __FILE__, __LINE__, d) + +void pthread_cleanup_debug_mutex_unlock(void *arg); + +#define pthread_cleanup_debug_mutex_lock(mu, t, d) if (_debug_mutex_lock(mu, t, __FILE__, __LINE__, d) == 0) pthread_cleanup_push(pthread_cleanup_debug_mutex_unlock,(void *)mu) + char *get_version_string(); // mallocs a string space -- remember to free it afterwards void sps_nanosleep(const time_t sec, From 6c7b26a63335aed17cc510a1e5c87e9d0ac9144a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 29 Jul 2018 15:33:42 +0100 Subject: [PATCH 036/569] Add debug message to try to understand a weird negagive memory request. --- player.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/player.c b/player.c index 3356cb67..a3739d0b 100644 --- a/player.c +++ b/player.c @@ -2052,12 +2052,14 @@ void *player_thread_func(void *arg) { silence_length = filler_length * 5; char *long_silence = malloc(conn->output_bytes_per_frame * silence_length); - if (long_silence == NULL) - die("Failed to allocate memory for a long_silence buffer of %d frames.", - silence_length); - memset(long_silence, 0, conn->output_bytes_per_frame * silence_length); - config.output->play(long_silence, silence_length); - free(long_silence); + if (long_silence) { + memset(long_silence, 0, conn->output_bytes_per_frame * silence_length); + config.output->play(long_silence, silence_length); + free(long_silence); + } else { + warn("Failed to allocate memory for a long_silence buffer of %d frames for a sync error of %" PRId64 " frames.", + silence_length, sync_error); + } } } else { From 4ea9de7b8bdc4c9af6c978411d9288b1eed50f25 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jul 2018 08:57:42 +0100 Subject: [PATCH 037/569] Move car setup alternative to an issue thread --- CAR SETUP ALTERNATIVE APPROACH.md | 92 ------------------------------- 1 file changed, 92 deletions(-) delete mode 100644 CAR SETUP ALTERNATIVE APPROACH.md diff --git a/CAR SETUP ALTERNATIVE APPROACH.md b/CAR SETUP ALTERNATIVE APPROACH.md deleted file mode 100644 index bd128af4..00000000 --- a/CAR SETUP ALTERNATIVE APPROACH.md +++ /dev/null @@ -1,92 +0,0 @@ -# From [FnasBas](https://github.com/FnasBas) -This is based on [this comment](https://github.com/mikebrady/shairport-sync/issues/482#issuecomment-402543454). - -Here's a starting point. I hope it is useful to someone :) - -# Alternative car setup approach -This is offered in the hopes it will be useful to someone. The background is that, besides all the advantages of WiFi/AirPlay over Bluetooth, -when travelling my phone is my only internet connection, and I want to be able to share it with other devices. -Like an iPad or laptop. I may also want to be able to send audio to the car Shairport Sync service from my other devices while -simultaneously having internet access on them, via the phone. - -So the basic idea is just to turn the table on which device acts as the Access Point for the WiFi. Which works perfectly well. -The prerequisite is that your carrier/contract allows tethering, as otherwise you won't be able to have your phone act as a WiFi Access Point. -I also think this is a bit simpler to set up as you don't need to set up `hostapd` and a DHCP server on the Pi. -The drawback is that you need to do a gesture and tap a button on the phone to make its network discoverable when entering your car. - -## Phone configuration -Enable tethering on your iPhone with a secure passphrase. - -## Raspberry Pi configuration -Set up Shairport Sync according to your needs and instructions found elsewhere. - -### Network -Configure your Raspberry Pi with only one WiFi network. Through experimentation I found that if I was in the vicinity of my home network, -the Pi would not reliably choose the iPhone's WiFi network even though it was prioritized over the home network. -And it seems `wpa-supplicant` in general is a bit difficult to get to roam between different WiFi networks once it's decided on one. - -Example of `/etc/wpa_supplicant/wpa_supplicant.conf` - -``` -ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev -update_config=1 -country=SE -network={ - priority=3 - ssid="My unique clever wifi network name" - psk="My very secure wifi key" -} -``` - -### Read only root filesystem -By configuring your Pi to have a read only root filesystem, you need not ever worry about an improper shutdown of the OS -causing the Pi to fail to boot. You will also be sure that the Pi always boots to a predetermined state. -And you will eleminate wear on the SD card completely except for when modifying the system. -This will result in a very reliable AirPlay service. - -These instructions are a bit old but a good starting point: (https://openenergymonitor.org/forum-archive/node/5331.html) - -In addition to what's laid out there I've also: - -* Added the `/boot` fs to be mounted read-only and modified the rpi-rw/ro scripts and `/etc/fstab` accordingly -* in `/etc/systemd/journald.conf`, set `storage=volatile` -* As the `systemd` `ntp` client is hard-coded to use a specific path for its book keeping, I've disabled that and installed `ntpd` -and set the drift file to `/tmp/drift` -* Your DHCP client will complain that it can't write its lease to a file. You can work around this by setting it to write its lease -to a file in `/tmp` or disregard it as it will work fine in any case. - -In my case I do not use a car stereo but go straight Pi>Dac>Power amplifier. So I've also set up EQ with `alsa`, and a relay module to a -GPIO pin and some scripts that run on session start/stop that turns the power amplifier on and off accordingly. -But this could turn in to "How I set up my car audio" rather than offering an alternative to [what's currently suggested -for a car installation](https://github.com/mikebrady/shairport-sync/blob/master/CAR%20INSTALL.md). -So I stop. ;) - -## Using it -Make your phone's network discoverable. In iOS 11 there's a shortcut for this – swipe up and hold down on the connectivity section. -There's a button to turn on Internet Sharing. -In my experiments the Pi connects almost instantaneously to this network and a few moments later the AirPlay service offered by Shairport Sync -will become visible. - -## Changing and Maintaining it -Make your phone network discoverable. The Pi connects. Connect your laptop to the same network. -The iPhone does not appear to resolve host names from one tethered device to another so you need to `ssh` to the Pi's IP-address. -There's a number of ways to find the Pi's IP-address: - -* You can check your laptop's IP and try subtracting or adding from the last octet -* Use a `zeroconf/avahi` browser tool and locate the AirPlay service and find the IP address that way -* Use `nmap` or other similar utility to scan for the Pi - -`ssh` to the Pi -``` -rpi-rw -change stuff -rpi-ro -``` - -### Further thinking -Other Apple devices can wake the WiFi Access Point on the iPhone. My conclusion is that this must be accomplished via Bluetooth, -but Apple doesn't seem to be open spec on this subject as all I've been able to find is that certain devices, when paired with Bluetooth, -connected to iCloud etc. etc. will offer this functionality. -Being able to reverse-engineer what Bluetooth communication is actually done in order to turn on the WiFi Access Point on the phone -would mitigate the drawback of this method, as the Paspberry Pi could be constantly looking for the phone via Bluetooth and -enabling Internet Sharing that way. From ac8afe0ab2e81c09278cff15da781275a08aa593 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jul 2018 10:30:00 +0100 Subject: [PATCH 038/569] Remove an incorrect warning about using the "general" "statistics" setting. --- shairport.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shairport.c b/shairport.c index 5ce011f2..f07ad52c 100644 --- a/shairport.c +++ b/shairport.c @@ -464,7 +464,7 @@ int parse_options(int argc, char **argv) { } /* Get the statistics setting. */ - if (!config_set_lookup_bool(config.cfg, "general.statistics", + if (config_set_lookup_bool(config.cfg, "general.statistics", &(config.statistics_requested))) { warn("The \"general\" \"statistics\" setting is deprecated. Please use the \"diagnostics\" " "\"statistics\" setting instead."); From a5dd76d22369d130f9cb46a5e732bd77a01da8dd Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jul 2018 10:31:21 +0100 Subject: [PATCH 039/569] Stop using SIGUSR1 for cancelling threads, use pthread_cancel and friends instead, fix some memory leaks, add accurate ouput rate calculation to statistics, add "quit" verb to MPRIS and native d-bus interfaces. Remove incorrect warning, quiten some debug messages. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index f047fc15..8650a1a0 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.2d67], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d0], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 15c80b7a93eb81d88d3146f52ebc5bc3be2cd90b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jul 2018 11:29:28 +0100 Subject: [PATCH 040/569] Update RELEASENOTES.md --- RELEASENOTES.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a9c7a5ef..2e75237f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,15 @@ +Version 3.3d0 +==== +Internal changes are being made for version 3.3 to avoid using `SIGUSR1` and `pthread_kill` to stop threads; the standard `pthread_cancel` and friends are being used instead. This should lead to more reliable and orderly cancellatioon of sessions and threads. However, it is quite a complex change, so bugs may have been introduced or reactivated. Lots of testing needed. + +**New Features** +* Add the command `quit` to the MPRIS and the D-Bus interfaces. The main motivation for this is that it makes it easier to search for memory leaks. +* Log the output rate, in frames per second, in the `statistics` output, for the entire play session up to that time. The rate is timed relative to `CLOCK_MONOTONIC`. When your system is connected for an appreciable period to network time, e.g. using an NTP client, `CLOCK_MONOTONIC` is adjusted ("conditioned") to keep time extremely accurately. This means that the output rate figure should be very accurate, especially over a long play session, say a couple of hours. + +**Bug Fixes** +* A number of memory leaks have been identified and removed. +* A bogus warning about using the deprecated `general` `statistics` options has been fixed. + Version 3.2d67 ==== This is equivalent to 3.2.1. @@ -5,7 +17,6 @@ This is equivalent to 3.2.1. **Bug Fix** * Fix a bug causing a crash when `soxr` interpolation was enabled and a play session was stopped. This bug overwrote incorrect memory locations, so depending on what it overwrote, may have been responsible for other inexplicable crashes. Thanks are due to [hanaguro](https://github.com/hanaguro), [FnasBas](https://github.com/FnasBas), [priitohlo](https://github.com/priitohlo), [David Krmpotić](https://github.com/davidhq) and [artenverho](https://github.com/artenverho). - Version 3.2.d66 ==== This is equivalent to 3.2. From 78ae53d2dcce15328804d628e7226ecdd79d6ba1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jul 2018 11:29:52 +0100 Subject: [PATCH 041/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 2e75237f..03dfbc11 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,6 +1,6 @@ Version 3.3d0 ==== -Internal changes are being made for version 3.3 to avoid using `SIGUSR1` and `pthread_kill` to stop threads; the standard `pthread_cancel` and friends are being used instead. This should lead to more reliable and orderly cancellatioon of sessions and threads. However, it is quite a complex change, so bugs may have been introduced or reactivated. Lots of testing needed. +Internal changes are being made for version 3.3 to avoid using `SIGUSR1` and `pthread_kill` to stop threads; the standard `pthread_cancel` and friends are being used instead. This should lead to more reliable and orderly cancellation of sessions and threads. However, it is quite a complex change, so bugs may have been introduced or reactivated. Lots of testing needed. **New Features** * Add the command `quit` to the MPRIS and the D-Bus interfaces. The main motivation for this is that it makes it easier to search for memory leaks. From d3838bf3907da32f491fa8d3f7cc1f6b981ebb4f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jul 2018 12:17:32 +0100 Subject: [PATCH 042/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 03dfbc11..971ce3f2 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -4,7 +4,7 @@ Internal changes are being made for version 3.3 to avoid using `SIGUSR1` and `pt **New Features** * Add the command `quit` to the MPRIS and the D-Bus interfaces. The main motivation for this is that it makes it easier to search for memory leaks. -* Log the output rate, in frames per second, in the `statistics` output, for the entire play session up to that time. The rate is timed relative to `CLOCK_MONOTONIC`. When your system is connected for an appreciable period to network time, e.g. using an NTP client, `CLOCK_MONOTONIC` is adjusted ("conditioned") to keep time extremely accurately. This means that the output rate figure should be very accurate, especially over a long play session, say a couple of hours. +* Log the output rate, in frames per second, in the `statistics` output, for the entire play session since that last pause/resume, if any. The rate is timed relative to `CLOCK_MONOTONIC`. When your system is connected for an appreciable period to network time, e.g. using an NTP client, `CLOCK_MONOTONIC` is adjusted ("conditioned") to keep time extremely accurately. This means that the output rate figure should be very accurate, especially over a long play session, say a couple of hours. **Bug Fixes** * A number of memory leaks have been identified and removed. From b01389c27ba06272dc251d367dd2154154ebee16 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jul 2018 12:23:55 +0100 Subject: [PATCH 043/569] Update RELEASENOTES.md --- RELEASENOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 971ce3f2..644a32ca 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -5,6 +5,7 @@ Internal changes are being made for version 3.3 to avoid using `SIGUSR1` and `pt **New Features** * Add the command `quit` to the MPRIS and the D-Bus interfaces. The main motivation for this is that it makes it easier to search for memory leaks. * Log the output rate, in frames per second, in the `statistics` output, for the entire play session since that last pause/resume, if any. The rate is timed relative to `CLOCK_MONOTONIC`. When your system is connected for an appreciable period to network time, e.g. using an NTP client, `CLOCK_MONOTONIC` is adjusted ("conditioned") to keep time extremely accurately. This means that the output rate figure should be very accurate, especially over a long play session, say a couple of hours. +Timing is done from the start of the play session, or from the resumption of play following a pause. Note that while some sources pause and resume between tracks, both iTunes on the Mac and the Music app on iOS play all the tracks on a playlist without pause, so long as the tracks are downloaded and present in the device in time. **Bug Fixes** * A number of memory leaks have been identified and removed. From b75c82d14a544b072fd30f181e9de16a15095fb9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jul 2018 13:39:38 +0100 Subject: [PATCH 044/569] Add input frame rate calculation. --- player.c | 125 +++++++++++++++++++--------------------------------- player.h | 14 +++++- shairport.c | 2 +- 3 files changed, 60 insertions(+), 81 deletions(-) diff --git a/player.c b/player.c index a143740e..5e18c1e4 100644 --- a/player.c +++ b/player.c @@ -523,6 +523,18 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp } if (conn->ab_write == seqno) { // expected packet + uint64_t reception_time = get_absolute_time_in_fp(); + + if ((conn->packet_count >= 145) && (conn->packet_count <= 155)) { + conn->frames_inward_measurement_start_time = reception_time; + conn->frames_inward_frames_received_at_measurement_start_time = timestamp; + conn->input_frame_rate_status = 1; // valid now + debug(1, "frames_inward_measurement_start_time set"); + } + + conn->frames_inward_measurement_time = reception_time; + conn->frames_inward_frames_received_at_measurement_time = timestamp; + abuf = conn->audio_buffer + BUFIDX(seqno); conn->ab_write = SUCCESSOR(seqno); } else if (seq_order(conn->ab_write, seqno, conn->ab_read)) { // newer than expected @@ -1432,11 +1444,12 @@ void player_thread_cleanup_handler(void *arg) { int elapsedMin = (rawSeconds / 60) % 60; int elapsedSec = rawSeconds % 60; if (conn->frame_rate_status == 0) - inform("Playback Stopped. Total playing time %02d:%02d:%02d at %0.2f frames per second.", - elapsedHours, elapsedMin, elapsedSec, conn->frame_rate); + inform("Playback Stopped. Total playing time %02d:%02d:%02d. Input: %0.2f, output: %0.2f " + "frames per second.", + elapsedHours, elapsedMin, elapsedSec, conn->input_frame_rate, conn->frame_rate); else - inform("Playback Stopped. Total playing time %02d:%02d:%02d.", elapsedHours, elapsedMin, - elapsedSec); + inform("Playback Stopped. Total playing time %02d:%02d:%02d. Input: %0.2f frames per second.", + elapsedHours, elapsedMin, elapsedSec, conn->input_frame_rate); } #ifdef HAVE_DACP_CLIENT @@ -1720,7 +1733,8 @@ void *player_thread_func(void *arg) { "min DAC queue size, " "min buffer occupancy, " "max buffer occupancy, " - "frames per second"); + "input frames per second, " + "output frames per second."); } else { inform("sync error in milliseconds, " "total packets, " @@ -1730,7 +1744,8 @@ void *player_thread_func(void *arg) { "resend requests, " "min DAC queue size, " "min buffer occupancy, " - "max buffer occupancy"); + "max buffer occupancy, " + "input frames per second."); } } else { inform("sync error in milliseconds, " @@ -1740,7 +1755,8 @@ void *player_thread_func(void *arg) { "too late packets, " "resend requests, " "min buffer occupancy, " - "max buffer occupancy"); + "max buffer occupancy, " + "input frames per second."); } } @@ -2291,6 +2307,18 @@ void *player_thread_func(void *arg) { // if ((play_number/print_interval)%20==0) if (config.statistics_requested) { if (at_least_one_frame_seen) { + if (conn->input_frame_rate_status == 1) { + uint64_t elapsed_reception_time, frames_received; + elapsed_reception_time = conn->frames_inward_measurement_time - + conn->frames_inward_measurement_start_time; + frames_received = conn->frames_inward_frames_received_at_measurement_time - + conn->frames_inward_frames_received_at_measurement_start_time; + conn->input_frame_rate = + 1.0 * (frames_received * (uint64_t)0x100000000) / elapsed_reception_time; + } else { + conn->input_frame_rate = 0.0; + } + if ((config.output->delay)) { if (config.no_sync == 0) { if (config.output->rate_info) { @@ -2313,7 +2341,8 @@ void *player_thread_func(void *arg) { "%*lli," /* min DAC queue size */ "%*d," /* min buffer occupancy */ "%*d," /* max buffer occupancy */ - "%*.2f", /* frame rate */ + "%*.2f," /* input frame rate */ + "%*.2f", /* output frame rate */ 10, 1000 * moving_average_sync_error / config.output_rate, 10, moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), @@ -2322,7 +2351,8 @@ void *player_thread_func(void *arg) { 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, - maximum_buffer_occupancy, 10, conn->frame_rate); + maximum_buffer_occupancy, 11, conn->input_frame_rate, 11, + conn->frame_rate); } else { inform("%*.1f," /* Sync error in milliseconds */ "%*d," /* total packets */ @@ -2332,12 +2362,14 @@ void *player_thread_func(void *arg) { "%*llu," /* resend requests */ "%*lli," /* min DAC queue size */ "%*d," /* min buffer occupancy */ - "%*d", /* max buffer occupancy */ + "%*d," /* max buffer occupancy */ + "%*.2f", /* input frame rate */ 10, 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, 5, - minimum_buffer_occupancy, 5, maximum_buffer_occupancy); + minimum_buffer_occupancy, 5, maximum_buffer_occupancy, 11, + conn->input_frame_rate); } } else { inform("%*.1f," /* Sync error in milliseconds */ @@ -2347,12 +2379,13 @@ void *player_thread_func(void *arg) { "%*llu," /* too late packets */ "%*llu," /* resend requests */ "%*d," /* min buffer occupancy */ - "%*d", /* max buffer occupancy */ + "%*d," /* max buffer occupancy */ + "%*.2f", /* input frame rate */ 10, 1000 * moving_average_sync_error / config.output_rate, 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); + maximum_buffer_occupancy, 11, conn->input_frame_rate); } } else { inform("No frames received in the last sampling interval."); @@ -2368,72 +2401,6 @@ void *player_thread_func(void *arg) { } debug(1, "This should never be called."); - - /* all done in the cleanup... - - debug(3, "Connection %d: player thread main loop exit.", conn->connection_number); - - if (config.statistics_requested) { - int rawSeconds = (int)difftime(time(NULL), conn->playstart); - int elapsedHours = rawSeconds / 3600; - int elapsedMin = (rawSeconds / 60) % 60; - int elapsedSec = rawSeconds % 60; - inform("Playback Stopped. Total playing time %02d:%02d:%02d.", elapsedHours, elapsedMin, - elapsedSec); - } - - #ifdef HAVE_DACP_CLIENT - - relinquish_dacp_server_information(conn); // say it doesn't belong to this conversation thread - any more... - - #else - // stop watching for DACP port number stuff - // this is only used for compatability, if dacp stuff isn't enabled. - if (conn->dapo_private_storage) { - mdns_dacp_dont_monitor(conn->dapo_private_storage); - conn->dapo_private_storage = NULL; - } else { - debug(2, "DACP Monitor already stopped"); - } - #endif - - debug(2, "Cancelling timing, control and audio threads..."); - debug(2, "Cancel timing thread."); - pthread_cancel(rtp_timing_thread); - debug(2, "Join timing thread."); - pthread_join(rtp_timing_thread, NULL); - debug(2, "Timing thread terminated."); - debug(2, "Cancel control thread."); - pthread_cancel(rtp_control_thread); - debug(2, "Join control thread."); - pthread_join(rtp_control_thread, NULL); - debug(2, "Control thread terminated."); - debug(2, "Cancel audio thread."); - pthread_cancel(rtp_audio_thread); - debug(2, "Join audio thread."); - pthread_join(rtp_audio_thread, NULL); - debug(2, "Audio thread terminated."); - clear_reference_timestamp(conn); - conn->rtp_running = 0; - - debug(3, "Connection %d: stopping output device.", conn->connection_number); - - if (config.output->stop) - config.output->stop(); - - debug(2, "Freeing audio buffers and decoders."); - - free_audio_buffers(conn); - terminate_decoders(conn); - debug(2, "Connection %d: player thread terminated.", conn->connection_number); - if (outbuf) - free(outbuf); - if (tbuf) - free(tbuf); - if (sbuf) - free(sbuf); - */ pthread_cleanup_pop(1); pthread_exit(NULL); } diff --git a/player.h b/player.h index 42d75f9b..709eef34 100644 --- a/player.h +++ b/player.h @@ -91,10 +91,22 @@ typedef struct { int32_t *sbuf; char *outbuf; - // for holding the rate information until printed out at the end of a session + // for holding the output rate information until printed out at the end of a session double frame_rate; int frame_rate_status; + // for holding input rate information until printed out at the end of a session + + double input_frame_rate; + int input_frame_rate_status; + + uint64_t frames_inward_measurement_start_time; + uint64_t frames_inward_frames_received_at_measurement_start_time; + + uint64_t frames_inward_measurement_time; + uint64_t frames_inward_frames_received_at_measurement_time; + + // other stuff... pthread_t *player_thread; abuf_t audio_buffer[BUFFER_FRAMES]; int max_frames_per_packet, input_num_channels, input_bit_depth, input_rate; diff --git a/shairport.c b/shairport.c index f07ad52c..bd4025dc 100644 --- a/shairport.c +++ b/shairport.c @@ -465,7 +465,7 @@ int parse_options(int argc, char **argv) { /* Get the statistics setting. */ if (config_set_lookup_bool(config.cfg, "general.statistics", - &(config.statistics_requested))) { + &(config.statistics_requested))) { warn("The \"general\" \"statistics\" setting is deprecated. Please use the \"diagnostics\" " "\"statistics\" setting instead."); } From f6b3c420c7f8bc557fff60e8bb2c8e0d05ea26e7 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jul 2018 14:07:39 +0100 Subject: [PATCH 045/569] Enable input frame rate calculation to handle a pause/resume correctly. --- player.c | 38 ++++++++++++++++++-------------------- player.h | 7 ++++--- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/player.c b/player.c index 5e18c1e4..956a4dbd 100644 --- a/player.c +++ b/player.c @@ -483,6 +483,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp debug_mutex_lock(&conn->ab_mutex, 30000, 1); conn->packet_count++; + conn->packet_count_since_flush++; conn->time_of_last_audio_packet = get_absolute_time_in_fp(); if (conn->connection_state_to_output) { // if we are supposed to be processing these packets @@ -494,6 +495,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp "Dropping flushed packet in player_put_packet, seqno %u, timestamp %lld, flushing to " "timestamp: %lld.", seqno, ltimestamp, conn->flush_rtp_timestamp); + } else { if ((conn->flush_rtp_timestamp != 0x0) && (ltimestamp > conn->flush_rtp_timestamp)) // if we have gone past the flush boundary time @@ -524,17 +526,17 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp if (conn->ab_write == seqno) { // expected packet uint64_t reception_time = get_absolute_time_in_fp(); - - if ((conn->packet_count >= 145) && (conn->packet_count <= 155)) { + + if ((conn->packet_count_since_flush>=145) && (conn->packet_count_since_flush<=155)) { conn->frames_inward_measurement_start_time = reception_time; conn->frames_inward_frames_received_at_measurement_start_time = timestamp; conn->input_frame_rate_status = 1; // valid now - debug(1, "frames_inward_measurement_start_time set"); + debug(1,"frames_inward_measurement_start_time set"); } conn->frames_inward_measurement_time = reception_time; conn->frames_inward_frames_received_at_measurement_time = timestamp; - + abuf = conn->audio_buffer + BUFIDX(seqno); conn->ab_write = SUCCESSOR(seqno); } else if (seq_order(conn->ab_write, seqno, conn->ab_read)) { // newer than expected @@ -1444,12 +1446,11 @@ void player_thread_cleanup_handler(void *arg) { int elapsedMin = (rawSeconds / 60) % 60; int elapsedSec = rawSeconds % 60; if (conn->frame_rate_status == 0) - inform("Playback Stopped. Total playing time %02d:%02d:%02d. Input: %0.2f, output: %0.2f " - "frames per second.", + inform("Playback Stopped. Total playing time %02d:%02d:%02d. Input: %0.2f, output: %0.2f frames per second.", elapsedHours, elapsedMin, elapsedSec, conn->input_frame_rate, conn->frame_rate); else - inform("Playback Stopped. Total playing time %02d:%02d:%02d. Input: %0.2f frames per second.", - elapsedHours, elapsedMin, elapsedSec, conn->input_frame_rate); + inform("Playback Stopped. Total playing time %02d:%02d:%02d. Input: %0.2f frames per second.", elapsedHours, elapsedMin, + elapsedSec, conn->input_frame_rate); } #ifdef HAVE_DACP_CLIENT @@ -1510,6 +1511,7 @@ void *player_thread_func(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; conn->packet_count = 0; + conn->packet_count_since_flush = 0; conn->previous_random_number = 0; conn->input_bytes_per_frame = 4; conn->decoder_in_use = 0; @@ -2309,16 +2311,13 @@ void *player_thread_func(void *arg) { if (at_least_one_frame_seen) { if (conn->input_frame_rate_status == 1) { uint64_t elapsed_reception_time, frames_received; - elapsed_reception_time = conn->frames_inward_measurement_time - - conn->frames_inward_measurement_start_time; - frames_received = conn->frames_inward_frames_received_at_measurement_time - - conn->frames_inward_frames_received_at_measurement_start_time; - conn->input_frame_rate = - 1.0 * (frames_received * (uint64_t)0x100000000) / elapsed_reception_time; + elapsed_reception_time = conn->frames_inward_measurement_time - conn->frames_inward_measurement_start_time; + frames_received = conn->frames_inward_frames_received_at_measurement_time - conn->frames_inward_frames_received_at_measurement_start_time; + conn->input_frame_rate = 1.0 * (frames_received * (uint64_t)0x100000000) / elapsed_reception_time; } else { conn->input_frame_rate = 0.0; } - + if ((config.output->delay)) { if (config.no_sync == 0) { if (config.output->rate_info) { @@ -2329,7 +2328,7 @@ void *player_thread_func(void *arg) { conn->frame_rate = 1.0 * (frames_played * (uint64_t)0x100000000) / elapsed_play_time; } - } + } inform("%*.1f," /* Sync error in milliseconds */ "%*.1f," /* net correction in ppm */ "%*.1f," /* corrections in ppm */ @@ -2351,8 +2350,7 @@ void *player_thread_func(void *arg) { 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, - maximum_buffer_occupancy, 11, conn->input_frame_rate, 11, - conn->frame_rate); + maximum_buffer_occupancy, 11, conn->input_frame_rate, 11, conn->frame_rate); } else { inform("%*.1f," /* Sync error in milliseconds */ "%*d," /* total packets */ @@ -2368,8 +2366,7 @@ void *player_thread_func(void *arg) { 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, 5, - minimum_buffer_occupancy, 5, maximum_buffer_occupancy, 11, - conn->input_frame_rate); + minimum_buffer_occupancy, 5, maximum_buffer_occupancy, 11, conn->input_frame_rate); } } else { inform("%*.1f," /* Sync error in milliseconds */ @@ -2651,6 +2648,7 @@ void do_flush(int64_t timestamp, rtsp_conn_info *conn) { conn->flush_rtp_timestamp = timestamp; // flush all packets up to (and including?) this conn->play_segment_reference_frame = 0; conn->play_number_after_flush = 0; + conn->packet_count_since_flush = 0; debug_mutex_unlock(&conn->flush_mutex, 3); #ifdef CONFIG_METADATA diff --git a/player.h b/player.h index 709eef34..134cba26 100644 --- a/player.h +++ b/player.h @@ -94,12 +94,12 @@ typedef struct { // for holding the output rate information until printed out at the end of a session double frame_rate; int frame_rate_status; - + // for holding input rate information until printed out at the end of a session - + double input_frame_rate; int input_frame_rate_status; - + uint64_t frames_inward_measurement_start_time; uint64_t frames_inward_frames_received_at_measurement_start_time; @@ -115,6 +115,7 @@ typedef struct { int64_t previous_random_number; alac_file *decoder_info; uint64_t packet_count; + uint64_t packet_count_since_flush; int connection_state_to_output; uint64_t first_packet_time_to_play; int64_t time_since_play_started; // nanoseconds From 7c182afcd723982c7470c5a94ad09d1f62afbd7c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jul 2018 14:20:28 +0100 Subject: [PATCH 046/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 644a32ca..842fd88e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -4,7 +4,7 @@ Internal changes are being made for version 3.3 to avoid using `SIGUSR1` and `pt **New Features** * Add the command `quit` to the MPRIS and the D-Bus interfaces. The main motivation for this is that it makes it easier to search for memory leaks. -* Log the output rate, in frames per second, in the `statistics` output, for the entire play session since that last pause/resume, if any. The rate is timed relative to `CLOCK_MONOTONIC`. When your system is connected for an appreciable period to network time, e.g. using an NTP client, `CLOCK_MONOTONIC` is adjusted ("conditioned") to keep time extremely accurately. This means that the output rate figure should be very accurate, especially over a long play session, say a couple of hours. +* Log the input and output frame rates in the `statistics` output. The figures generated are averaged over the entire play session since the last pause/resume, if any, so they should settle down and become more accurate over a long play session, say a couple of hours. Timing is done relative to `CLOCK_MONOTONIC`. When your system is connected for an appreciable period to network time, e.g. using an NTP client, `CLOCK_MONOTONIC` is adjusted ("conditioned") to keep time extremely accurately. The output frame rate should be very accurate. The calculated input frame rate will vary considerably over short intervals due to network conditions, but over a long play session it should also become very accurate. Timing is done from the start of the play session, or from the resumption of play following a pause. Note that while some sources pause and resume between tracks, both iTunes on the Mac and the Music app on iOS play all the tracks on a playlist without pause, so long as the tracks are downloaded and present in the device in time. **Bug Fixes** From acc1c5d6df7608b23335fc6e534940f9d2949b5f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jul 2018 14:23:41 +0100 Subject: [PATCH 047/569] Update RELEASENOTES.md --- RELEASENOTES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 842fd88e..d2122860 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -3,9 +3,9 @@ Version 3.3d0 Internal changes are being made for version 3.3 to avoid using `SIGUSR1` and `pthread_kill` to stop threads; the standard `pthread_cancel` and friends are being used instead. This should lead to more reliable and orderly cancellation of sessions and threads. However, it is quite a complex change, so bugs may have been introduced or reactivated. Lots of testing needed. **New Features** -* Add the command `quit` to the MPRIS and the D-Bus interfaces. The main motivation for this is that it makes it easier to search for memory leaks. -* Log the input and output frame rates in the `statistics` output. The figures generated are averaged over the entire play session since the last pause/resume, if any, so they should settle down and become more accurate over a long play session, say a couple of hours. Timing is done relative to `CLOCK_MONOTONIC`. When your system is connected for an appreciable period to network time, e.g. using an NTP client, `CLOCK_MONOTONIC` is adjusted ("conditioned") to keep time extremely accurately. The output frame rate should be very accurate. The calculated input frame rate will vary considerably over short intervals due to network conditions, but over a long play session it should also become very accurate. +* Frame rates! Log the input and output frame rates in the `statistics` output. The figures generated are averaged over the entire play session since the last pause/resume, if any, so they should settle down and become more accurate over a long play session, say a couple of hours. Timing is done relative to `CLOCK_MONOTONIC`. When your system is connected for an appreciable period to network time, e.g. using an NTP client, `CLOCK_MONOTONIC` is adjusted ("conditioned") to keep time extremely accurately. The output frame rate should be very accurate. The calculated input frame rate will vary considerably over short intervals due to network conditions, but over a long play session it should also become very accurate. Timing is done from the start of the play session, or from the resumption of play following a pause. Note that while some sources pause and resume between tracks, both iTunes on the Mac and the Music app on iOS play all the tracks on a playlist without pause, so long as the tracks are downloaded and present in the device in time. +* Add the command `quit` to the MPRIS and the D-Bus interfaces. The main motivation for this is that it makes it easier to search for memory leaks. **Bug Fixes** * A number of memory leaks have been identified and removed. From db5a38b31e2c32327e129bd2b6a6dea4325c6dce Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 30 Jul 2018 22:13:56 +0100 Subject: [PATCH 048/569] Update RELEASENOTES.md --- RELEASENOTES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d2122860..e709ba81 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -3,8 +3,8 @@ Version 3.3d0 Internal changes are being made for version 3.3 to avoid using `SIGUSR1` and `pthread_kill` to stop threads; the standard `pthread_cancel` and friends are being used instead. This should lead to more reliable and orderly cancellation of sessions and threads. However, it is quite a complex change, so bugs may have been introduced or reactivated. Lots of testing needed. **New Features** -* Frame rates! Log the input and output frame rates in the `statistics` output. The figures generated are averaged over the entire play session since the last pause/resume, if any, so they should settle down and become more accurate over a long play session, say a couple of hours. Timing is done relative to `CLOCK_MONOTONIC`. When your system is connected for an appreciable period to network time, e.g. using an NTP client, `CLOCK_MONOTONIC` is adjusted ("conditioned") to keep time extremely accurately. The output frame rate should be very accurate. The calculated input frame rate will vary considerably over short intervals due to network conditions, but over a long play session it should also become very accurate. -Timing is done from the start of the play session, or from the resumption of play following a pause. Note that while some sources pause and resume between tracks, both iTunes on the Mac and the Music app on iOS play all the tracks on a playlist without pause, so long as the tracks are downloaded and present in the device in time. +* Frame rates! Exact input and output frame rates are now included in the `statistics` output. The figures generated are averaged over the entire play session since the last pause/resume, if any, so they should settle down and become more accurate over a long play session, say a couple of hours. Timing is relative to `CLOCK_MONOTONIC`. When your system is connected for an appreciable period to network time, e.g. using an NTP client, `CLOCK_MONOTONIC` is adjusted ("conditioned") to keep time extremely accurately. The calculated output frame rate should be very accurate. The calculated input frame rate will vary considerably over short intervals due to network conditions, but over a long play session it should also become very accurate. +Timing is done from the start of the play session, or from the resumption of play following a pause. Note that while some audio sources pause and resume between tracks, both iTunes on the Mac and the Music app on iOS play all the tracks on a playlist without pause (so long as the tracks are downloaded and present in the device in time). * Add the command `quit` to the MPRIS and the D-Bus interfaces. The main motivation for this is that it makes it easier to search for memory leaks. **Bug Fixes** From 3888125a24efa31e55aa1e05fd6b08c7e134a6a4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 1 Aug 2018 09:35:28 +0100 Subject: [PATCH 049/569] Change calculation order to prevent overflow after about 26 hours at 44,100 fps in the frame rate calculations. Calculate frame rates whether statistcs are requested or not. Clean up some fps rate availability flags. --- audio_alsa.c | 4 +++- player.c | 53 +++++++++++++++++++++++++++++++++------------------- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 27995d57..4ab704fe 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -894,14 +894,16 @@ int delay(long *the_delay) { } int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) { + int response = 0; // zero means okay if (measurement_data_is_valid) { *elapsed_time = measurement_time - measurement_start_time; *frames_played = frames_played_at_measurement_time - frames_played_at_measurement_start_time; } else { *elapsed_time = 0; *frames_played = 0; + response = -1; } - return 0; + return response; } static int play(void *buf, int samples) { diff --git a/player.c b/player.c index 956a4dbd..3dd1ac4b 100644 --- a/player.c +++ b/player.c @@ -1445,7 +1445,7 @@ void player_thread_cleanup_handler(void *arg) { int elapsedHours = rawSeconds / 3600; int elapsedMin = (rawSeconds / 60) % 60; int elapsedSec = rawSeconds % 60; - if (conn->frame_rate_status == 0) + if (conn->frame_rate_status) inform("Playback Stopped. Total playing time %02d:%02d:%02d. Input: %0.2f, output: %0.2f frames per second.", elapsedHours, elapsedMin, elapsedSec, conn->input_frame_rate, conn->frame_rate); else @@ -1611,7 +1611,10 @@ void *player_thread_func(void *arg) { conn->playstart = time(NULL); conn->frame_rate = 0.0; - conn->frame_rate_status = -1; // zero means okay + conn->frame_rate_status = 0; + + conn->input_frame_rate = 0.0; + conn->input_frame_rate_status = 0; conn->buffer_occupancy = 0; @@ -2299,6 +2302,35 @@ void *player_thread_func(void *arg) { } } if (play_number % print_interval == 0) { + + // here, calculate the input and output frame rates, where possible, even if statistics have not been requested + // this is to calculate them in case they are needed by the D-Bus interface or elsewhere. + + if (conn->input_frame_rate_status) { + uint64_t elapsed_reception_time, frames_received; + elapsed_reception_time = conn->frames_inward_measurement_time - conn->frames_inward_measurement_start_time; + frames_received = conn->frames_inward_frames_received_at_measurement_time - conn->frames_inward_frames_received_at_measurement_start_time; + conn->input_frame_rate = (1.0 * frames_received) / elapsed_reception_time; // an IEEE double calculation with two 64-bit integers + conn->input_frame_rate = conn->input_frame_rate * (uint64_t)0x100000000; // this should just change the [binary] exponent in the IEEE FP representation; the mantissa should be unaffected. + } + + if ((config.output->delay) && (config.no_sync == 0) && (config.output->rate_info)) { + uint64_t elapsed_play_time, frames_played; + if (config.output->rate_info(&elapsed_play_time, &frames_played)==0) + conn->frame_rate_status = 1; + else + conn->frame_rate_status = 0; + if (conn->frame_rate_status) { + conn->frame_rate = + (1.0 * frames_played) / elapsed_play_time; // an IEEE double calculation with two 64-bit integers + conn->frame_rate = + conn->frame_rate * (uint64_t)0x100000000; // this should just change the [binary] exponent in the IEEE FP representation; the mantissa should be unaffected. + } else { + conn->frame_rate = 0.0; + } + } + + // we can now calculate running averages for sync error (frames), corrections (ppm), // insertions plus deletions (ppm), drift (ppm) double moving_average_sync_error = (1.0 * tsum_of_sync_errors) / number_of_statistics; @@ -2309,26 +2341,9 @@ void *player_thread_func(void *arg) { // if ((play_number/print_interval)%20==0) if (config.statistics_requested) { if (at_least_one_frame_seen) { - if (conn->input_frame_rate_status == 1) { - uint64_t elapsed_reception_time, frames_received; - elapsed_reception_time = conn->frames_inward_measurement_time - conn->frames_inward_measurement_start_time; - frames_received = conn->frames_inward_frames_received_at_measurement_time - conn->frames_inward_frames_received_at_measurement_start_time; - conn->input_frame_rate = 1.0 * (frames_received * (uint64_t)0x100000000) / elapsed_reception_time; - } else { - conn->input_frame_rate = 0.0; - } if ((config.output->delay)) { if (config.no_sync == 0) { - if (config.output->rate_info) { - uint64_t elapsed_play_time, frames_played; - conn->frame_rate_status = - config.output->rate_info(&elapsed_play_time, &frames_played); - if (conn->frame_rate_status == 0) { - conn->frame_rate = - 1.0 * (frames_played * (uint64_t)0x100000000) / elapsed_play_time; - } - } inform("%*.1f," /* Sync error in milliseconds */ "%*.1f," /* net correction in ppm */ "%*.1f," /* corrections in ppm */ From 3a0fb4c9197515376ff3a3364cbb854d405ea362 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 1 Aug 2018 09:36:28 +0100 Subject: [PATCH 050/569] Change calculation order to prevent overflow after about 26 hours at 44,100 fps in the frame rate calculations. Calculate frame rates whether statistcs are requested or not. Clean up some fps rate availability flags. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 8650a1a0..54c0e6fd 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.3d0], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d1], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From e8de38546ac037ae8357bb8c7da52c5c4a0ace60 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 1 Aug 2018 09:49:34 +0100 Subject: [PATCH 051/569] Update RELEASENOTES.md --- RELEASENOTES.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e709ba81..53305b6a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,7 +1,13 @@ -Version 3.3d0 +Version 3.3d1 ==== Internal changes are being made for version 3.3 to avoid using `SIGUSR1` and `pthread_kill` to stop threads; the standard `pthread_cancel` and friends are being used instead. This should lead to more reliable and orderly cancellation of sessions and threads. However, it is quite a complex change, so bugs may have been introduced or reactivated. Lots of testing needed. +**Bug Fix** +* Fix an arithmetic overflow in frame rate calculations that could occur after 2^32 frames – approximately 27 hours at 44,100 frames per second. + +Version 3.3d0 +==== + **New Features** * Frame rates! Exact input and output frame rates are now included in the `statistics` output. The figures generated are averaged over the entire play session since the last pause/resume, if any, so they should settle down and become more accurate over a long play session, say a couple of hours. Timing is relative to `CLOCK_MONOTONIC`. When your system is connected for an appreciable period to network time, e.g. using an NTP client, `CLOCK_MONOTONIC` is adjusted ("conditioned") to keep time extremely accurately. The calculated output frame rate should be very accurate. The calculated input frame rate will vary considerably over short intervals due to network conditions, but over a long play session it should also become very accurate. Timing is done from the start of the play session, or from the resumption of play following a pause. Note that while some audio sources pause and resume between tracks, both iTunes on the Mac and the Music app on iOS play all the tracks on a playlist without pause (so long as the tracks are downloaded and present in the device in time). From 3870195cf987ebf9f96244107f0bdecdf4f1ea55 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 5 Aug 2018 17:23:07 +0100 Subject: [PATCH 052/569] Begin to use a unified and more accurate scheme for translating between local time and rtp timestamp, taking clock drift and actualt source rate into account if available. Not dealing with highes output rates yet. --- player.c | 101 +++++++++++--- player.h | 25 +++- rtp.c | 400 +++++++++++++++++++++++++++++++++++++------------------ rtp.h | 9 ++ 4 files changed, 376 insertions(+), 159 deletions(-) diff --git a/player.c b/player.c index 3dd1ac4b..51a59ef4 100644 --- a/player.c +++ b/player.c @@ -494,8 +494,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp debug(3, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %lld, flushing to " "timestamp: %lld.", - seqno, ltimestamp, conn->flush_rtp_timestamp); - + seqno, ltimestamp, conn->flush_rtp_timestamp); } else { if ((conn->flush_rtp_timestamp != 0x0) && (ltimestamp > conn->flush_rtp_timestamp)) // if we have gone past the flush boundary time @@ -527,11 +526,11 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp if (conn->ab_write == seqno) { // expected packet uint64_t reception_time = get_absolute_time_in_fp(); - if ((conn->packet_count_since_flush>=145) && (conn->packet_count_since_flush<=155)) { + if ((conn->packet_count_since_flush>=500) && (conn->packet_count_since_flush<=510)) { conn->frames_inward_measurement_start_time = reception_time; conn->frames_inward_frames_received_at_measurement_start_time = timestamp; conn->input_frame_rate_status = 1; // valid now - debug(1,"frames_inward_measurement_start_time set"); + // debug(1,"frames_inward_measurement_start_time set"); } conn->frames_inward_measurement_time = reception_time; @@ -911,7 +910,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // debug(1,"First frame seen, time %u, with %d // frames...",curframe->timestamp,seq_diff(ab_read, ab_write)); - if (reference_timestamp) { // if we have a reference time + if (have_timestamp_timing_information(conn)) { // if we have a reference time // debug(1,"First frame seen with timestamp..."); conn->first_packet_timestamp = curframe->timestamp; // we will keep buffering until we are @@ -951,6 +950,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // -4410 frames. // debug(1, "Output sample ratio is %d", conn->output_sample_ratio); + + + // what we are asking for here is "what is the local time at which time the calculated frame should be played" int64_t delta = (conn->first_packet_timestamp - reference_timestamp) + conn->latency * conn->output_sample_ratio + @@ -964,9 +966,23 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { int64_t abs_delta = -delta; int64_t delta_fp_sec = (abs_delta << 32) / config.output_rate; // int64_t which is positive - conn->first_packet_time_to_play = reference_timestamp_time - delta_fp_sec; + conn->first_packet_time_to_play = reference_timestamp_time - delta_fp_sec; } + uint64_t should_be_time; + frame_to_local_time(conn->first_packet_timestamp +conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate),&should_be_time,conn); + + if(should_be_time>=conn->first_packet_time_to_play) { + if ((((should_be_time-conn->first_packet_time_to_play)*1000000)>>32)>10) + debug(1,"New time for first packet timestamp %" PRId64 " is later than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((should_be_time-conn->first_packet_time_to_play)*1000000)>>32); + } else { + if ((((conn->first_packet_time_to_play-should_be_time)*1000000)>>32)>10) + debug(1,"New time for first packet timestamp %" PRId64 " is earlier than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((conn->first_packet_time_to_play-should_be_time)*1000000)>>32); + } + + // cut over to new calculation scheme + conn->first_packet_time_to_play = should_be_time; + if (local_time_now >= conn->first_packet_time_to_play) { debug( 1, @@ -993,6 +1009,19 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { (abs_delta << 32) / config.output_rate; // int64_t which is positive conn->first_packet_time_to_play = reference_timestamp_time - delta_fp_sec; } + + uint64_t should_be_time; + frame_to_local_time(conn->first_packet_timestamp +conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate),&should_be_time,conn); + + if(should_be_time>=conn->first_packet_time_to_play) { + if ((((should_be_time-conn->first_packet_time_to_play)*1000000)>>32)>50) + debug(1,"New time for recalculated first packet timestamp %" PRId64 " is later than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((should_be_time-conn->first_packet_time_to_play)*1000000)>>32); + } else { + if ((((conn->first_packet_time_to_play-should_be_time)*1000000)>>32)>50) + debug(1,"New time for recalculated first packet timestamp %" PRId64 " is earlier than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((conn->first_packet_time_to_play-should_be_time)*1000000)>>32); + } + + // now, the size of the initial silence must be affected by the lead-in time. // it must be somewhat less than the lead-in time so that dynamic adjustments can be @@ -1140,12 +1169,14 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { } } if (conn->ab_buffering == 0) { + /* // note the time of the playing of the first frame uint64_t reference_timestamp_time; // don't need this... get_reference_timestamp_stuff(&conn->play_segment_reference_frame, &reference_timestamp_time, &conn->play_segment_reference_frame_remote_time, conn); conn->play_segment_reference_frame *= conn->output_sample_ratio; + */ #ifdef CONFIG_METADATA debug(2, "prsm"); send_ssnc_metadata('prsm', NULL, 0, @@ -1172,12 +1203,14 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if ((conn->ab_synced) && (curframe) && (curframe->ready) && (curframe->timestamp)) { do_wait = 1; // if the current frame exists and is ready, then wait unless it's time to let it go... + + // here, get the time to play the current frame. int64_t reference_timestamp; uint64_t reference_timestamp_time, remote_reference_timestamp_time; get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time, conn); // all types okay reference_timestamp *= conn->output_sample_ratio; - if (reference_timestamp) { // if we have a reference time + if (have_timestamp_timing_information(conn)) { // if we have a reference time int64_t packet_timestamp = curframe->timestamp; // types okay int64_t delta = packet_timestamp - reference_timestamp; int64_t offset = @@ -1186,6 +1219,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { config.audio_backend_buffer_desired_length * config.output_rate; // all arguments are int32_t, so expression promotion okay int64_t net_offset = delta + offset; // okay + uint64_t time_to_play = reference_timestamp_time; // type okay if (net_offset >= 0) { uint64_t net_offset_fp_sec = @@ -1199,7 +1233,20 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { time_to_play -= net_offset_fp_sec; // debug(2,"Net Offset: %lld, adjusted: -%lld.",net_offset,net_offset_fp_sec); } - + + uint64_t new_time_to_play = 0; + frame_to_local_time(packet_timestamp+offset, &new_time_to_play, conn); + + if(new_time_to_play>=time_to_play) { + if ((((new_time_to_play-time_to_play)*1000000)>>32)>100) + debug(1,"New time for frame %" PRId64 " is later than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((new_time_to_play-time_to_play)*1000000)>>32); + } else { + if ((((time_to_play-new_time_to_play)*1000000)>>32)>100) + debug(1,"New time for frame %" PRId64 " is earlier than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((time_to_play-new_time_to_play)*1000000)>>32); + } + // cut over to the new calculation system + time_to_play = new_time_to_play; + if (local_time_now >= time_to_play) { do_wait = 0; } @@ -1583,7 +1630,7 @@ void *player_thread_func(void *arg) { debug(3, "Output frame bytes is %d.", conn->output_bytes_per_frame); conn->session_corrections = 0; - conn->play_segment_reference_frame = 0; // zero signals that we are not in a play segment + // conn->play_segment_reference_frame = 0; // zero signals that we are not in a play segment // check that there are enough buffers to accommodate the desired latency and the latency offset @@ -1739,7 +1786,8 @@ void *player_thread_func(void *arg) { "min buffer occupancy, " "max buffer occupancy, " "input frames per second, " - "output frames per second."); + "output frames per second, " + "drift client vs local clock in ppm"); } else { inform("sync error in milliseconds, " "total packets, " @@ -1937,10 +1985,10 @@ void *player_thread_func(void *arg) { // uint64_t // local_time_now=((uint64_t)tn.tv_sec<<32)+((uint64_t)tn.tv_nsec<<32)/1000000000; - int64_t td = 0; - int64_t td_in_frames = 0; + int64_t td = 0; // td is the time difference between the reference timestamp time and the present time. Only used to calculate td_in_frames + int64_t td_in_frames = 0; // td_in_frames is the number of frames between between the reference timestamp time and the present time + if (local_time_now >= reference_timestamp_time) { - // debug(1,"td is %lld.",td); td = local_time_now - reference_timestamp_time; // this is the positive value. // Conversion is positive uint64_t to // int64_t, thus okay @@ -2014,9 +2062,20 @@ void *player_thread_func(void *arg) { if (resp >= 0) { + int64_t should_be_frame; + local_time_to_frame(local_time_now,&should_be_frame,conn); + + if (abs(td_in_frames + rt-should_be_frame)>10) + debug(1,"Difference between old and new frame number is %" PRId64 " frames.",td_in_frames + rt - should_be_frame); // this is the actual delay, including the latency we actually want, which will // fluctuate a good bit about a potentially rising or falling trend. - int64_t delay = td_in_frames + rt - (nt - current_delay); // all int64_t + +// int64_t delay = td_in_frames + rt - (nt - current_delay); // all int64_t + // cut over to the new calculation method + int64_t delay = should_be_frame - (nt - current_delay); // all int64_t + + // td_in_frames + rt is the frame number that should be output at local_time_now. + // This is the timing error for the next audio frame in the DAC. @@ -2344,7 +2403,7 @@ void *player_thread_func(void *arg) { if ((config.output->delay)) { if (config.no_sync == 0) { - inform("%*.1f," /* Sync error in milliseconds */ + inform("%*.2f," /* Sync error in milliseconds */ "%*.1f," /* net correction in ppm */ "%*.1f," /* corrections in ppm */ "%*d," /* total packets */ @@ -2356,7 +2415,8 @@ void *player_thread_func(void *arg) { "%*d," /* min buffer occupancy */ "%*d," /* max buffer occupancy */ "%*.2f," /* input frame rate */ - "%*.2f", /* output frame rate */ + "%*.2f," /* output frame rate */ + "%*.2f", /* client clock to local clock drift in ppm */ 10, 1000 * moving_average_sync_error / config.output_rate, 10, moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), @@ -2365,9 +2425,9 @@ void *player_thread_func(void *arg) { 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, - maximum_buffer_occupancy, 11, conn->input_frame_rate, 11, conn->frame_rate); + maximum_buffer_occupancy, 11, conn->input_frame_rate, 11, conn->frame_rate , 10, (1.0-conn->local_to_remote_time_gradient)*1000000); } else { - inform("%*.1f," /* Sync error in milliseconds */ + inform("%*.2f," /* Sync error in milliseconds */ "%*d," /* total packets */ "%*llu," /* missing packets */ "%*llu," /* late packets */ @@ -2384,7 +2444,7 @@ void *player_thread_func(void *arg) { minimum_buffer_occupancy, 5, maximum_buffer_occupancy, 11, conn->input_frame_rate); } } else { - inform("%*.1f," /* Sync error in milliseconds */ + inform("%*.2f," /* Sync error in milliseconds */ "%*d," /* total packets */ "%*llu," /* missing packets */ "%*llu," /* late packets */ @@ -2661,9 +2721,10 @@ void do_flush(int64_t timestamp, rtsp_conn_info *conn) { conn->flush_requested = 1; // if (timestamp!=0) conn->flush_rtp_timestamp = timestamp; // flush all packets up to (and including?) this - conn->play_segment_reference_frame = 0; + // conn->play_segment_reference_frame = 0; conn->play_number_after_flush = 0; conn->packet_count_since_flush = 0; + debug_mutex_unlock(&conn->flush_mutex, 3); #ifdef CONFIG_METADATA diff --git a/player.h b/player.h index 134cba26..5402a18f 100644 --- a/player.h +++ b/player.h @@ -24,13 +24,15 @@ #include "alac.h" #include "audio.h" -#define time_ping_history 8 +#define time_ping_history 32 typedef struct time_ping_record { uint64_t local_to_remote_difference; uint64_t dispersion; uint64_t local_time; uint64_t remote_time; + int sequence_number; + int chosen; } time_ping_record; typedef uint16_t seq_t; @@ -182,14 +184,21 @@ typedef struct { uint16_t local_timing_port; int64_t latency_delayed_timestamp; // this is for debugging only... - int64_t reference_timestamp; - uint64_t reference_timestamp_time; - uint64_t remote_reference_timestamp_time; + // this is what connects an rtp timestamp to the remote time + + int64_t reference_timestamp; + uint64_t remote_reference_timestamp_time; + + + // the ratio of the following should give us the operating rate, nominally 44,100 + int64_t reference_to_previous_frame_difference; + uint64_t reference_to_previous_time_difference; + // debug variables int request_sent; - uint8_t time_ping_count; + int time_ping_count; struct time_ping_record time_pings[time_ping_history]; uint64_t departure_time; // dangerous -- this assumes that there will never be two timing @@ -197,12 +206,14 @@ typedef struct { pthread_mutex_t reference_time_mutex; + double local_to_remote_time_gradient; // if no drift, this would be exactly 1.0; likely it's slightly above or below. uint64_t local_to_remote_time_difference; // used to switch between local and remote clocks + uint64_t local_to_remote_time_difference_measurement_time; // when the above was calculated int last_stuff_request; - int64_t play_segment_reference_frame; - uint64_t play_segment_reference_frame_remote_time; + // int64_t play_segment_reference_frame; + // uint64_t play_segment_reference_frame_remote_time; int32_t buffer_occupancy; // allow it to be negative because seq_diff may be negative int64_t session_corrections; diff --git a/rtp.c b/rtp.c index c86d3fa0..ef053e9b 100644 --- a/rtp.c +++ b/rtp.c @@ -45,8 +45,8 @@ #include #include -uint64_t local_to_remote_time_jitters; -uint64_t local_to_remote_time_jitters_count; +uint64_t local_to_remote_time_jitter; +uint64_t local_to_remote_time_jitter_count; void rtp_initialise(rtsp_conn_info *conn) { conn->rtp_time_of_last_resend_request_error_fp = 0; @@ -65,6 +65,27 @@ void rtp_terminate(rtsp_conn_info *conn) { debug(1, "Error destroying reference_time_mutex variable."); } +uint64_t local_to_remote_time_difference_now(rtsp_conn_info *conn) { + // this is an attempt to compensate for clock drift since the last time ping that was used + // so, if we have a non-zero clock drift, we will calculate the drift there would + // be from the time of the last time ping + uint64_t local_time_now_fp = get_absolute_time_in_fp(); + uint64_t time_since_last_local_to_remote_time_difference_measurement = local_time_now_fp - conn->local_to_remote_time_difference_measurement_time; + + uint64_t remote_time_since_last_local_to_remote_time_difference_measurement = (uint64_t)(conn->local_to_remote_time_gradient*time_since_last_local_to_remote_time_difference_measurement); + + double drift; + if (remote_time_since_last_local_to_remote_time_difference_measurement >= time_since_last_local_to_remote_time_difference_measurement) + drift = (1.0*(remote_time_since_last_local_to_remote_time_difference_measurement - time_since_last_local_to_remote_time_difference_measurement))/(uint64_t)0x100000000; + else + drift = -((1.0*(time_since_last_local_to_remote_time_difference_measurement - remote_time_since_last_local_to_remote_time_difference_measurement))/(uint64_t)0x100000000); + + double interval_ms = 1.0*(((time_since_last_local_to_remote_time_difference_measurement)*1000)>>32); +// debug(1,"Measurement drift is %.2f microseconds (0x%" PRIx64 " in 64-bit fp) over %.2f milliseconds with drift of %.2f ppm.",drift*1000000,(uint64_t)(drift*(uint64_t)0x100000000),interval_ms,(1.0-conn->local_to_remote_time_gradient)*1000000); +// return conn->local_to_remote_time_difference + (uint64_t)(drift*(uint64_t 0x100000000)); + return conn->local_to_remote_time_difference + (uint64_t)(drift*(uint64_t)0x100000000); +} + void rtp_audio_receiver_cleanup_handler(void *arg) { debug(3, "Audio Receiver Cleanup."); rtsp_conn_info *conn = (rtsp_conn_info *)arg; @@ -246,7 +267,7 @@ void *rtp_control_receiver(void *arg) { // 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 packe is 7 + // pair of bytes in the packet is 7 // then an extra time lag is expected to be added, presumably by // the AirPort Express. @@ -337,28 +358,30 @@ void *rtp_control_receiver(void *arg) { debug_mutex_lock(&conn->reference_time_mutex, 1000, 1); // this is for debugging - // uint64_t old_remote_reference_time = conn->remote_reference_timestamp_time; - // int64_t old_reference_timestamp = conn->reference_timestamp; - // int64_t old_latency_delayed_timestamp = conn->latency_delayed_timestamp; + uint64_t old_remote_reference_time = conn->remote_reference_timestamp_time; + int64_t old_reference_timestamp = conn->reference_timestamp; + int64_t old_latency_delayed_timestamp = conn->latency_delayed_timestamp; + conn->remote_reference_timestamp_time = remote_time_of_sync; - conn->reference_timestamp_time = - remote_time_of_sync - conn->local_to_remote_time_difference; + //conn->reference_timestamp_time = + // remote_time_of_sync - local_to_remote_time_difference_now(conn); conn->reference_timestamp = sync_rtp_timestamp; conn->latency_delayed_timestamp = rtp_timestamp_less_latency; debug_mutex_unlock(&conn->reference_time_mutex, 3); + + conn->reference_to_previous_time_difference = remote_time_of_sync - old_remote_reference_time; + if (old_reference_timestamp==0) + conn->reference_to_previous_frame_difference = 0; + else + conn->reference_to_previous_frame_difference = sync_rtp_timestamp - old_reference_timestamp; - // this is for debugging + int64_t delayed_frame_difference = rtp_timestamp_less_latency - old_latency_delayed_timestamp; + /* - uint64_t time_difference = remote_time_of_sync - old_remote_reference_time; - int64_t reference_frame_difference = sync_rtp_timestamp - old_reference_timestamp; - int64_t delayed_frame_difference = rtp_timestamp_less_latency - - old_latency_delayed_timestamp; - if (old_remote_reference_time) - debug(1,"Time difference: %" PRIu64 " reference and delayed frame differences: %" - PRId64 " - and %" PRId64 ", giving rates of %f and %f respectively.", - (time_difference*1000000)>>32,reference_frame_difference,delayed_frame_difference,(1.0*(reference_frame_difference*10000000))/((time_difference*10000000)>>32),(1.0*(delayed_frame_difference*10000000))/((time_difference*10000000)>>32)); + debug(1,"Time difference: %" PRIu64 " reference and delayed frame differences: %" PRId64 " and %" PRId64 ", giving rates _at source!!_ of %f and %f respectively.", + (conn->reference_to_previous_time_difference*1000000)>>32,conn->reference_to_previous_frame_difference,delayed_frame_difference, + (1.0*(conn->reference_to_previous_frame_difference*10000000))/((conn->reference_to_previous_time_difference*10000000)>>32),(1.0*(delayed_frame_difference*10000000))/((conn->reference_to_previous_time_difference*10000000)>>32)); else debug(1,"First sync received"); */ @@ -493,14 +516,21 @@ void *rtp_timing_receiver(void *arg) { pthread_create(&conn->timer_requester, NULL, &rtp_timing_sender, arg); // struct timespec att; uint64_t distant_receive_time, distant_transmit_time, arrival_time, return_time; - local_to_remote_time_jitters = 0; - local_to_remote_time_jitters_count = 0; + local_to_remote_time_jitter = 0; + local_to_remote_time_jitter_count = 0; // uint64_t first_remote_time = 0; - uint64_t first_local_time = 0; + // uint64_t first_local_time = 0; uint64_t first_local_to_remote_time_difference = 0; // uint64_t first_local_to_remote_time_difference_time; // uint64_t l2rtd = 0; + int sequence_number = 0; + + // for getting mean and sd of return times + int32_t stat_n = 0; + double stat_mean = 0.0; + double stat_M2 = 0.0; + while (1) { nread = recv(conn->timing_socket, packet, sizeof(packet), 0); @@ -548,151 +578,175 @@ void *rtp_timing_receiver(void *arg) { distant_transmit_time = (uint64_t)nctohl(&packet[24]) << 32; distant_transmit_time += nctohl(&packet[28]); - // processing_time = distant_transmit_time - distant_receive_time; - - // debug(1,"Return trip time: %lluuS, remote processing time: - // %lluuS.",(return_time*1000000)>>32,(processing_time*1000000)>>32); - + uint64_t remote_processing_time = distant_transmit_time - distant_receive_time; + + //debug(1,"Return trip time: %" PRIu64 " uS, remote processing time: %" PRIu64 " uS.",(return_time*1000000)>>32,(remote_processing_time*1000000)>>32); + uint64_t local_time_by_remote_clock = distant_transmit_time + return_time / 2; + + // remove the remote processing time from the record of the return time, as long at the processing time looks sensible. + + if ((remote_processing_time > 0) && (remote_processing_time < return_time)) + return_time -= remote_processing_time; + else + debug(1,"Non-sensical remote processing time -- ignored."); - unsigned int cc; + int cc; for (cc = time_ping_history - 1; cc > 0; cc--) { conn->time_pings[cc] = conn->time_pings[cc - 1]; + //if ((conn->time_ping_count) && (conn->time_ping_count < 10)) +// conn->time_pings[cc].dispersion = +// conn->time_pings[cc].dispersion * pow(2.14, 1.0/conn->time_ping_count); conn->time_pings[cc].dispersion = - (conn->time_pings[cc].dispersion * 110) / - 100; // make the dispersions 'age' by this rational factor + (conn->time_pings[cc].dispersion * 110) / 100; // make the dispersions 'age' by this rational factor } - // these are for diagnostics only -- not used + // these are used for doing a least squares calculation to get the drift conn->time_pings[0].local_time = arrival_time; - conn->time_pings[0].remote_time = distant_transmit_time; + conn->time_pings[0].remote_time = distant_transmit_time; + conn->time_pings[0].sequence_number = sequence_number++; + conn->time_pings[0].chosen = 0; + + conn->time_pings[0].local_to_remote_difference = local_time_by_remote_clock - arrival_time; conn->time_pings[0].dispersion = return_time; if (conn->time_ping_count < time_ping_history) conn->time_ping_count++; - - uint64_t local_time_chosen = arrival_time; - + + // here, calculate the mean and standard deviation of the return times + + // mean and variance calculations from "online_variance" algorithm at + // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm + + double rtfus = 1.0*((return_time * 1000000) >> 32); + stat_n += 1; + double stat_delta = rtfus - stat_mean; + stat_mean += stat_delta / stat_n; + stat_M2 += stat_delta * (rtfus - stat_mean); + //debug(1, "Timing packet return time stats: current, mean and standard deviation over %d packets: %.1f, %.1f, %.1f (microseconds).", + // stat_n,rtfus,stat_mean, sqrtf(stat_M2 / (stat_n - 1))); + + // here, pick the record with the least dispersion, and record that it's been chosen + + // uint64_t local_time_chosen = arrival_time; // uint64_t remote_time_chosen = distant_transmit_time; // now pick the timestamp with the lowest dispersion uint64_t l2rtd = conn->time_pings[0].local_to_remote_difference; + uint64_t lt = conn->time_pings[0].local_time; uint64_t tld = conn->time_pings[0].dispersion; - // chosen = 0; + int chosen = 0; for (cc = 1; cc < conn->time_ping_count; cc++) if (conn->time_pings[cc].dispersion < tld) { + chosen = cc; l2rtd = conn->time_pings[cc].local_to_remote_difference; - // chosen = cc; + lt = conn->time_pings[cc].local_time; tld = conn->time_pings[cc].dispersion; - local_time_chosen = conn->time_pings[cc].local_time; + // local_time_chosen = conn->time_pings[cc].local_time; // remote_time_chosen = conn->time_pings[cc].remote_time; } - // int64_t ji; + // debug(1,"Record %d has the lowest dispersion with %0.2f us dispersion.",chosen,1.0*((tld * 1000000) >> 32)); + conn->time_pings[chosen].chosen = 1; // record the fact that it has been used for timing + // calculate the jitter -- the absolute time between the current local_to_remote_time_difference and the new one and add it to the total jitter count + int64_t ji; + int64_t ltd =0; // local time difference for the jitter + if (conn->time_ping_count > 1) { if (l2rtd > conn->local_to_remote_time_difference) { - local_to_remote_time_jitters = - local_to_remote_time_jitters + l2rtd - conn->local_to_remote_time_difference; - // ji = l2rtd - conn->local_to_remote_time_difference; + local_to_remote_time_jitter = + local_to_remote_time_jitter + l2rtd - conn->local_to_remote_time_difference; + ji = l2rtd - conn->local_to_remote_time_difference; // this is the difference between the present local-to-remote-time-difference and the new one, i.e. the jitter step } else { - local_to_remote_time_jitters = - local_to_remote_time_jitters + conn->local_to_remote_time_difference - l2rtd; - // ji = -(conn->local_to_remote_time_difference - l2rtd); + local_to_remote_time_jitter = + local_to_remote_time_jitter + conn->local_to_remote_time_difference - l2rtd; + ji = -(conn->local_to_remote_time_difference - l2rtd); } - local_to_remote_time_jitters_count += 1; + local_to_remote_time_jitter_count += 1; } - // uncomment below to print jitter between client's clock and oour clock - // int64_t rtus = (tld*1000000)>>32; ji = (ji*1000000)>>32; debug(1,"Choosing time - // difference - // with dispersion of %lld us with delta of %lld us",rtus,ji); - conn->local_to_remote_time_difference = l2rtd; + if (conn->local_to_remote_time_difference_measurement_time < lt) + ltd = lt-conn->local_to_remote_time_difference_measurement_time; + else + ltd = -(conn->local_to_remote_time_difference_measurement_time-lt); + + /* + if (ltd) { + debug(1,"Jitter: %" PRId64 " microseconds in %" PRId64 " microseconds.", (ji * (int64_t)1000000)>>32, (ltd * (int64_t)1000000)>>32); + debug(1,"Source clock to local clock drift: %.2f ppm.",((1.0*ji)/ltd)*1000000.0); + } + // uncomment below to print jitter between client's clock and our clock + */ + + /* + if (ji) { + int64_t rtus = (tld*1000000)>>32; + debug(1,"Choosing time difference[%d] with dispersion of %" PRId64 " us with an adjustment of %" PRId64 " us",chosen, rtus, (ji*1000000)>>32); + } + */ + conn->local_to_remote_time_difference = l2rtd; // make this the new local-to-remote-time-difference + conn->local_to_remote_time_difference_measurement_time = lt; // done at this time. + if (first_local_to_remote_time_difference == 0) { first_local_to_remote_time_difference = conn->local_to_remote_time_difference; // first_local_to_remote_time_difference_time = get_absolute_time_in_fp(); } - // int64_t clock_drift; - // int64_t clock_drift_in_usec; - // double clock_drift_ppm = 0.0; - if (first_local_time == 0) { - first_local_time = local_time_chosen; - // first_remote_time = remote_time_chosen; - // clock_drift = 0; + // here, let's try to use the timing pings that were selected because of their short return times to + // estimate a figure for drift between the local clock (x) and the remote clock (y) + + // if we plug in a local interval, we will get back what that is in remote time + + // calculate the line of best fit for relating the local time and the remote time + // we will calculate the slope, which is the drift + // see https://www.varsitytutors.com/hotmath/hotmath_help/topics/line-of-best-fit + + double m,drift; + + drift = 0.0; + + uint64_t y_bar = 0; // remote timestamp average + uint64_t x_bar = 0; // local timestamp average + int sample_count = 0; + + + for (cc = 0; cc < conn->time_ping_count; cc++) + if ((conn->time_pings[cc].chosen) && (conn->time_pings[cc].sequence_number>9)) { // wait for a minute or so.... + y_bar += (conn->time_pings[cc].remote_time>>12); //precision is down to 1/4th of a microsecond + x_bar += (conn->time_pings[cc].local_time>>12); + sample_count++; + } + + y_bar = y_bar/sample_count; + x_bar = x_bar/sample_count; + + int64_t xid, yid; + int64_t mtl, mbl; + mtl=0;mbl=0; + for (cc = 0; cc < conn->time_ping_count; cc++) + if ((conn->time_pings[cc].chosen) && (conn->time_pings[cc].sequence_number>9)) { + + uint64_t slt = conn->time_pings[cc].local_time>>12; + if (slt > x_bar) + xid = slt - x_bar; + else + xid = -(x_bar - slt); + + uint64_t srt = conn->time_pings[cc].remote_time>>12; + if (srt > y_bar) + yid = srt - y_bar; + else + yid = -(y_bar - srt); + + mtl = mtl + xid*yid; + mbl = mbl + xid*xid; + } + if (sample_count > 2) { + conn->local_to_remote_time_gradient = (1.0*mtl)/mbl; + // debug(1,"Drift is %12.2f ppm, based on %d samples.",(1.0-conn->local_to_remote_time_gradient)*1000000,sample_count); } else { - // uint64_t local_time_change = local_time_chosen - first_local_time; - // uint64_t remote_time_change = remote_time_chosen - first_remote_time; - - /* - if (remote_time_change >= local_time_change) - clock_drift = remote_time_change - local_time_change; - else - clock_drift = -(local_time_change - remote_time_change); - */ - /* - if (clock_drift >= 0) - clock_drift_in_usec = (clock_drift * 1000000) >> 32; - else - clock_drift_in_usec = -(((-clock_drift) * 1000000) >> 32); - */ - - // clock_drift_ppm = (1.0 * clock_drift_in_usec) / (local_time_change >> 32); + conn->local_to_remote_time_gradient = 1.0; } - - int64_t source_drift_usec; - if (conn->play_segment_reference_frame != 0) { - int64_t reference_timestamp; - uint64_t reference_timestamp_time, remote_reference_timestamp_time; - get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, - &remote_reference_timestamp_time, conn); - uint64_t frame_difference = 0; - if (reference_timestamp >= conn->play_segment_reference_frame) - frame_difference = - (uint64_t)reference_timestamp - (uint64_t)conn->play_segment_reference_frame; - else // rollover - frame_difference = (uint64_t)reference_timestamp + 0x100000000 - - (uint64_t)conn->play_segment_reference_frame; - uint64_t frame_time_difference_calculated = - (((uint64_t)frame_difference << 32) / 44100); - uint64_t frame_time_difference_actual = - remote_reference_timestamp_time - - conn->play_segment_reference_frame_remote_time; // this is all done by reference - // to - // the - // sources' system clock - // debug(1,"%llu frames since play started, %llu usec calculated, %llu usec - // actual",frame_difference, (frame_time_difference_calculated*1000000)>>32, - // (frame_time_difference_actual*1000000)>>32); - if (frame_time_difference_calculated >= - frame_time_difference_actual) // i.e. if the time it should have taken to send the - // packets is greater than the actual time difference - // measured on the source clock - // then the source DAC's clock is running fast relative to the source system clock - source_drift_usec = frame_time_difference_calculated - frame_time_difference_actual; - else - // otherwise the source DAC's clock is running slow relative to the source system - // clock - source_drift_usec = - -(frame_time_difference_actual - frame_time_difference_calculated); - } else - source_drift_usec = 0; - source_drift_usec = (source_drift_usec * 1000000) >> 32; // turn it to microseconds - - // long current_delay = 0; - // if (config.output->delay) { - // config.output->delay(¤t_delay); - //} - // Useful for troubleshooting: - // debug(1, "clock_drift_ppm %f\tchosen %5d\tsource_drift_usec - // %10.1lld\treturn_time_in_usec - // %10.1llu", - // clock_drift_ppm, - // chosen, - //(session_corrections*1000000)/44100, - // current_delay, - // source_drift_usec, - // buffer_occupancy, - //(return_time*1000000)>>32); } else { debug(2, "Time ping turnaround time: %lld us -- it looks like a timing ping was lost.", (return_time * 1000000) >> 32); @@ -902,21 +956,103 @@ void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time, // types okay debug_mutex_lock(&conn->reference_time_mutex, 1000, 1); *timestamp = conn->reference_timestamp; - *timestamp_time = conn->reference_timestamp_time; + *remote_timestamp_time = conn->remote_reference_timestamp_time; + *timestamp_time = conn->remote_reference_timestamp_time - local_to_remote_time_difference_now(conn); // if ((*timestamp == 0) && (*timestamp_time == 0)) { // debug(1,"Reference timestamp is invalid."); //} - *remote_timestamp_time = conn->remote_reference_timestamp_time; debug_mutex_unlock(&conn->reference_time_mutex, 3); } void clear_reference_timestamp(rtsp_conn_info *conn) { debug_mutex_lock(&conn->reference_time_mutex, 1000, 1); conn->reference_timestamp = 0; - conn->reference_timestamp_time = 0; + conn->remote_reference_timestamp_time = 0; debug_mutex_unlock(&conn->reference_time_mutex, 3); } +int have_timestamp_timing_information(rtsp_conn_info *conn) { + if (conn->reference_timestamp==0) + return 0; + else + return 1; +} + +int sanitised_source_rate_information(int64_t *frames, uint64_t *time, rtsp_conn_info *conn) { + int result = 0; + *frames = conn->input_rate; + *time = (uint64_t)(0x100000000); // one second in fp form + int64_t local_frames = conn->reference_to_previous_frame_difference; + uint64_t local_time = conn->reference_to_previous_time_difference; + if ((local_frames == 0) || (local_time == 0)) { + result = 1; + } else { + double calculated_frame_rate = ((1.0*local_frames)/local_time)*(uint64_t)0x100000000; + if (((calculated_frame_rate/conn->input_rate) > 1.001) || ((calculated_frame_rate/conn->input_rate) < 0.999)) { + result = 1; + } else { + *frames = local_frames; + *time = local_time; + result = 0; + } + } + return result; +} + +int frame_to_local_time(int64_t timestamp, uint64_t *time, rtsp_conn_info *conn) { + debug_mutex_lock(&conn->reference_time_mutex, 1000, 1); + int result = 0; + uint64_t time_difference; + int64_t frame_difference; + result = sanitised_source_rate_information(&frame_difference,&time_difference,conn); + + int64_t timestamp_interval = timestamp - conn->reference_timestamp; // we could be dealing with multiples of 44100 (nominally) + // debug(1, "Timestamp interval: %" PRId64 " frames with reference timestamp %" PRId64 ".",timestamp_interval,conn->reference_timestamp); + uint64_t timestamp_interval_time; + uint64_t remote_time_of_timestamp; + if (timestamp_interval>=0) { + timestamp_interval_time = (timestamp_interval * time_difference)/frame_difference; // this is the nominal time, based on the fps specified between current and previous sync frame. + remote_time_of_timestamp = conn->remote_reference_timestamp_time + timestamp_interval_time; // based on the reference timestamp time plus the time interval calculated based on the specified fps. + } else { + timestamp_interval_time = ((-timestamp_interval) * time_difference)/frame_difference; // this is the nominal time, based on the fps specified between current and previous sync frame. + remote_time_of_timestamp = conn->remote_reference_timestamp_time - timestamp_interval_time; // based on the reference timestamp time plus the time interval calculated based on the specified fps. + } + *time = remote_time_of_timestamp - local_to_remote_time_difference_now(conn); + debug_mutex_unlock(&conn->reference_time_mutex, 3); + return result; +} + +int local_time_to_frame(uint64_t time, int64_t *frame, rtsp_conn_info *conn) { + debug_mutex_lock(&conn->reference_time_mutex, 1000, 1); + int result = 0; + + uint64_t time_difference; + int64_t frame_difference; + result = sanitised_source_rate_information(&frame_difference,&time_difference,conn); + + // first, get from [local] time to remote time. + uint64_t remote_time = time + local_to_remote_time_difference_now(conn); + // next, get the remote time interval from the remote_time to the reference time + uint64_t time_interval; + + if (remote_time >= conn->remote_reference_timestamp_time) + time_interval = remote_time - conn->remote_reference_timestamp_time; + else + time_interval = conn->remote_reference_timestamp_time - remote_time; + + // now, convert the remote time interval into frames + int64_t frame_interval = (time_interval * frame_difference)/time_difference; + if (remote_time >= conn->remote_reference_timestamp_time) { + // debug(1,"Frame interval is %" PRId64 " frames.",frame_interval); + *frame = conn->reference_timestamp+frame_interval; + } else { + // debug(1,"Frame interval is %" PRId64 " frames.",-frame_interval); + *frame = conn->reference_timestamp-frame_interval; + } + debug_mutex_unlock(&conn->reference_time_mutex, 3); + return result; +} + void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) { if (conn->rtp_running) { // if (!request_sent) { diff --git a/rtp.h b/rtp.h index 743cff52..13bf2b3f 100644 --- a/rtp.h +++ b/rtp.h @@ -21,4 +21,13 @@ void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time, uint64_t *remote_timestamp_time, rtsp_conn_info *conn); void clear_reference_timestamp(rtsp_conn_info *conn); +int have_timestamp_timing_information(rtsp_conn_info *conn); + +int get_frame_play_time(int64_t timestamp, int sample_ratio, uint64_t *time_to_play); + +int frame_to_local_time(int64_t timestamp, uint64_t *time, rtsp_conn_info *conn); +int local_time_to_frame(uint64_t time, int64_t *frame, rtsp_conn_info *conn); + +int sanitised_source_rate_information(int64_t *frames, uint64_t *time, rtsp_conn_info *conn); + #endif // _RTP_H From 78f1cd614c1a31d1c0edd6e92aca1ccc683cdd04 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 6 Aug 2018 09:48:36 +0100 Subject: [PATCH 053/569] Allow for different output rates in the new calculations --- player.c | 10 +++++----- rtp.c | 16 ++++++++++------ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/player.c b/player.c index 51a59ef4..4501b5b2 100644 --- a/player.c +++ b/player.c @@ -973,10 +973,10 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { frame_to_local_time(conn->first_packet_timestamp +conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate),&should_be_time,conn); if(should_be_time>=conn->first_packet_time_to_play) { - if ((((should_be_time-conn->first_packet_time_to_play)*1000000)>>32)>10) + if ((((should_be_time-conn->first_packet_time_to_play)*1000000)>>32)>10*conn->output_sample_ratio) debug(1,"New time for first packet timestamp %" PRId64 " is later than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((should_be_time-conn->first_packet_time_to_play)*1000000)>>32); } else { - if ((((conn->first_packet_time_to_play-should_be_time)*1000000)>>32)>10) + if ((((conn->first_packet_time_to_play-should_be_time)*1000000)>>32)>10*conn->output_sample_ratio) debug(1,"New time for first packet timestamp %" PRId64 " is earlier than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((conn->first_packet_time_to_play-should_be_time)*1000000)>>32); } @@ -1014,10 +1014,10 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { frame_to_local_time(conn->first_packet_timestamp +conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate),&should_be_time,conn); if(should_be_time>=conn->first_packet_time_to_play) { - if ((((should_be_time-conn->first_packet_time_to_play)*1000000)>>32)>50) + if ((((should_be_time-conn->first_packet_time_to_play)*1000000)>>32)>50*conn->output_sample_ratio) debug(1,"New time for recalculated first packet timestamp %" PRId64 " is later than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((should_be_time-conn->first_packet_time_to_play)*1000000)>>32); } else { - if ((((conn->first_packet_time_to_play-should_be_time)*1000000)>>32)>50) + if ((((conn->first_packet_time_to_play-should_be_time)*1000000)>>32)>50*conn->output_sample_ratio) debug(1,"New time for recalculated first packet timestamp %" PRId64 " is earlier than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((conn->first_packet_time_to_play-should_be_time)*1000000)>>32); } @@ -2065,7 +2065,7 @@ void *player_thread_func(void *arg) { int64_t should_be_frame; local_time_to_frame(local_time_now,&should_be_frame,conn); - if (abs(td_in_frames + rt-should_be_frame)>10) + if (abs(td_in_frames + rt-should_be_frame)>10*conn->output_sample_ratio) debug(1,"Difference between old and new frame number is %" PRId64 " frames.",td_in_frames + rt - should_be_frame); // this is the actual delay, including the latency we actually want, which will // fluctuate a good bit about a potentially rising or falling trend. diff --git a/rtp.c b/rtp.c index ef053e9b..67be2fd5 100644 --- a/rtp.c +++ b/rtp.c @@ -999,6 +999,9 @@ int sanitised_source_rate_information(int64_t *frames, uint64_t *time, rtsp_conn return result; } +// we assume here that the timestamp is a timestamp calculated at the output rate, which could be an integer multiple of the input rate +// the reference timestamps are denominated in terms of the input rate + int frame_to_local_time(int64_t timestamp, uint64_t *time, rtsp_conn_info *conn) { debug_mutex_lock(&conn->reference_time_mutex, 1000, 1); int result = 0; @@ -1006,15 +1009,15 @@ int frame_to_local_time(int64_t timestamp, uint64_t *time, rtsp_conn_info *conn) int64_t frame_difference; result = sanitised_source_rate_information(&frame_difference,&time_difference,conn); - int64_t timestamp_interval = timestamp - conn->reference_timestamp; // we could be dealing with multiples of 44100 (nominally) + int64_t timestamp_interval = timestamp - conn->reference_timestamp*conn->output_sample_ratio; // we could be dealing with multiples of 44100 (nominally) // debug(1, "Timestamp interval: %" PRId64 " frames with reference timestamp %" PRId64 ".",timestamp_interval,conn->reference_timestamp); uint64_t timestamp_interval_time; uint64_t remote_time_of_timestamp; if (timestamp_interval>=0) { - timestamp_interval_time = (timestamp_interval * time_difference)/frame_difference; // this is the nominal time, based on the fps specified between current and previous sync frame. + timestamp_interval_time = (timestamp_interval * time_difference)/(frame_difference*conn->output_sample_ratio); // this is the nominal time, based on the fps specified between current and previous sync frame. remote_time_of_timestamp = conn->remote_reference_timestamp_time + timestamp_interval_time; // based on the reference timestamp time plus the time interval calculated based on the specified fps. } else { - timestamp_interval_time = ((-timestamp_interval) * time_difference)/frame_difference; // this is the nominal time, based on the fps specified between current and previous sync frame. + timestamp_interval_time = ((-timestamp_interval) * time_difference)/(frame_difference*conn->output_sample_ratio); // this is the nominal time, based on the fps specified between current and previous sync frame. remote_time_of_timestamp = conn->remote_reference_timestamp_time - timestamp_interval_time; // based on the reference timestamp time plus the time interval calculated based on the specified fps. } *time = remote_time_of_timestamp - local_to_remote_time_difference_now(conn); @@ -1035,19 +1038,20 @@ int local_time_to_frame(uint64_t time, int64_t *frame, rtsp_conn_info *conn) { // next, get the remote time interval from the remote_time to the reference time uint64_t time_interval; + // here, we calculate the time interval, in terms of remote time if (remote_time >= conn->remote_reference_timestamp_time) time_interval = remote_time - conn->remote_reference_timestamp_time; else time_interval = conn->remote_reference_timestamp_time - remote_time; - // now, convert the remote time interval into frames + // now, convert the remote time interval into frames using the frame rate we have observed or which has been nominated int64_t frame_interval = (time_interval * frame_difference)/time_difference; if (remote_time >= conn->remote_reference_timestamp_time) { // debug(1,"Frame interval is %" PRId64 " frames.",frame_interval); - *frame = conn->reference_timestamp+frame_interval; + *frame = (conn->reference_timestamp+frame_interval)*conn->output_sample_ratio; } else { // debug(1,"Frame interval is %" PRId64 " frames.",-frame_interval); - *frame = conn->reference_timestamp-frame_interval; + *frame = (conn->reference_timestamp-frame_interval)*conn->output_sample_ratio; } debug_mutex_unlock(&conn->reference_time_mutex, 3); return result; From 51283d2e07c01b8ca264506c9e20adff05937d16 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 6 Aug 2018 12:33:45 +0100 Subject: [PATCH 054/569] Update some debug messages --- player.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/player.c b/player.c index 4501b5b2..595c1fda 100644 --- a/player.c +++ b/player.c @@ -973,11 +973,11 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { frame_to_local_time(conn->first_packet_timestamp +conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate),&should_be_time,conn); if(should_be_time>=conn->first_packet_time_to_play) { - if ((((should_be_time-conn->first_packet_time_to_play)*1000000)>>32)>10*conn->output_sample_ratio) - debug(1,"New time for first packet timestamp %" PRId64 " is later than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((should_be_time-conn->first_packet_time_to_play)*1000000)>>32); + if ((((should_be_time-conn->first_packet_time_to_play)*1000000)>>32)>10) + debug(2,"New time for first packet timestamp %" PRId64 " is later than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((should_be_time-conn->first_packet_time_to_play)*1000000)>>32); } else { - if ((((conn->first_packet_time_to_play-should_be_time)*1000000)>>32)>10*conn->output_sample_ratio) - debug(1,"New time for first packet timestamp %" PRId64 " is earlier than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((conn->first_packet_time_to_play-should_be_time)*1000000)>>32); + if ((((conn->first_packet_time_to_play-should_be_time)*1000000)>>32)>10) + debug(2,"New time for first packet timestamp %" PRId64 " is earlier than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((conn->first_packet_time_to_play-should_be_time)*1000000)>>32); } // cut over to new calculation scheme @@ -1014,11 +1014,11 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { frame_to_local_time(conn->first_packet_timestamp +conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate),&should_be_time,conn); if(should_be_time>=conn->first_packet_time_to_play) { - if ((((should_be_time-conn->first_packet_time_to_play)*1000000)>>32)>50*conn->output_sample_ratio) - debug(1,"New time for recalculated first packet timestamp %" PRId64 " is later than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((should_be_time-conn->first_packet_time_to_play)*1000000)>>32); + if ((((should_be_time-conn->first_packet_time_to_play)*1000000)>>32)>50) + debug(2,"New time for recalculated first packet timestamp %" PRId64 " is later than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((should_be_time-conn->first_packet_time_to_play)*1000000)>>32); } else { - if ((((conn->first_packet_time_to_play-should_be_time)*1000000)>>32)>50*conn->output_sample_ratio) - debug(1,"New time for recalculated first packet timestamp %" PRId64 " is earlier than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((conn->first_packet_time_to_play-should_be_time)*1000000)>>32); + if ((((conn->first_packet_time_to_play-should_be_time)*1000000)>>32)>50) + debug(2,"New time for recalculated first packet timestamp %" PRId64 " is earlier than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((conn->first_packet_time_to_play-should_be_time)*1000000)>>32); } @@ -1239,10 +1239,10 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if(new_time_to_play>=time_to_play) { if ((((new_time_to_play-time_to_play)*1000000)>>32)>100) - debug(1,"New time for frame %" PRId64 " is later than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((new_time_to_play-time_to_play)*1000000)>>32); + debug(2,"New time for frame %" PRId64 " is later than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((new_time_to_play-time_to_play)*1000000)>>32); } else { if ((((time_to_play-new_time_to_play)*1000000)>>32)>100) - debug(1,"New time for frame %" PRId64 " is earlier than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((time_to_play-new_time_to_play)*1000000)>>32); + debug(2,"New time for frame %" PRId64 " is earlier than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((time_to_play-new_time_to_play)*1000000)>>32); } // cut over to the new calculation system time_to_play = new_time_to_play; From 8b0889ea5d89f4382cdb7f7e1adab0f724a32159 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 6 Aug 2018 17:57:42 +0100 Subject: [PATCH 055/569] Remove bogus possibly uninitialised variable warning. --- audio_alsa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_alsa.c b/audio_alsa.c index 4ab704fe..577c01c2 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -848,7 +848,7 @@ static void start(int i_sample_rate, int i_sample_format) { int delay(long *the_delay) { // snd_pcm_sframes_t is a signed long -- hence the return of a "long" - int reply; + int reply = 0; // debug(3,"audio_alsa delay called."); if (alsa_handle == NULL) { return -ENODEV; From 48d001591af432e0cb217b489bb97d02952837c2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 8 Aug 2018 09:00:41 +0100 Subject: [PATCH 056/569] Turn off "clobbering" warning, as it generates too many false positives at present. --- Makefile.am | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile.am b/Makefile.am index e5f33afd..b8da42b2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -6,14 +6,14 @@ bin_PROGRAMS = shairport-sync shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c loudness.c -AM_CFLAGS = -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\" +AM_CFLAGS = -Wno-multichar -Wall -Wextra -Wno-clobbered -pthread -DSYSCONFDIR=\"$(sysconfdir)\" if BUILD_FOR_FREEBSD - AM_CPPFLAGS = -I/usr/local/include -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\" -O2 + AM_CPPFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -Wno-clobbered -DSYSCONFDIR=\"$(sysconfdir)\" -O2 else if BUILD_FOR_OPENBSD - AM_CPPFLAGS = -I/usr/local/include -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\" + AM_CPPFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -Wno-clobbered -DSYSCONFDIR=\"$(sysconfdir)\" else - AM_CPPFLAGS = -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\" + AM_CPPFLAGS = -Wno-multichar -Wall -Wextra -Wno-clobbered -DSYSCONFDIR=\"$(sysconfdir)\" endif endif From 95bb10a6c36ce2286b4b98037c69b287229ae972 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 8 Aug 2018 09:03:53 +0100 Subject: [PATCH 057/569] Remove or comment out some extraneous code, quieten some debug messages. --- rtp.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/rtp.c b/rtp.c index 67be2fd5..df71f413 100644 --- a/rtp.c +++ b/rtp.c @@ -80,7 +80,7 @@ uint64_t local_to_remote_time_difference_now(rtsp_conn_info *conn) { else drift = -((1.0*(time_since_last_local_to_remote_time_difference_measurement - remote_time_since_last_local_to_remote_time_difference_measurement))/(uint64_t)0x100000000); - double interval_ms = 1.0*(((time_since_last_local_to_remote_time_difference_measurement)*1000)>>32); +// double interval_ms = 1.0*(((time_since_last_local_to_remote_time_difference_measurement)*1000)>>32); // debug(1,"Measurement drift is %.2f microseconds (0x%" PRIx64 " in 64-bit fp) over %.2f milliseconds with drift of %.2f ppm.",drift*1000000,(uint64_t)(drift*(uint64_t)0x100000000),interval_ms,(1.0-conn->local_to_remote_time_gradient)*1000000); // return conn->local_to_remote_time_difference + (uint64_t)(drift*(uint64_t 0x100000000)); return conn->local_to_remote_time_difference + (uint64_t)(drift*(uint64_t)0x100000000); @@ -360,7 +360,7 @@ void *rtp_control_receiver(void *arg) { // this is for debugging uint64_t old_remote_reference_time = conn->remote_reference_timestamp_time; int64_t old_reference_timestamp = conn->reference_timestamp; - int64_t old_latency_delayed_timestamp = conn->latency_delayed_timestamp; + // int64_t old_latency_delayed_timestamp = conn->latency_delayed_timestamp; conn->remote_reference_timestamp_time = remote_time_of_sync; //conn->reference_timestamp_time = @@ -375,7 +375,7 @@ void *rtp_control_receiver(void *arg) { else conn->reference_to_previous_frame_difference = sync_rtp_timestamp - old_reference_timestamp; - int64_t delayed_frame_difference = rtp_timestamp_less_latency - old_latency_delayed_timestamp; + // int64_t delayed_frame_difference = rtp_timestamp_less_latency - old_latency_delayed_timestamp; /* if (old_remote_reference_time) @@ -648,10 +648,11 @@ void *rtp_timing_receiver(void *arg) { // debug(1,"Record %d has the lowest dispersion with %0.2f us dispersion.",chosen,1.0*((tld * 1000000) >> 32)); conn->time_pings[chosen].chosen = 1; // record the fact that it has been used for timing + /* // calculate the jitter -- the absolute time between the current local_to_remote_time_difference and the new one and add it to the total jitter count int64_t ji; int64_t ltd =0; // local time difference for the jitter - + if (conn->time_ping_count > 1) { if (l2rtd > conn->local_to_remote_time_difference) { local_to_remote_time_jitter = @@ -664,21 +665,17 @@ void *rtp_timing_receiver(void *arg) { } local_to_remote_time_jitter_count += 1; } - if (conn->local_to_remote_time_difference_measurement_time < lt) ltd = lt-conn->local_to_remote_time_difference_measurement_time; else ltd = -(conn->local_to_remote_time_difference_measurement_time-lt); - /* if (ltd) { debug(1,"Jitter: %" PRId64 " microseconds in %" PRId64 " microseconds.", (ji * (int64_t)1000000)>>32, (ltd * (int64_t)1000000)>>32); debug(1,"Source clock to local clock drift: %.2f ppm.",((1.0*ji)/ltd)*1000000.0); } // uncomment below to print jitter between client's clock and our clock - */ - - /* + if (ji) { int64_t rtus = (tld*1000000)>>32; debug(1,"Choosing time difference[%d] with dispersion of %" PRId64 " us with an adjustment of %" PRId64 " us",chosen, rtus, (ji*1000000)>>32); @@ -701,10 +698,6 @@ void *rtp_timing_receiver(void *arg) { // we will calculate the slope, which is the drift // see https://www.varsitytutors.com/hotmath/hotmath_help/topics/line-of-best-fit - double m,drift; - - drift = 0.0; - uint64_t y_bar = 0; // remote timestamp average uint64_t x_bar = 0; // local timestamp average int sample_count = 0; From 4590b08e62eac5b48fd8e70eef6743109b99e27f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 8 Aug 2018 09:04:58 +0100 Subject: [PATCH 058/569] Fix a bug whereby libsoxr-required code wasn't included, causing soxr interpolation to be disabled. --- dbus-service.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/dbus-service.c b/dbus-service.c index 90e68a08..03afadb0 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -662,21 +662,31 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name config.loudness_reference_volume_db); #ifdef HAVE_APPLE_ALAC - if (config.use_apple_decoder == 0) + if (config.use_apple_decoder == 0) { shairport_sync_set_alacdecoder(SHAIRPORT_SYNC(shairportSyncSkeleton), "hammerton"); - else + debug(1, ">> ALACDecoder set to \"hammerton\""); + } else { shairport_sync_set_alacdecoder(SHAIRPORT_SYNC(shairportSyncSkeleton), "apple"); + debug(1, ">> ALACDecoder set to \"apple\""); + } #else shairport_sync_set_alacdecoder(SHAIRPORT_SYNC(shairportSyncSkeleton), "hammerton"); + debug(1, ">> ALACDecoder set to \"hammerton\""); + #endif -#ifdef HAVE_SOXR - if (config.packet_stuffing == ST_basic) +#ifdef HAVE_LIBSOXR + if (config.packet_stuffing == ST_basic) { shairport_sync_set_interpolation(SHAIRPORT_SYNC(shairportSyncSkeleton), "basic"); - else + debug(1, ">> interpolation set to \"basic\" (soxr support built in)"); + } else { shairport_sync_set_interpolation(SHAIRPORT_SYNC(shairportSyncSkeleton), "soxr"); + debug(1, ">> interpolation set to \"soxr\""); + } #else shairport_sync_set_interpolation(SHAIRPORT_SYNC(shairportSyncSkeleton), "basic"); + debug(1, ">> interpolation set to \"basic\" (no soxr support)"); + #endif if (config.volume_control_profile == VCP_standard) From 63c447582f0a6ff17f7e01f6e22f8f3db5f2a455 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 8 Aug 2018 09:28:34 +0100 Subject: [PATCH 059/569] Fix a read-write mutex locking issue in the metadata hub. --- metadata_hub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metadata_hub.c b/metadata_hub.c index 3ddbcb4d..cf19aa85 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -151,7 +151,7 @@ void metadata_hub_modify_prolog(void) { // debug(1, "locking metadata hub for writing"); if (pthread_rwlock_trywrlock(&metadata_hub_re_lock) != 0) { debug(2, "Metadata_hub write lock is already taken -- must wait."); - pthread_rwlock_unlock(&metadata_hub_re_lock); + pthread_rwlock_wrlock(&metadata_hub_re_lock); debug(2, "Okay -- acquired the metadata_hub write lock."); } } From f2ab25d01a7e5dc755eac683407cd903e4a123f4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 8 Aug 2018 09:30:38 +0100 Subject: [PATCH 060/569] Fix a read-write mutex locking issue in the metadata hub, fix a libsoxr related dbus bug, switch over to better (we hope) timing measurement, giving smoother usage of interpolation. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 54c0e6fd..888f2285 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.3d1], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d2], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From b0445b143be83548138c479207c02bc22f01ae70 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 8 Aug 2018 09:36:17 +0100 Subject: [PATCH 061/569] Update RELEASENOTES.md --- RELEASENOTES.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 53305b6a..06bb2ce2 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,11 @@ +Version 3.3d2 +==== +Modify the code that synchronises the Shairport Sync system's clock with the source clock to try to take account of the sources's true rate, which (oddly, e.g. iTunes on a Mac) might not be exactly 44,100 fps. Also try to interpolate for the measured drift between the standard three-second timing snapshots. These changes make a very slight difference from time to time, of the order of microseconds, and it's not clear yet how reliable the drift interpolation is. + +**Bug Fixes** +* Fix a bug in the `dbus` native interface which would silently switch `soxr` interpolation to `basic`. +* Fix a mutex lock bug in the metadata hub. No known effects. + Version 3.3d1 ==== Internal changes are being made for version 3.3 to avoid using `SIGUSR1` and `pthread_kill` to stop threads; the standard `pthread_cancel` and friends are being used instead. This should lead to more reliable and orderly cancellation of sessions and threads. However, it is quite a complex change, so bugs may have been introduced or reactivated. Lots of testing needed. From 25d02d183ea556bb5a57699e7497152fce6ab882 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 13 Aug 2018 14:04:22 +0100 Subject: [PATCH 062/569] Use CXXFLAGS etc. to ensure they can be overridden properly. Turn off ABI-changed warnings. --- Makefile.am | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile.am b/Makefile.am index b8da42b2..83fc2132 100644 --- a/Makefile.am +++ b/Makefile.am @@ -6,14 +6,14 @@ bin_PROGRAMS = shairport-sync shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c loudness.c -AM_CFLAGS = -Wno-multichar -Wall -Wextra -Wno-clobbered -pthread -DSYSCONFDIR=\"$(sysconfdir)\" +AM_CFLAGS = -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\" if BUILD_FOR_FREEBSD - AM_CPPFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -Wno-clobbered -DSYSCONFDIR=\"$(sysconfdir)\" -O2 + AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\" -O2 else if BUILD_FOR_OPENBSD - AM_CPPFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -Wno-clobbered -DSYSCONFDIR=\"$(sysconfdir)\" + AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\" else - AM_CPPFLAGS = -Wno-multichar -Wall -Wextra -Wno-clobbered -DSYSCONFDIR=\"$(sysconfdir)\" + AM_CXXFLAGS = -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\" endif endif @@ -68,7 +68,7 @@ endif if USE_CONVOLUTION shairport_sync_SOURCES += FFTConvolver/AudioFFT.cpp FFTConvolver/FFTConvolver.cpp FFTConvolver/Utilities.cpp FFTConvolver/convolver.cpp -AM_CXXFLAGS = -std=c++11 +AM_CXXFLAGS += -std=c++11 endif if USE_DNS_SD From df18e3a017ce2e744ab104de171e8934eba35e70 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 13 Aug 2018 14:05:14 +0100 Subject: [PATCH 063/569] Add the ability to read and change the stuffing threshold --- dbus-service.c | 17 +++++++++++++++++ org.gnome.ShairportSync.xml | 1 + 2 files changed, 18 insertions(+) diff --git a/dbus-service.c b/dbus-service.c index 03afadb0..59dff59e 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -404,6 +404,19 @@ gboolean notify_loudness_threshold_callback(ShairportSync *skeleton, return TRUE; } +gboolean notify_drift_tolerance_callback(ShairportSync *skeleton, + __attribute__((unused)) gpointer user_data) { + gdouble dt = shairport_sync_get_drift_tolerance(skeleton); + if ((dt >= 0.0) && (dt <= 2.0)) { + debug(1, "Setting drift tolerance to %f.", dt); + config.tolerance = dt; + } else { + debug(1, "Invalid drift tolerance: %f. Ignored.", dt); + shairport_sync_set_drift_tolerance(skeleton, config.tolerance); + } + return TRUE; +} + gboolean notify_alacdecoder_callback(ShairportSync *skeleton, __attribute__((unused)) gpointer user_data) { char *th = (char *)shairport_sync_get_alacdecoder(skeleton); @@ -602,6 +615,8 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name G_CALLBACK(notify_loudness_filter_active_callback), NULL); g_signal_connect(shairportSyncSkeleton, "notify::loudness-threshold", G_CALLBACK(notify_loudness_threshold_callback), NULL); + g_signal_connect(shairportSyncSkeleton, "notify::drift-tolerance", + G_CALLBACK(notify_drift_tolerance_callback), NULL); g_signal_connect(shairportSyncSkeleton, "handle-quit", G_CALLBACK(on_handle_quit), NULL); @@ -660,6 +675,8 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name shairport_sync_set_loudness_threshold(SHAIRPORT_SYNC(shairportSyncSkeleton), config.loudness_reference_volume_db); + shairport_sync_set_drift_tolerance(SHAIRPORT_SYNC(shairportSyncSkeleton), + config.tolerance); #ifdef HAVE_APPLE_ALAC if (config.use_apple_decoder == 0) { diff --git a/org.gnome.ShairportSync.xml b/org.gnome.ShairportSync.xml index 1b734bff..44d90a27 100644 --- a/org.gnome.ShairportSync.xml +++ b/org.gnome.ShairportSync.xml @@ -4,6 +4,7 @@ + From a8cfc9915f05e3fcb8dc725dd89f0197bb4ee234 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 13 Aug 2018 14:06:40 +0100 Subject: [PATCH 064/569] display the number of samples used to calculcate drift, clean up the settings for the number of samples to skip at the start and for the minimum number of samples to use. Store 64 samples. --- player.c | 5 +++-- player.h | 3 ++- rtp.c | 21 ++++++++++++++------- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/player.c b/player.c index 595c1fda..f2e1a7fd 100644 --- a/player.c +++ b/player.c @@ -2416,7 +2416,8 @@ void *player_thread_func(void *arg) { "%*d," /* max buffer occupancy */ "%*.2f," /* input frame rate */ "%*.2f," /* output frame rate */ - "%*.2f", /* client clock to local clock drift in ppm */ + "%*.2f," /* output frame rate */ + "%*d", /* sample count */ 10, 1000 * moving_average_sync_error / config.output_rate, 10, moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), @@ -2425,7 +2426,7 @@ void *player_thread_func(void *arg) { 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, - maximum_buffer_occupancy, 11, conn->input_frame_rate, 11, conn->frame_rate , 10, (1.0-conn->local_to_remote_time_gradient)*1000000); + maximum_buffer_occupancy, 11, conn->input_frame_rate, 11, conn->frame_rate , 10, (1.0-conn->local_to_remote_time_gradient)*1000000, 6, conn->local_to_remote_time_gradient_sample_count); } else { inform("%*.2f," /* Sync error in milliseconds */ "%*d," /* total packets */ diff --git a/player.h b/player.h index 5402a18f..a7d961a5 100644 --- a/player.h +++ b/player.h @@ -24,7 +24,7 @@ #include "alac.h" #include "audio.h" -#define time_ping_history 32 +#define time_ping_history 64 // at 1 per three seconds, approximately three minutes of records typedef struct time_ping_record { uint64_t local_to_remote_difference; @@ -207,6 +207,7 @@ typedef struct { pthread_mutex_t reference_time_mutex; double local_to_remote_time_gradient; // if no drift, this would be exactly 1.0; likely it's slightly above or below. + int local_to_remote_time_gradient_sample_count; // the number of samples used to calculate the gradient uint64_t local_to_remote_time_difference; // used to switch between local and remote clocks uint64_t local_to_remote_time_difference_measurement_time; // when the above was calculated diff --git a/rtp.c b/rtp.c index df71f413..96bb0fcf 100644 --- a/rtp.c +++ b/rtp.c @@ -702,9 +702,12 @@ void *rtp_timing_receiver(void *arg) { uint64_t x_bar = 0; // local timestamp average int sample_count = 0; - + // approximate time in seconds to let the system settle down + const int settling_time = 60; + // number of points to have for calculating a valid drift + const int sample_point_minimum = 8; for (cc = 0; cc < conn->time_ping_count; cc++) - if ((conn->time_pings[cc].chosen) && (conn->time_pings[cc].sequence_number>9)) { // wait for a minute or so.... + if ((conn->time_pings[cc].chosen) && (conn->time_pings[cc].sequence_number>(settling_time/3))) { // wait for a approximate settling time y_bar += (conn->time_pings[cc].remote_time>>12); //precision is down to 1/4th of a microsecond x_bar += (conn->time_pings[cc].local_time>>12); sample_count++; @@ -717,7 +720,7 @@ void *rtp_timing_receiver(void *arg) { int64_t mtl, mbl; mtl=0;mbl=0; for (cc = 0; cc < conn->time_ping_count; cc++) - if ((conn->time_pings[cc].chosen) && (conn->time_pings[cc].sequence_number>9)) { + if ((conn->time_pings[cc].chosen) && (conn->time_pings[cc].sequence_number>(settling_time/3))) { uint64_t slt = conn->time_pings[cc].local_time>>12; if (slt > x_bar) @@ -734,8 +737,9 @@ void *rtp_timing_receiver(void *arg) { mtl = mtl + xid*yid; mbl = mbl + xid*xid; } - if (sample_count > 2) { - conn->local_to_remote_time_gradient = (1.0*mtl)/mbl; + conn->local_to_remote_time_gradient_sample_count = sample_count; + if (sample_count > sample_point_minimum) { + conn->local_to_remote_time_gradient = (1.0*mtl)/mbl; // debug(1,"Drift is %12.2f ppm, based on %d samples.",(1.0-conn->local_to_remote_time_gradient)*1000000,sample_count); } else { conn->local_to_remote_time_gradient = 1.0; @@ -971,13 +975,16 @@ int have_timestamp_timing_information(rtsp_conn_info *conn) { return 1; } +// set this to zero to use the rates supplied by the sources, which might not always be completely right... +const int use_nominal_rate = 1; // specify whether to use the nominal input rate, usually 44100 fps + int sanitised_source_rate_information(int64_t *frames, uint64_t *time, rtsp_conn_info *conn) { int result = 0; - *frames = conn->input_rate; + *frames = conn->input_rate; *time = (uint64_t)(0x100000000); // one second in fp form int64_t local_frames = conn->reference_to_previous_frame_difference; uint64_t local_time = conn->reference_to_previous_time_difference; - if ((local_frames == 0) || (local_time == 0)) { + if ((local_frames == 0) || (local_time == 0) || (use_nominal_rate)) { result = 1; } else { double calculated_frame_rate = ((1.0*local_frames)/local_time)*(uint64_t)0x100000000; From 2f588562686c8eaeee794a510b49f4eba2471bf5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 13 Aug 2018 14:44:22 +0100 Subject: [PATCH 065/569] Add source rate calculation based on source frame and timing information, calculated from start of play session. --- player.c | 15 +++++++++++---- player.h | 5 +++++ rtp.c | 11 +++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/player.c b/player.c index f2e1a7fd..0d867069 100644 --- a/player.c +++ b/player.c @@ -1785,9 +1785,10 @@ void *player_thread_func(void *arg) { "min DAC queue size, " "min buffer occupancy, " "max buffer occupancy, " - "input frames per second, " + "source frames per second, " + "average input frames per second, " "output frames per second, " - "drift client vs local clock in ppm"); + "client:local clock drift in ppm"); } else { inform("sync error in milliseconds, " "total packets, " @@ -2414,7 +2415,8 @@ void *player_thread_func(void *arg) { "%*lli," /* min DAC queue size */ "%*d," /* min buffer occupancy */ "%*d," /* max buffer occupancy */ - "%*.2f," /* input frame rate */ + "%*.2f," /* remote frame rate */ + "%*.2f," /* actual (average) input frame rate */ "%*.2f," /* output frame rate */ "%*.2f," /* output frame rate */ "%*d", /* sample count */ @@ -2426,7 +2428,12 @@ void *player_thread_func(void *arg) { 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, - maximum_buffer_occupancy, 11, conn->input_frame_rate, 11, conn->frame_rate , 10, (1.0-conn->local_to_remote_time_gradient)*1000000, 6, conn->local_to_remote_time_gradient_sample_count); + maximum_buffer_occupancy, + 11, conn->remote_frame_rate, + 11, conn->input_frame_rate, + 11, conn->frame_rate , + 10, (1.0-conn->local_to_remote_time_gradient)*1000000, + 6, conn->local_to_remote_time_gradient_sample_count); } else { inform("%*.2f," /* Sync error in milliseconds */ "%*d," /* total packets */ diff --git a/player.h b/player.h index a7d961a5..783c5165 100644 --- a/player.h +++ b/player.h @@ -191,6 +191,11 @@ typedef struct { uint64_t remote_reference_timestamp_time; + // used as the initials values for calculating the rate at which the source thinks it's sending frames + int64_t initial_reference_timestamp; + uint64_t initial_reference_time; + double remote_frame_rate; + // the ratio of the following should give us the operating rate, nominally 44,100 int64_t reference_to_previous_frame_difference; uint64_t reference_to_previous_time_difference; diff --git a/rtp.c b/rtp.c index 96bb0fcf..6747724f 100644 --- a/rtp.c +++ b/rtp.c @@ -356,6 +356,17 @@ void *rtp_control_receiver(void *arg) { } debug_mutex_lock(&conn->reference_time_mutex, 1000, 1); + + if (conn->initial_reference_time==0) { + conn->initial_reference_time = remote_time_of_sync; + conn->initial_reference_timestamp = sync_rtp_timestamp; + } else { + uint64_t remote_frame_time_interval = conn->remote_reference_timestamp_time - conn->initial_reference_time; // here, this should never be zero + if (remote_frame_time_interval) { + conn->remote_frame_rate = (1.0 * (conn->reference_timestamp - conn->initial_reference_timestamp)) / remote_frame_time_interval; // an IEEE double calculation with two 64-bit integers + conn->remote_frame_rate = conn->remote_frame_rate * (uint64_t)0x100000000; // this should just change the [binary] exponent in the IEEE FP representation; the mantissa should be unaffected. + } + } // this is for debugging uint64_t old_remote_reference_time = conn->remote_reference_timestamp_time; From 519135734f88799088f23ba90d4d37d032974209 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 14 Aug 2018 12:13:02 +0100 Subject: [PATCH 066/569] Calculate actual and nominal input frame rates more accurately, allowing for flush and resume events. --- player.c | 40 ++++++++++++++++++++++++++-------------- player.h | 3 ++- rtp.c | 49 ++++++++++++++++++++++++++++--------------------- 3 files changed, 56 insertions(+), 36 deletions(-) diff --git a/player.c b/player.c index 0d867069..ed8c6722 100644 --- a/player.c +++ b/player.c @@ -492,9 +492,11 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp if ((conn->flush_rtp_timestamp != 0) && (ltimestamp <= conn->flush_rtp_timestamp)) { debug(3, - "Dropping flushed packet in player_put_packet, seqno %u, timestamp %lld, flushing to " - "timestamp: %lld.", - seqno, ltimestamp, conn->flush_rtp_timestamp); + "Dropping flushed packet in player_put_packet, seqno %u, timestamp %" PRId64 ", flushing to " + "timestamp: %" PRId64 ".", + seqno, ltimestamp, conn->flush_rtp_timestamp); + conn->initial_reference_time = 0; + conn->initial_reference_timestamp = 0; } else { if ((conn->flush_rtp_timestamp != 0x0) && (ltimestamp > conn->flush_rtp_timestamp)) // if we have gone past the flush boundary time @@ -525,12 +527,13 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp if (conn->ab_write == seqno) { // expected packet uint64_t reception_time = get_absolute_time_in_fp(); - - if ((conn->packet_count_since_flush>=500) && (conn->packet_count_since_flush<=510)) { - conn->frames_inward_measurement_start_time = reception_time; - conn->frames_inward_frames_received_at_measurement_start_time = timestamp; - conn->input_frame_rate_status = 1; // valid now - // debug(1,"frames_inward_measurement_start_time set"); + 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 = reception_time; + conn->frames_inward_frames_received_at_measurement_start_time = timestamp; + conn->input_frame_rate_starting_point_is_valid = 1; // valid now + debug(1,"input_frame_rate_starting_point_is_valid set"); + } } conn->frames_inward_measurement_time = reception_time; @@ -876,12 +879,14 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if ((conn->flush_rtp_timestamp != 0) && (curframe->timestamp <= conn->flush_rtp_timestamp)) { - debug(1, "Dropping flushed packet seqno %u, timestamp %lld", curframe->sequence_number, + debug(2, "Dropping flushed packet in buffer_get_frame seqno %u, timestamp %" PRId64 ".", curframe->sequence_number, curframe->timestamp); curframe->ready = 0; curframe->resend_level = 0; flush_limit++; conn->ab_read = SUCCESSOR(conn->ab_read); + conn->initial_reference_time = 0; + conn->initial_reference_timestamp = 0; } if (curframe->timestamp > conn->flush_rtp_timestamp) conn->flush_rtp_timestamp = 0; @@ -916,7 +921,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { curframe->timestamp; // we will keep buffering until we are // supposed to start playing this have_sent_prefiller_silence = 0; - + conn->packet_stream_established = 1; + // debug(1, "First packet timestamp is %" PRId64 ".", conn->first_packet_timestamp); // say we have started playing here @@ -1661,7 +1667,7 @@ void *player_thread_func(void *arg) { conn->frame_rate_status = 0; conn->input_frame_rate = 0.0; - conn->input_frame_rate_status = 0; + conn->input_frame_rate_starting_point_is_valid = 0; conn->buffer_occupancy = 0; @@ -2366,13 +2372,15 @@ void *player_thread_func(void *arg) { // here, calculate the input and output frame rates, where possible, even if statistics have not been requested // this is to calculate them in case they are needed by the D-Bus interface or elsewhere. - if (conn->input_frame_rate_status) { + if (conn->input_frame_rate_starting_point_is_valid) { uint64_t elapsed_reception_time, frames_received; elapsed_reception_time = conn->frames_inward_measurement_time - conn->frames_inward_measurement_start_time; frames_received = conn->frames_inward_frames_received_at_measurement_time - conn->frames_inward_frames_received_at_measurement_start_time; conn->input_frame_rate = (1.0 * frames_received) / elapsed_reception_time; // an IEEE double calculation with two 64-bit integers conn->input_frame_rate = conn->input_frame_rate * (uint64_t)0x100000000; // this should just change the [binary] exponent in the IEEE FP representation; the mantissa should be unaffected. - } + } else { + conn->input_frame_rate = 0.0; + } if ((config.output->delay) && (config.no_sync == 0) && (config.output->rate_info)) { uint64_t elapsed_play_time, frames_played; @@ -2732,6 +2740,10 @@ void do_flush(int64_t timestamp, rtsp_conn_info *conn) { // conn->play_segment_reference_frame = 0; conn->play_number_after_flush = 0; conn->packet_count_since_flush = 0; + conn->packet_stream_established = 0; + conn->input_frame_rate_starting_point_is_valid = 0; + conn->initial_reference_time = 0; + conn->initial_reference_timestamp = 0; debug_mutex_unlock(&conn->flush_mutex, 3); diff --git a/player.h b/player.h index 783c5165..beebab70 100644 --- a/player.h +++ b/player.h @@ -100,7 +100,7 @@ typedef struct { // for holding input rate information until printed out at the end of a session double input_frame_rate; - int input_frame_rate_status; + int input_frame_rate_starting_point_is_valid; uint64_t frames_inward_measurement_start_time; uint64_t frames_inward_frames_received_at_measurement_start_time; @@ -190,6 +190,7 @@ typedef struct { int64_t reference_timestamp; uint64_t remote_reference_timestamp_time; + int packet_stream_established; // true if a stream of packets is flowing, made true by a first packet, false by a flush // used as the initials values for calculating the rate at which the source thinks it's sending frames int64_t initial_reference_timestamp; diff --git a/rtp.c b/rtp.c index 6747724f..f459541e 100644 --- a/rtp.c +++ b/rtp.c @@ -357,15 +357,19 @@ void *rtp_control_receiver(void *arg) { debug_mutex_lock(&conn->reference_time_mutex, 1000, 1); - if (conn->initial_reference_time==0) { - conn->initial_reference_time = remote_time_of_sync; - conn->initial_reference_timestamp = sync_rtp_timestamp; - } else { - uint64_t remote_frame_time_interval = conn->remote_reference_timestamp_time - conn->initial_reference_time; // here, this should never be zero - if (remote_frame_time_interval) { - conn->remote_frame_rate = (1.0 * (conn->reference_timestamp - conn->initial_reference_timestamp)) / remote_frame_time_interval; // an IEEE double calculation with two 64-bit integers - conn->remote_frame_rate = conn->remote_frame_rate * (uint64_t)0x100000000; // this should just change the [binary] exponent in the IEEE FP representation; the mantissa should be unaffected. - } + if (conn->packet_stream_established) { + if (conn->initial_reference_time==0) { + conn->initial_reference_time = remote_time_of_sync; + conn->initial_reference_timestamp = sync_rtp_timestamp; + } else { + uint64_t remote_frame_time_interval = conn->remote_reference_timestamp_time - conn->initial_reference_time; // here, this should never be zero + if (remote_frame_time_interval) { + conn->remote_frame_rate = (1.0 * (conn->reference_timestamp - conn->initial_reference_timestamp)) / remote_frame_time_interval; // an IEEE double calculation with two 64-bit integers + conn->remote_frame_rate = conn->remote_frame_rate * (uint64_t)0x100000000; // this should just change the [binary] exponent in the IEEE FP representation; the mantissa should be unaffected. + } else { + conn->remote_frame_rate = 0.0; // use as a flag. + } + } } // this is for debugging @@ -987,24 +991,27 @@ int have_timestamp_timing_information(rtsp_conn_info *conn) { } // set this to zero to use the rates supplied by the sources, which might not always be completely right... -const int use_nominal_rate = 1; // specify whether to use the nominal input rate, usually 44100 fps +const int use_nominal_rate = 0; // specify whether to use the nominal input rate, usually 44100 fps int sanitised_source_rate_information(int64_t *frames, uint64_t *time, rtsp_conn_info *conn) { - int result = 0; + int result = 1; *frames = conn->input_rate; *time = (uint64_t)(0x100000000); // one second in fp form - int64_t local_frames = conn->reference_to_previous_frame_difference; - uint64_t local_time = conn->reference_to_previous_time_difference; - if ((local_frames == 0) || (local_time == 0) || (use_nominal_rate)) { - result = 1; - } else { - double calculated_frame_rate = ((1.0*local_frames)/local_time)*(uint64_t)0x100000000; - if (((calculated_frame_rate/conn->input_rate) > 1.001) || ((calculated_frame_rate/conn->input_rate) < 0.999)) { + if ((conn->packet_stream_established) && (conn->initial_reference_time) && (conn->initial_reference_timestamp)) { + int64_t local_frames = conn->reference_timestamp - conn->initial_reference_timestamp; + uint64_t local_time = conn->remote_reference_timestamp_time - conn->initial_reference_time; + if ((local_frames == 0) || (local_time == 0) || (use_nominal_rate)) { result = 1; } else { - *frames = local_frames; - *time = local_time; - result = 0; + double calculated_frame_rate = ((1.0*local_frames)/local_time)*(uint64_t)0x100000000; + if (((calculated_frame_rate/conn->input_rate) > 1.001) || ((calculated_frame_rate/conn->input_rate) < 0.999)) { + debug(1,"input frame rate out of bounds at %.2f fps.",calculated_frame_rate); + result = 1; + } else { + *frames = local_frames; + *time = local_time; + result = 0; + } } } return result; From 5141e2f5dcfe403f1b2b6c76aa23997a0d355216 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 14 Aug 2018 12:14:49 +0100 Subject: [PATCH 067/569] Format tidy-up --- dbus-service.c | 7 +- player.c | 231 +++++++++++++++++++++----------------- player.h | 28 +++-- rtp.c | 297 ++++++++++++++++++++++++++++++------------------- 4 files changed, 332 insertions(+), 231 deletions(-) diff --git a/dbus-service.c b/dbus-service.c index 59dff59e..daf94e0f 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -405,7 +405,7 @@ gboolean notify_loudness_threshold_callback(ShairportSync *skeleton, } gboolean notify_drift_tolerance_callback(ShairportSync *skeleton, - __attribute__((unused)) gpointer user_data) { + __attribute__((unused)) gpointer user_data) { gdouble dt = shairport_sync_get_drift_tolerance(skeleton); if ((dt >= 0.0) && (dt <= 2.0)) { debug(1, "Setting drift tolerance to %f.", dt); @@ -675,8 +675,7 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name shairport_sync_set_loudness_threshold(SHAIRPORT_SYNC(shairportSyncSkeleton), config.loudness_reference_volume_db); - shairport_sync_set_drift_tolerance(SHAIRPORT_SYNC(shairportSyncSkeleton), - config.tolerance); + shairport_sync_set_drift_tolerance(SHAIRPORT_SYNC(shairportSyncSkeleton), config.tolerance); #ifdef HAVE_APPLE_ALAC if (config.use_apple_decoder == 0) { @@ -689,7 +688,7 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name #else shairport_sync_set_alacdecoder(SHAIRPORT_SYNC(shairportSyncSkeleton), "hammerton"); debug(1, ">> ALACDecoder set to \"hammerton\""); - + #endif #ifdef HAVE_LIBSOXR diff --git a/player.c b/player.c index ed8c6722..fec56ecc 100644 --- a/player.c +++ b/player.c @@ -491,12 +491,12 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp // debug(1,"Flush_rtp_timestamp is %u",flush_rtp_timestamp); if ((conn->flush_rtp_timestamp != 0) && (ltimestamp <= conn->flush_rtp_timestamp)) { - debug(3, - "Dropping flushed packet in player_put_packet, seqno %u, timestamp %" PRId64 ", flushing to " - "timestamp: %" PRId64 ".", + debug(3, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %" PRId64 + ", flushing to " + "timestamp: %" PRId64 ".", seqno, ltimestamp, conn->flush_rtp_timestamp); - conn->initial_reference_time = 0; - conn->initial_reference_timestamp = 0; + conn->initial_reference_time = 0; + conn->initial_reference_timestamp = 0; } else { if ((conn->flush_rtp_timestamp != 0x0) && (ltimestamp > conn->flush_rtp_timestamp)) // if we have gone past the flush boundary time @@ -528,17 +528,17 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp if (conn->ab_write == seqno) { // expected packet uint64_t reception_time = get_absolute_time_in_fp(); if (conn->input_frame_rate_starting_point_is_valid == 0) { - if ((conn->packet_count_since_flush>=500) && (conn->packet_count_since_flush<=510)) { + if ((conn->packet_count_since_flush >= 500) && (conn->packet_count_since_flush <= 510)) { conn->frames_inward_measurement_start_time = reception_time; conn->frames_inward_frames_received_at_measurement_start_time = timestamp; conn->input_frame_rate_starting_point_is_valid = 1; // valid now - debug(1,"input_frame_rate_starting_point_is_valid set"); + debug(1, "input_frame_rate_starting_point_is_valid set"); } } conn->frames_inward_measurement_time = reception_time; conn->frames_inward_frames_received_at_measurement_time = timestamp; - + abuf = conn->audio_buffer + BUFIDX(seqno); conn->ab_write = SUCCESSOR(seqno); } else if (seq_order(conn->ab_write, seqno, conn->ab_read)) { // newer than expected @@ -879,8 +879,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if ((conn->flush_rtp_timestamp != 0) && (curframe->timestamp <= conn->flush_rtp_timestamp)) { - debug(2, "Dropping flushed packet in buffer_get_frame seqno %u, timestamp %" PRId64 ".", curframe->sequence_number, - curframe->timestamp); + debug(2, "Dropping flushed packet in buffer_get_frame seqno %u, timestamp %" PRId64 ".", + curframe->sequence_number, curframe->timestamp); curframe->ready = 0; curframe->resend_level = 0; flush_limit++; @@ -922,7 +922,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // supposed to start playing this have_sent_prefiller_silence = 0; conn->packet_stream_established = 1; - + // debug(1, "First packet timestamp is %" PRId64 ".", conn->first_packet_timestamp); // say we have started playing here @@ -956,9 +956,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // -4410 frames. // debug(1, "Output sample ratio is %d", conn->output_sample_ratio); - - - // what we are asking for here is "what is the local time at which time the calculated frame should be played" + + // what we are asking for here is "what is the local time at which time the calculated + // frame should be played" int64_t delta = (conn->first_packet_timestamp - reference_timestamp) + conn->latency * conn->output_sample_ratio + @@ -972,22 +972,31 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { int64_t abs_delta = -delta; int64_t delta_fp_sec = (abs_delta << 32) / config.output_rate; // int64_t which is positive - conn->first_packet_time_to_play = reference_timestamp_time - delta_fp_sec; + conn->first_packet_time_to_play = reference_timestamp_time - delta_fp_sec; } uint64_t should_be_time; - frame_to_local_time(conn->first_packet_timestamp +conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate),&should_be_time,conn); - - if(should_be_time>=conn->first_packet_time_to_play) { - if ((((should_be_time-conn->first_packet_time_to_play)*1000000)>>32)>10) - debug(2,"New time for first packet timestamp %" PRId64 " is later than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((should_be_time-conn->first_packet_time_to_play)*1000000)>>32); + frame_to_local_time( + conn->first_packet_timestamp + conn->latency * conn->output_sample_ratio + + (int64_t)(config.audio_backend_latency_offset * config.output_rate), + &should_be_time, conn); + + if (should_be_time >= conn->first_packet_time_to_play) { + if ((((should_be_time - conn->first_packet_time_to_play) * 1000000) >> 32) > 10) + debug(2, "New time for first packet timestamp %" PRId64 + " is later than calculated time by %" PRId64 " microseconds.", + curframe->timestamp, + ((should_be_time - conn->first_packet_time_to_play) * 1000000) >> 32); } else { - if ((((conn->first_packet_time_to_play-should_be_time)*1000000)>>32)>10) - debug(2,"New time for first packet timestamp %" PRId64 " is earlier than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((conn->first_packet_time_to_play-should_be_time)*1000000)>>32); - } - + if ((((conn->first_packet_time_to_play - should_be_time) * 1000000) >> 32) > 10) + debug(2, "New time for first packet timestamp %" PRId64 + " is earlier than calculated time by %" PRId64 " microseconds.", + curframe->timestamp, + ((conn->first_packet_time_to_play - should_be_time) * 1000000) >> 32); + } + // cut over to new calculation scheme - 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) { debug( @@ -1015,19 +1024,26 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { (abs_delta << 32) / config.output_rate; // int64_t which is positive conn->first_packet_time_to_play = reference_timestamp_time - delta_fp_sec; } - + uint64_t should_be_time; - frame_to_local_time(conn->first_packet_timestamp +conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate),&should_be_time,conn); - - if(should_be_time>=conn->first_packet_time_to_play) { - if ((((should_be_time-conn->first_packet_time_to_play)*1000000)>>32)>50) - debug(2,"New time for recalculated first packet timestamp %" PRId64 " is later than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((should_be_time-conn->first_packet_time_to_play)*1000000)>>32); + frame_to_local_time( + conn->first_packet_timestamp + conn->latency * conn->output_sample_ratio + + (int64_t)(config.audio_backend_latency_offset * config.output_rate), + &should_be_time, conn); + + if (should_be_time >= conn->first_packet_time_to_play) { + if ((((should_be_time - conn->first_packet_time_to_play) * 1000000) >> 32) > 50) + debug(2, "New time for recalculated first packet timestamp %" PRId64 + " is later than calculated time by %" PRId64 " microseconds.", + curframe->timestamp, + ((should_be_time - conn->first_packet_time_to_play) * 1000000) >> 32); } else { - if ((((conn->first_packet_time_to_play-should_be_time)*1000000)>>32)>50) - debug(2,"New time for recalculated first packet timestamp %" PRId64 " is earlier than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((conn->first_packet_time_to_play-should_be_time)*1000000)>>32); - } - - + if ((((conn->first_packet_time_to_play - should_be_time) * 1000000) >> 32) > 50) + debug(2, "New time for recalculated first packet timestamp %" PRId64 + " is earlier than calculated time by %" PRId64 " microseconds.", + curframe->timestamp, + ((conn->first_packet_time_to_play - should_be_time) * 1000000) >> 32); + } // now, the size of the initial silence must be affected by the lead-in time. // it must be somewhat less than the lead-in time so that dynamic adjustments can be @@ -1175,14 +1191,14 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { } } if (conn->ab_buffering == 0) { - /* - // note the time of the playing of the first frame - uint64_t reference_timestamp_time; // don't need this... - get_reference_timestamp_stuff(&conn->play_segment_reference_frame, - &reference_timestamp_time, - &conn->play_segment_reference_frame_remote_time, conn); - conn->play_segment_reference_frame *= conn->output_sample_ratio; - */ +/* + // note the time of the playing of the first frame + uint64_t reference_timestamp_time; // don't need this... + get_reference_timestamp_stuff(&conn->play_segment_reference_frame, + &reference_timestamp_time, + &conn->play_segment_reference_frame_remote_time, conn); + conn->play_segment_reference_frame *= conn->output_sample_ratio; +*/ #ifdef CONFIG_METADATA debug(2, "prsm"); send_ssnc_metadata('prsm', NULL, 0, @@ -1209,14 +1225,14 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if ((conn->ab_synced) && (curframe) && (curframe->ready) && (curframe->timestamp)) { do_wait = 1; // if the current frame exists and is ready, then wait unless it's time to let it go... - + // here, get the time to play the current frame. int64_t reference_timestamp; uint64_t reference_timestamp_time, remote_reference_timestamp_time; get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time, conn); // all types okay reference_timestamp *= conn->output_sample_ratio; - if (have_timestamp_timing_information(conn)) { // if we have a reference time + if (have_timestamp_timing_information(conn)) { // if we have a reference time int64_t packet_timestamp = curframe->timestamp; // types okay int64_t delta = packet_timestamp - reference_timestamp; int64_t offset = @@ -1224,8 +1240,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { (int64_t)(config.audio_backend_latency_offset * config.output_rate) - config.audio_backend_buffer_desired_length * config.output_rate; // all arguments are int32_t, so expression promotion okay - int64_t net_offset = delta + offset; // okay - + int64_t net_offset = delta + offset; // okay + uint64_t time_to_play = reference_timestamp_time; // type okay if (net_offset >= 0) { uint64_t net_offset_fp_sec = @@ -1239,20 +1255,24 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { time_to_play -= net_offset_fp_sec; // debug(2,"Net Offset: %lld, adjusted: -%lld.",net_offset,net_offset_fp_sec); } - + uint64_t new_time_to_play = 0; - frame_to_local_time(packet_timestamp+offset, &new_time_to_play, conn); - - if(new_time_to_play>=time_to_play) { - if ((((new_time_to_play-time_to_play)*1000000)>>32)>100) - debug(2,"New time for frame %" PRId64 " is later than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((new_time_to_play-time_to_play)*1000000)>>32); + frame_to_local_time(packet_timestamp + offset, &new_time_to_play, conn); + + if (new_time_to_play >= time_to_play) { + if ((((new_time_to_play - time_to_play) * 1000000) >> 32) > 100) + debug(2, "New time for frame %" PRId64 " is later than calculated time by %" PRId64 + " microseconds.", + curframe->timestamp, ((new_time_to_play - time_to_play) * 1000000) >> 32); } else { - if ((((time_to_play-new_time_to_play)*1000000)>>32)>100) - debug(2,"New time for frame %" PRId64 " is earlier than calculated time by %" PRId64 " microseconds.",curframe->timestamp,((time_to_play-new_time_to_play)*1000000)>>32); - } + if ((((time_to_play - new_time_to_play) * 1000000) >> 32) > 100) + debug(2, "New time for frame %" PRId64 " is earlier than calculated time by %" PRId64 + " microseconds.", + curframe->timestamp, ((time_to_play - new_time_to_play) * 1000000) >> 32); + } // cut over to the new calculation system - time_to_play = new_time_to_play; - + time_to_play = new_time_to_play; + if (local_time_now >= time_to_play) { do_wait = 0; } @@ -1499,11 +1519,12 @@ void player_thread_cleanup_handler(void *arg) { int elapsedMin = (rawSeconds / 60) % 60; int elapsedSec = rawSeconds % 60; if (conn->frame_rate_status) - inform("Playback Stopped. Total playing time %02d:%02d:%02d. Input: %0.2f, output: %0.2f frames per second.", + inform("Playback Stopped. Total playing time %02d:%02d:%02d. Input: %0.2f, output: %0.2f " + "frames per second.", elapsedHours, elapsedMin, elapsedSec, conn->input_frame_rate, conn->frame_rate); else - inform("Playback Stopped. Total playing time %02d:%02d:%02d. Input: %0.2f frames per second.", elapsedHours, elapsedMin, - elapsedSec, conn->input_frame_rate); + inform("Playback Stopped. Total playing time %02d:%02d:%02d. Input: %0.2f frames per second.", + elapsedHours, elapsedMin, elapsedSec, conn->input_frame_rate); } #ifdef HAVE_DACP_CLIENT @@ -1992,9 +2013,11 @@ void *player_thread_func(void *arg) { // uint64_t // local_time_now=((uint64_t)tn.tv_sec<<32)+((uint64_t)tn.tv_nsec<<32)/1000000000; - int64_t td = 0; // td is the time difference between the reference timestamp time and the present time. Only used to calculate td_in_frames - int64_t td_in_frames = 0; // td_in_frames is the number of frames between between the reference timestamp time and the present time - + int64_t td = 0; // td is the time difference between the reference timestamp time and the + // present time. Only used to calculate td_in_frames + int64_t td_in_frames = 0; // td_in_frames is the number of frames between between the + // reference timestamp time and the present time + if (local_time_now >= reference_timestamp_time) { td = local_time_now - reference_timestamp_time; // this is the positive value. // Conversion is positive uint64_t to @@ -2069,20 +2092,20 @@ void *player_thread_func(void *arg) { if (resp >= 0) { - int64_t should_be_frame; - local_time_to_frame(local_time_now,&should_be_frame,conn); - - if (abs(td_in_frames + rt-should_be_frame)>10*conn->output_sample_ratio) - debug(1,"Difference between old and new frame number is %" PRId64 " frames.",td_in_frames + rt - should_be_frame); + int64_t should_be_frame; + local_time_to_frame(local_time_now, &should_be_frame, conn); + + if (abs(td_in_frames + rt - should_be_frame) > 10 * conn->output_sample_ratio) + debug(1, "Difference between old and new frame number is %" PRId64 " frames.", + td_in_frames + rt - should_be_frame); // this is the actual delay, including the latency we actually want, which will // fluctuate a good bit about a potentially rising or falling trend. -// int64_t delay = td_in_frames + rt - (nt - current_delay); // all int64_t + // int64_t delay = td_in_frames + rt - (nt - current_delay); // all int64_t // cut over to the new calculation method int64_t delay = should_be_frame - (nt - current_delay); // all int64_t - + // td_in_frames + rt is the frame number that should be output at local_time_now. - // This is the timing error for the next audio frame in the DAC. @@ -2368,36 +2391,47 @@ void *player_thread_func(void *arg) { } } if (play_number % print_interval == 0) { - - // here, calculate the input and output frame rates, where possible, even if statistics have not been requested + + // here, calculate the input and output frame rates, where possible, even if statistics + // have not been requested // this is to calculate them in case they are needed by the D-Bus interface or elsewhere. - + if (conn->input_frame_rate_starting_point_is_valid) { uint64_t elapsed_reception_time, frames_received; - elapsed_reception_time = conn->frames_inward_measurement_time - conn->frames_inward_measurement_start_time; - frames_received = conn->frames_inward_frames_received_at_measurement_time - conn->frames_inward_frames_received_at_measurement_start_time; - conn->input_frame_rate = (1.0 * frames_received) / elapsed_reception_time; // an IEEE double calculation with two 64-bit integers - conn->input_frame_rate = conn->input_frame_rate * (uint64_t)0x100000000; // this should just change the [binary] exponent in the IEEE FP representation; the mantissa should be unaffected. + elapsed_reception_time = + conn->frames_inward_measurement_time - conn->frames_inward_measurement_start_time; + frames_received = conn->frames_inward_frames_received_at_measurement_time - + conn->frames_inward_frames_received_at_measurement_start_time; + conn->input_frame_rate = + (1.0 * frames_received) / + elapsed_reception_time; // an IEEE double calculation with two 64-bit integers + conn->input_frame_rate = + conn->input_frame_rate * (uint64_t)0x100000000; // this should just change the + // [binary] exponent in the IEEE FP + // representation; the mantissa + // should be unaffected. } else { conn->input_frame_rate = 0.0; - } - + } + if ((config.output->delay) && (config.no_sync == 0) && (config.output->rate_info)) { uint64_t elapsed_play_time, frames_played; - if (config.output->rate_info(&elapsed_play_time, &frames_played)==0) + if (config.output->rate_info(&elapsed_play_time, &frames_played) == 0) conn->frame_rate_status = 1; else - conn->frame_rate_status = 0; - if (conn->frame_rate_status) { + conn->frame_rate_status = 0; + if (conn->frame_rate_status) { conn->frame_rate = - (1.0 * frames_played) / elapsed_play_time; // an IEEE double calculation with two 64-bit integers - conn->frame_rate = - conn->frame_rate * (uint64_t)0x100000000; // this should just change the [binary] exponent in the IEEE FP representation; the mantissa should be unaffected. + (1.0 * frames_played) / + elapsed_play_time; // an IEEE double calculation with two 64-bit integers + conn->frame_rate = conn->frame_rate * + (uint64_t)0x100000000; // this should just change the [binary] + // exponent in the IEEE FP representation; + // the mantissa should be unaffected. } else { conn->frame_rate = 0.0; } - } - + } // we can now calculate running averages for sync error (frames), corrections (ppm), // insertions plus deletions (ppm), drift (ppm) @@ -2409,7 +2443,7 @@ void *player_thread_func(void *arg) { // if ((play_number/print_interval)%20==0) if (config.statistics_requested) { if (at_least_one_frame_seen) { - + if ((config.output->delay)) { if (config.no_sync == 0) { inform("%*.2f," /* Sync error in milliseconds */ @@ -2436,12 +2470,10 @@ void *player_thread_func(void *arg) { 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, - maximum_buffer_occupancy, - 11, conn->remote_frame_rate, - 11, conn->input_frame_rate, - 11, conn->frame_rate , - 10, (1.0-conn->local_to_remote_time_gradient)*1000000, - 6, conn->local_to_remote_time_gradient_sample_count); + maximum_buffer_occupancy, 11, conn->remote_frame_rate, 11, + conn->input_frame_rate, 11, conn->frame_rate, 10, + (1.0 - conn->local_to_remote_time_gradient) * 1000000, 6, + conn->local_to_remote_time_gradient_sample_count); } else { inform("%*.2f," /* Sync error in milliseconds */ "%*d," /* total packets */ @@ -2457,7 +2489,8 @@ void *player_thread_func(void *arg) { 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, 5, - minimum_buffer_occupancy, 5, maximum_buffer_occupancy, 11, conn->input_frame_rate); + minimum_buffer_occupancy, 5, maximum_buffer_occupancy, 11, + conn->input_frame_rate); } } else { inform("%*.2f," /* Sync error in milliseconds */ @@ -2744,7 +2777,7 @@ void do_flush(int64_t timestamp, rtsp_conn_info *conn) { conn->input_frame_rate_starting_point_is_valid = 0; conn->initial_reference_time = 0; conn->initial_reference_timestamp = 0; - + debug_mutex_unlock(&conn->flush_mutex, 3); #ifdef CONFIG_METADATA diff --git a/player.h b/player.h index beebab70..078a96a0 100644 --- a/player.h +++ b/player.h @@ -24,7 +24,7 @@ #include "alac.h" #include "audio.h" -#define time_ping_history 64 // at 1 per three seconds, approximately three minutes of records +#define time_ping_history 64 // at 1 per three seconds, approximately three minutes of records typedef struct time_ping_record { uint64_t local_to_remote_difference; @@ -96,12 +96,12 @@ typedef struct { // for holding the output rate information until printed out at the end of a session double frame_rate; int frame_rate_status; - + // for holding input rate information until printed out at the end of a session - + double input_frame_rate; int input_frame_rate_starting_point_is_valid; - + uint64_t frames_inward_measurement_start_time; uint64_t frames_inward_frames_received_at_measurement_start_time; @@ -188,11 +188,13 @@ typedef struct { // this is what connects an rtp timestamp to the remote time int64_t reference_timestamp; - uint64_t remote_reference_timestamp_time; + uint64_t remote_reference_timestamp_time; - int packet_stream_established; // true if a stream of packets is flowing, made true by a first packet, false by a flush - - // used as the initials values for calculating the rate at which the source thinks it's sending frames + int packet_stream_established; // true if a stream of packets is flowing, made true by a first + // packet, false by a flush + + // used as the initials values for calculating the rate at which the source thinks it's sending + // frames int64_t initial_reference_timestamp; uint64_t initial_reference_time; double remote_frame_rate; @@ -200,7 +202,7 @@ typedef struct { // the ratio of the following should give us the operating rate, nominally 44,100 int64_t reference_to_previous_frame_difference; uint64_t reference_to_previous_time_difference; - + // debug variables int request_sent; @@ -212,9 +214,11 @@ typedef struct { pthread_mutex_t reference_time_mutex; - double local_to_remote_time_gradient; // if no drift, this would be exactly 1.0; likely it's slightly above or below. - int local_to_remote_time_gradient_sample_count; // the number of samples used to calculate the gradient - uint64_t local_to_remote_time_difference; // used to switch between local and remote clocks + double local_to_remote_time_gradient; // if no drift, this would be exactly 1.0; likely it's + // slightly above or below. + int local_to_remote_time_gradient_sample_count; // the number of samples used to calculate the + // gradient + uint64_t local_to_remote_time_difference; // used to switch between local and remote clocks uint64_t local_to_remote_time_difference_measurement_time; // when the above was calculated int last_stuff_request; diff --git a/rtp.c b/rtp.c index f459541e..9d3c6f76 100644 --- a/rtp.c +++ b/rtp.c @@ -70,20 +70,31 @@ uint64_t local_to_remote_time_difference_now(rtsp_conn_info *conn) { // so, if we have a non-zero clock drift, we will calculate the drift there would // be from the time of the last time ping uint64_t local_time_now_fp = get_absolute_time_in_fp(); - uint64_t time_since_last_local_to_remote_time_difference_measurement = local_time_now_fp - conn->local_to_remote_time_difference_measurement_time; - - uint64_t remote_time_since_last_local_to_remote_time_difference_measurement = (uint64_t)(conn->local_to_remote_time_gradient*time_since_last_local_to_remote_time_difference_measurement); - - double drift; - if (remote_time_since_last_local_to_remote_time_difference_measurement >= time_since_last_local_to_remote_time_difference_measurement) - drift = (1.0*(remote_time_since_last_local_to_remote_time_difference_measurement - time_since_last_local_to_remote_time_difference_measurement))/(uint64_t)0x100000000; - else - drift = -((1.0*(time_since_last_local_to_remote_time_difference_measurement - remote_time_since_last_local_to_remote_time_difference_measurement))/(uint64_t)0x100000000); + uint64_t time_since_last_local_to_remote_time_difference_measurement = + local_time_now_fp - conn->local_to_remote_time_difference_measurement_time; -// double interval_ms = 1.0*(((time_since_last_local_to_remote_time_difference_measurement)*1000)>>32); -// debug(1,"Measurement drift is %.2f microseconds (0x%" PRIx64 " in 64-bit fp) over %.2f milliseconds with drift of %.2f ppm.",drift*1000000,(uint64_t)(drift*(uint64_t)0x100000000),interval_ms,(1.0-conn->local_to_remote_time_gradient)*1000000); -// return conn->local_to_remote_time_difference + (uint64_t)(drift*(uint64_t 0x100000000)); - return conn->local_to_remote_time_difference + (uint64_t)(drift*(uint64_t)0x100000000); + uint64_t remote_time_since_last_local_to_remote_time_difference_measurement = + (uint64_t)(conn->local_to_remote_time_gradient * + time_since_last_local_to_remote_time_difference_measurement); + + double drift; + if (remote_time_since_last_local_to_remote_time_difference_measurement >= + time_since_last_local_to_remote_time_difference_measurement) + drift = (1.0 * (remote_time_since_last_local_to_remote_time_difference_measurement - + time_since_last_local_to_remote_time_difference_measurement)) / + (uint64_t)0x100000000; + else + drift = -((1.0 * (time_since_last_local_to_remote_time_difference_measurement - + remote_time_since_last_local_to_remote_time_difference_measurement)) / + (uint64_t)0x100000000); + + // double interval_ms = + // 1.0*(((time_since_last_local_to_remote_time_difference_measurement)*1000)>>32); + // debug(1,"Measurement drift is %.2f microseconds (0x%" PRIx64 " in 64-bit fp) over %.2f + // milliseconds with drift of %.2f + // ppm.",drift*1000000,(uint64_t)(drift*(uint64_t)0x100000000),interval_ms,(1.0-conn->local_to_remote_time_gradient)*1000000); + // return conn->local_to_remote_time_difference + (uint64_t)(drift*(uint64_t 0x100000000)); + return conn->local_to_remote_time_difference + (uint64_t)(drift * (uint64_t)0x100000000); } void rtp_audio_receiver_cleanup_handler(void *arg) { @@ -356,19 +367,28 @@ void *rtp_control_receiver(void *arg) { } debug_mutex_lock(&conn->reference_time_mutex, 1000, 1); - + if (conn->packet_stream_established) { - if (conn->initial_reference_time==0) { + if (conn->initial_reference_time == 0) { conn->initial_reference_time = remote_time_of_sync; conn->initial_reference_timestamp = sync_rtp_timestamp; } else { - uint64_t remote_frame_time_interval = conn->remote_reference_timestamp_time - conn->initial_reference_time; // here, this should never be zero + uint64_t remote_frame_time_interval = + conn->remote_reference_timestamp_time - + conn->initial_reference_time; // here, this should never be zero if (remote_frame_time_interval) { - conn->remote_frame_rate = (1.0 * (conn->reference_timestamp - conn->initial_reference_timestamp)) / remote_frame_time_interval; // an IEEE double calculation with two 64-bit integers - conn->remote_frame_rate = conn->remote_frame_rate * (uint64_t)0x100000000; // this should just change the [binary] exponent in the IEEE FP representation; the mantissa should be unaffected. + conn->remote_frame_rate = + (1.0 * (conn->reference_timestamp - conn->initial_reference_timestamp)) / + remote_frame_time_interval; // an IEEE double calculation with two 64-bit + // integers + conn->remote_frame_rate = conn->remote_frame_rate * + (uint64_t)0x100000000; // this should just change the + // [binary] exponent in the IEEE + // FP representation; the + // mantissa should be unaffected. } else { conn->remote_frame_rate = 0.0; // use as a flag. - } + } } } @@ -376,25 +396,29 @@ void *rtp_control_receiver(void *arg) { uint64_t old_remote_reference_time = conn->remote_reference_timestamp_time; int64_t old_reference_timestamp = conn->reference_timestamp; // int64_t old_latency_delayed_timestamp = conn->latency_delayed_timestamp; - + conn->remote_reference_timestamp_time = remote_time_of_sync; - //conn->reference_timestamp_time = + // conn->reference_timestamp_time = // remote_time_of_sync - local_to_remote_time_difference_now(conn); conn->reference_timestamp = sync_rtp_timestamp; conn->latency_delayed_timestamp = rtp_timestamp_less_latency; debug_mutex_unlock(&conn->reference_time_mutex, 3); - - conn->reference_to_previous_time_difference = remote_time_of_sync - old_remote_reference_time; - if (old_reference_timestamp==0) + + conn->reference_to_previous_time_difference = + remote_time_of_sync - old_remote_reference_time; + if (old_reference_timestamp == 0) conn->reference_to_previous_frame_difference = 0; else - conn->reference_to_previous_frame_difference = sync_rtp_timestamp - old_reference_timestamp; + conn->reference_to_previous_frame_difference = + sync_rtp_timestamp - old_reference_timestamp; + + // int64_t delayed_frame_difference = rtp_timestamp_less_latency - + // old_latency_delayed_timestamp; - // int64_t delayed_frame_difference = rtp_timestamp_less_latency - old_latency_delayed_timestamp; - /* if (old_remote_reference_time) - debug(1,"Time difference: %" PRIu64 " reference and delayed frame differences: %" PRId64 " and %" PRId64 ", giving rates _at source!!_ of %f and %f respectively.", + debug(1,"Time difference: %" PRIu64 " reference and delayed frame differences: %" + PRId64 " and %" PRId64 ", giving rates _at source!!_ of %f and %f respectively.", (conn->reference_to_previous_time_difference*1000000)>>32,conn->reference_to_previous_frame_difference,delayed_frame_difference, (1.0*(conn->reference_to_previous_frame_difference*10000000))/((conn->reference_to_previous_time_difference*10000000)>>32),(1.0*(delayed_frame_difference*10000000))/((conn->reference_to_previous_time_difference*10000000)>>32)); else @@ -540,7 +564,7 @@ void *rtp_timing_receiver(void *arg) { // uint64_t first_local_to_remote_time_difference_time; // uint64_t l2rtd = 0; int sequence_number = 0; - + // for getting mean and sd of return times int32_t stat_n = 0; double stat_mean = 0.0; @@ -594,56 +618,59 @@ void *rtp_timing_receiver(void *arg) { distant_transmit_time += nctohl(&packet[28]); uint64_t remote_processing_time = distant_transmit_time - distant_receive_time; - - //debug(1,"Return trip time: %" PRIu64 " uS, remote processing time: %" PRIu64 " uS.",(return_time*1000000)>>32,(remote_processing_time*1000000)>>32); - + + // debug(1,"Return trip time: %" PRIu64 " uS, remote processing time: %" PRIu64 " + // uS.",(return_time*1000000)>>32,(remote_processing_time*1000000)>>32); + uint64_t local_time_by_remote_clock = distant_transmit_time + return_time / 2; - - // remove the remote processing time from the record of the return time, as long at the processing time looks sensible. - + + // remove the remote processing time from the record of the return time, as long at the + // processing time looks sensible. + if ((remote_processing_time > 0) && (remote_processing_time < return_time)) return_time -= remote_processing_time; else - debug(1,"Non-sensical remote processing time -- ignored."); + debug(1, "Non-sensical remote processing time -- ignored."); int cc; for (cc = time_ping_history - 1; cc > 0; cc--) { conn->time_pings[cc] = conn->time_pings[cc - 1]; - //if ((conn->time_ping_count) && (conn->time_ping_count < 10)) -// conn->time_pings[cc].dispersion = -// conn->time_pings[cc].dispersion * pow(2.14, 1.0/conn->time_ping_count); + // if ((conn->time_ping_count) && (conn->time_ping_count < 10)) + // conn->time_pings[cc].dispersion = + // conn->time_pings[cc].dispersion * pow(2.14, + // 1.0/conn->time_ping_count); conn->time_pings[cc].dispersion = - (conn->time_pings[cc].dispersion * 110) / 100; // make the dispersions 'age' by this rational factor + (conn->time_pings[cc].dispersion * 110) / + 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; - conn->time_pings[0].remote_time = distant_transmit_time; - conn->time_pings[0].sequence_number = sequence_number++; - conn->time_pings[0].chosen = 0; - - + conn->time_pings[0].remote_time = distant_transmit_time; + conn->time_pings[0].sequence_number = sequence_number++; + conn->time_pings[0].chosen = 0; conn->time_pings[0].local_to_remote_difference = local_time_by_remote_clock - arrival_time; conn->time_pings[0].dispersion = return_time; if (conn->time_ping_count < time_ping_history) conn->time_ping_count++; - + // here, calculate the mean and standard deviation of the return times - + // mean and variance calculations from "online_variance" algorithm at // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm - - double rtfus = 1.0*((return_time * 1000000) >> 32); + + double rtfus = 1.0 * ((return_time * 1000000) >> 32); stat_n += 1; double stat_delta = rtfus - stat_mean; stat_mean += stat_delta / stat_n; stat_M2 += stat_delta * (rtfus - stat_mean); - //debug(1, "Timing packet return time stats: current, mean and standard deviation over %d packets: %.1f, %.1f, %.1f (microseconds).", + // debug(1, "Timing packet return time stats: current, mean and standard deviation over + // %d packets: %.1f, %.1f, %.1f (microseconds).", // stat_n,rtfus,stat_mean, sqrtf(stat_M2 / (stat_n - 1))); - + // here, pick the record with the least dispersion, and record that it's been chosen - + // uint64_t local_time_chosen = arrival_time; // uint64_t remote_time_chosen = distant_transmit_time; // now pick the timestamp with the lowest dispersion @@ -660,19 +687,23 @@ void *rtp_timing_receiver(void *arg) { // local_time_chosen = conn->time_pings[cc].local_time; // remote_time_chosen = conn->time_pings[cc].remote_time; } - // debug(1,"Record %d has the lowest dispersion with %0.2f us dispersion.",chosen,1.0*((tld * 1000000) >> 32)); + // debug(1,"Record %d has the lowest dispersion with %0.2f us + // dispersion.",chosen,1.0*((tld * 1000000) >> 32)); conn->time_pings[chosen].chosen = 1; // record the fact that it has been used for timing /* - // calculate the jitter -- the absolute time between the current local_to_remote_time_difference and the new one and add it to the total jitter count + // calculate the jitter -- the absolute time between the current + local_to_remote_time_difference and the new one and add it to the total jitter count int64_t ji; int64_t ltd =0; // local time difference for the jitter - + if (conn->time_ping_count > 1) { if (l2rtd > conn->local_to_remote_time_difference) { local_to_remote_time_jitter = local_to_remote_time_jitter + l2rtd - conn->local_to_remote_time_difference; - ji = l2rtd - conn->local_to_remote_time_difference; // this is the difference between the present local-to-remote-time-difference and the new one, i.e. the jitter step + ji = l2rtd - conn->local_to_remote_time_difference; // this is the difference + between the present local-to-remote-time-difference and the new one, i.e. the jitter + step } else { local_to_remote_time_jitter = local_to_remote_time_jitter + conn->local_to_remote_time_difference - l2rtd; @@ -684,80 +715,90 @@ void *rtp_timing_receiver(void *arg) { ltd = lt-conn->local_to_remote_time_difference_measurement_time; else ltd = -(conn->local_to_remote_time_difference_measurement_time-lt); - + if (ltd) { - debug(1,"Jitter: %" PRId64 " microseconds in %" PRId64 " microseconds.", (ji * (int64_t)1000000)>>32, (ltd * (int64_t)1000000)>>32); + debug(1,"Jitter: %" PRId64 " microseconds in %" PRId64 " microseconds.", (ji * + (int64_t)1000000)>>32, (ltd * (int64_t)1000000)>>32); debug(1,"Source clock to local clock drift: %.2f ppm.",((1.0*ji)/ltd)*1000000.0); - } + } // uncomment below to print jitter between client's clock and our clock if (ji) { int64_t rtus = (tld*1000000)>>32; - debug(1,"Choosing time difference[%d] with dispersion of %" PRId64 " us with an adjustment of %" PRId64 " us",chosen, rtus, (ji*1000000)>>32); + debug(1,"Choosing time difference[%d] with dispersion of %" PRId64 " us with an + adjustment of %" PRId64 " us",chosen, rtus, (ji*1000000)>>32); } */ - conn->local_to_remote_time_difference = l2rtd; // make this the new local-to-remote-time-difference + conn->local_to_remote_time_difference = + l2rtd; // make this the new local-to-remote-time-difference conn->local_to_remote_time_difference_measurement_time = lt; // done at this time. - + if (first_local_to_remote_time_difference == 0) { first_local_to_remote_time_difference = conn->local_to_remote_time_difference; // first_local_to_remote_time_difference_time = get_absolute_time_in_fp(); } - // here, let's try to use the timing pings that were selected because of their short return times to + // here, let's try to use the timing pings that were selected because of their short + // return times to // estimate a figure for drift between the local clock (x) and the remote clock (y) - + // if we plug in a local interval, we will get back what that is in remote time - + // calculate the line of best fit for relating the local time and the remote time // we will calculate the slope, which is the drift // see https://www.varsitytutors.com/hotmath/hotmath_help/topics/line-of-best-fit - + uint64_t y_bar = 0; // remote timestamp average uint64_t x_bar = 0; // local timestamp average int sample_count = 0; - + // approximate time in seconds to let the system settle down const int settling_time = 60; // number of points to have for calculating a valid drift const int sample_point_minimum = 8; - for (cc = 0; cc < conn->time_ping_count; cc++) - if ((conn->time_pings[cc].chosen) && (conn->time_pings[cc].sequence_number>(settling_time/3))) { // wait for a approximate settling time - y_bar += (conn->time_pings[cc].remote_time>>12); //precision is down to 1/4th of a microsecond - x_bar += (conn->time_pings[cc].local_time>>12); + for (cc = 0; cc < conn->time_ping_count; cc++) + if ((conn->time_pings[cc].chosen) && + (conn->time_pings[cc].sequence_number > + (settling_time / 3))) { // wait for a approximate settling time + y_bar += (conn->time_pings[cc].remote_time >> + 12); // precision is down to 1/4th of a microsecond + x_bar += (conn->time_pings[cc].local_time >> 12); sample_count++; } - - y_bar = y_bar/sample_count; - x_bar = x_bar/sample_count; - + + y_bar = y_bar / sample_count; + x_bar = x_bar / sample_count; + int64_t xid, yid; int64_t mtl, mbl; - mtl=0;mbl=0; + mtl = 0; + mbl = 0; for (cc = 0; cc < conn->time_ping_count; cc++) - if ((conn->time_pings[cc].chosen) && (conn->time_pings[cc].sequence_number>(settling_time/3))) { - - uint64_t slt = conn->time_pings[cc].local_time>>12; + if ((conn->time_pings[cc].chosen) && + (conn->time_pings[cc].sequence_number > (settling_time / 3))) { + + uint64_t slt = conn->time_pings[cc].local_time >> 12; if (slt > x_bar) xid = slt - x_bar; else xid = -(x_bar - slt); - - uint64_t srt = conn->time_pings[cc].remote_time>>12; + + uint64_t srt = conn->time_pings[cc].remote_time >> 12; if (srt > y_bar) yid = srt - y_bar; else yid = -(y_bar - srt); - - mtl = mtl + xid*yid; - mbl = mbl + xid*xid; - } - conn->local_to_remote_time_gradient_sample_count = sample_count; - if (sample_count > sample_point_minimum) { - conn->local_to_remote_time_gradient = (1.0*mtl)/mbl; - // debug(1,"Drift is %12.2f ppm, based on %d samples.",(1.0-conn->local_to_remote_time_gradient)*1000000,sample_count); + + mtl = mtl + xid * yid; + mbl = mbl + xid * xid; + } + conn->local_to_remote_time_gradient_sample_count = sample_count; + if (sample_count > sample_point_minimum) { + conn->local_to_remote_time_gradient = (1.0 * mtl) / mbl; + // debug(1,"Drift is %12.2f ppm, based on %d + // samples.",(1.0-conn->local_to_remote_time_gradient)*1000000,sample_count); } else { - conn->local_to_remote_time_gradient = 1.0; + conn->local_to_remote_time_gradient = 1.0; } } else { debug(2, "Time ping turnaround time: %lld us -- it looks like a timing ping was lost.", @@ -969,7 +1010,8 @@ void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time, debug_mutex_lock(&conn->reference_time_mutex, 1000, 1); *timestamp = conn->reference_timestamp; *remote_timestamp_time = conn->remote_reference_timestamp_time; - *timestamp_time = conn->remote_reference_timestamp_time - local_to_remote_time_difference_now(conn); + *timestamp_time = + conn->remote_reference_timestamp_time - local_to_remote_time_difference_now(conn); // if ((*timestamp == 0) && (*timestamp_time == 0)) { // debug(1,"Reference timestamp is invalid."); //} @@ -984,28 +1026,31 @@ void clear_reference_timestamp(rtsp_conn_info *conn) { } int have_timestamp_timing_information(rtsp_conn_info *conn) { - if (conn->reference_timestamp==0) + if (conn->reference_timestamp == 0) return 0; else return 1; } -// set this to zero to use the rates supplied by the sources, which might not always be completely right... -const int use_nominal_rate = 0; // specify whether to use the nominal input rate, usually 44100 fps +// set this to zero to use the rates supplied by the sources, which might not always be completely +// right... +const int use_nominal_rate = 0; // specify whether to use the nominal input rate, usually 44100 fps int sanitised_source_rate_information(int64_t *frames, uint64_t *time, rtsp_conn_info *conn) { int result = 1; - *frames = conn->input_rate; + *frames = conn->input_rate; *time = (uint64_t)(0x100000000); // one second in fp form - if ((conn->packet_stream_established) && (conn->initial_reference_time) && (conn->initial_reference_timestamp)) { + if ((conn->packet_stream_established) && (conn->initial_reference_time) && + (conn->initial_reference_timestamp)) { int64_t local_frames = conn->reference_timestamp - conn->initial_reference_timestamp; uint64_t local_time = conn->remote_reference_timestamp_time - conn->initial_reference_time; if ((local_frames == 0) || (local_time == 0) || (use_nominal_rate)) { result = 1; } else { - double calculated_frame_rate = ((1.0*local_frames)/local_time)*(uint64_t)0x100000000; - if (((calculated_frame_rate/conn->input_rate) > 1.001) || ((calculated_frame_rate/conn->input_rate) < 0.999)) { - debug(1,"input frame rate out of bounds at %.2f fps.",calculated_frame_rate); + double calculated_frame_rate = ((1.0 * local_frames) / local_time) * (uint64_t)0x100000000; + if (((calculated_frame_rate / conn->input_rate) > 1.001) || + ((calculated_frame_rate / conn->input_rate) < 0.999)) { + debug(1, "input frame rate out of bounds at %.2f fps.", calculated_frame_rate); result = 1; } else { *frames = local_frames; @@ -1017,7 +1062,8 @@ int sanitised_source_rate_information(int64_t *frames, uint64_t *time, rtsp_conn return result; } -// we assume here that the timestamp is a timestamp calculated at the output rate, which could be an integer multiple of the input rate +// we assume here that the timestamp is a timestamp calculated at the output rate, which could be an +// integer multiple of the input rate // the reference timestamps are denominated in terms of the input rate int frame_to_local_time(int64_t timestamp, uint64_t *time, rtsp_conn_info *conn) { @@ -1025,18 +1071,36 @@ int frame_to_local_time(int64_t timestamp, uint64_t *time, rtsp_conn_info *conn) int result = 0; uint64_t time_difference; int64_t frame_difference; - result = sanitised_source_rate_information(&frame_difference,&time_difference,conn); - - int64_t timestamp_interval = timestamp - conn->reference_timestamp*conn->output_sample_ratio; // we could be dealing with multiples of 44100 (nominally) - // debug(1, "Timestamp interval: %" PRId64 " frames with reference timestamp %" PRId64 ".",timestamp_interval,conn->reference_timestamp); + result = sanitised_source_rate_information(&frame_difference, &time_difference, conn); + + int64_t timestamp_interval = + timestamp - + conn->reference_timestamp * + conn->output_sample_ratio; // we could be dealing with multiples of 44100 (nominally) + // debug(1, "Timestamp interval: %" PRId64 " frames with reference timestamp %" PRId64 + // ".",timestamp_interval,conn->reference_timestamp); uint64_t timestamp_interval_time; uint64_t remote_time_of_timestamp; - if (timestamp_interval>=0) { - timestamp_interval_time = (timestamp_interval * time_difference)/(frame_difference*conn->output_sample_ratio); // this is the nominal time, based on the fps specified between current and previous sync frame. - remote_time_of_timestamp = conn->remote_reference_timestamp_time + timestamp_interval_time; // based on the reference timestamp time plus the time interval calculated based on the specified fps. + if (timestamp_interval >= 0) { + timestamp_interval_time = + (timestamp_interval * time_difference) / + (frame_difference * conn->output_sample_ratio); // this is the nominal time, based on the + // fps specified between current and + // previous sync frame. + remote_time_of_timestamp = conn->remote_reference_timestamp_time + + timestamp_interval_time; // based on the reference timestamp time + // plus the time interval calculated based + // on the specified fps. } else { - timestamp_interval_time = ((-timestamp_interval) * time_difference)/(frame_difference*conn->output_sample_ratio); // this is the nominal time, based on the fps specified between current and previous sync frame. - remote_time_of_timestamp = conn->remote_reference_timestamp_time - timestamp_interval_time; // based on the reference timestamp time plus the time interval calculated based on the specified fps. + timestamp_interval_time = + ((-timestamp_interval) * time_difference) / + (frame_difference * conn->output_sample_ratio); // this is the nominal time, based on the + // fps specified between current and + // previous sync frame. + remote_time_of_timestamp = conn->remote_reference_timestamp_time - + timestamp_interval_time; // based on the reference timestamp time + // plus the time interval calculated based + // on the specified fps. } *time = remote_time_of_timestamp - local_to_remote_time_difference_now(conn); debug_mutex_unlock(&conn->reference_time_mutex, 3); @@ -1049,27 +1113,28 @@ int local_time_to_frame(uint64_t time, int64_t *frame, rtsp_conn_info *conn) { uint64_t time_difference; int64_t frame_difference; - result = sanitised_source_rate_information(&frame_difference,&time_difference,conn); + result = sanitised_source_rate_information(&frame_difference, &time_difference, conn); // first, get from [local] time to remote time. uint64_t remote_time = time + local_to_remote_time_difference_now(conn); // next, get the remote time interval from the remote_time to the reference time uint64_t time_interval; - + // here, we calculate the time interval, in terms of remote time if (remote_time >= conn->remote_reference_timestamp_time) time_interval = remote_time - conn->remote_reference_timestamp_time; else time_interval = conn->remote_reference_timestamp_time - remote_time; - - // now, convert the remote time interval into frames using the frame rate we have observed or which has been nominated - int64_t frame_interval = (time_interval * frame_difference)/time_difference; + + // now, convert the remote time interval into frames using the frame rate we have observed or + // which has been nominated + int64_t frame_interval = (time_interval * frame_difference) / time_difference; if (remote_time >= conn->remote_reference_timestamp_time) { // debug(1,"Frame interval is %" PRId64 " frames.",frame_interval); - *frame = (conn->reference_timestamp+frame_interval)*conn->output_sample_ratio; + *frame = (conn->reference_timestamp + frame_interval) * conn->output_sample_ratio; } else { // debug(1,"Frame interval is %" PRId64 " frames.",-frame_interval); - *frame = (conn->reference_timestamp-frame_interval)*conn->output_sample_ratio; + *frame = (conn->reference_timestamp - frame_interval) * conn->output_sample_ratio; } debug_mutex_unlock(&conn->reference_time_mutex, 3); return result; From 2a4abea6e505c80c005352e33b8a53dd7fed81ef Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 15 Aug 2018 10:28:51 +0100 Subject: [PATCH 068/569] Modify the timing to be (hopefully) a bit more gentle in its corrections --- player.c | 45 +++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/player.c b/player.c index fec56ecc..3c4e693c 100644 --- a/player.c +++ b/player.c @@ -1294,6 +1294,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { time_to_wait_for_wakeup_fp *= 4 * 352; // four full 352-frame packets time_to_wait_for_wakeup_fp /= 3; // four thirds of a packet time + time_to_wait_for_wakeup_fp = (uint64_t)0x200000000/(uint64_t)1000; // two millisecond + #ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD uint64_t time_of_wakeup_fp = local_time_now + time_to_wait_for_wakeup_fp; uint64_t sec = time_of_wakeup_fp >> 32; @@ -2117,6 +2119,8 @@ void *player_thread_func(void *arg) { delay - (conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate)); // int64_t from int64_t - int32_t, so okay + + //debug(1,"%" PRId64 "",sync_error,inbuflength); // not too sure if abs() is implemented for int64_t, so we'll do it manually int64_t abs_sync_error = sync_error; @@ -2167,8 +2171,8 @@ void *player_thread_func(void *arg) { } } else { + /* // before we finally commit to this frame, check its sequencing and timing - // require a certain error before bothering to fix it... if (sync_error > config.tolerance * config.output_rate) { // int64_t > int, okay amount_to_stuff = -1; @@ -2176,13 +2180,25 @@ void *player_thread_func(void *arg) { if (sync_error < -config.tolerance * config.output_rate) { amount_to_stuff = 1; } - - // only allow stuffing if there is enough time to do it -- check DAC buffer... - if (current_delay < DAC_BUFFER_QUEUE_MINIMUM_LENGTH) { - // debug(2,"DAC buffer too short(at %lld frames) to allow stuffing.",current_delay); - amount_to_stuff = 0; + */ + + + if (amount_to_stuff==0) { + // use a "V" shaped function to decide if stuffing should occur + int64_t s = r64i(); + s = s>>31; + s = s * config.tolerance * config.output_rate; + s = (s>>32)+config.tolerance * config.output_rate; //should be a number from 0 to config.tolerance * config.output_rate; + if ((sync_error>0) && (sync_error>s)) { + debug(1,"Extra stuff -1"); + amount_to_stuff = -1; + } + if ((sync_error<0) && (sync_error<(-s))) { + debug(1,"Extra stuff +1"); + amount_to_stuff = 1; + } } - + // try to keep the corrections definitely below 1 in 1000 audio frames // calculate the time elapsed since the play session started. @@ -2260,23 +2276,20 @@ void *player_thread_func(void *arg) { tbuf32[2 * i + 1] = fbuf_r[i]; } } - - switch (config.packet_stuffing) { - case ST_basic: - // if (amount_to_stuff) debug(1,"Basic stuff..."); + + if ((current_delay < DAC_BUFFER_QUEUE_MINIMUM_LENGTH) || (config.packet_stuffing == ST_basic)) { play_samples = stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format, - conn->outbuf, amount_to_stuff, enable_dither, conn); - break; - case ST_soxr: + conn->outbuf, amount_to_stuff, enable_dither, conn); + } #ifdef HAVE_LIBSOXR + else if (config.packet_stuffing == ST_soxr) { // if (amount_to_stuff) debug(1,"Soxr stuff..."); play_samples = stuff_buffer_soxr_32((int32_t *)conn->tbuf, (int32_t *)conn->sbuf, inbuflength, config.output_format, conn->outbuf, amount_to_stuff, enable_dither, conn); -#endif - break; } +#endif /* { From 9a005c8ec410ae018abcf3e0a5d5f2f57060317e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 15 Aug 2018 10:36:29 +0100 Subject: [PATCH 069/569] Quieten a debug message --- player.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/player.c b/player.c index 3c4e693c..3b724ad8 100644 --- a/player.c +++ b/player.c @@ -2190,11 +2190,11 @@ void *player_thread_func(void *arg) { s = s * config.tolerance * config.output_rate; s = (s>>32)+config.tolerance * config.output_rate; //should be a number from 0 to config.tolerance * config.output_rate; if ((sync_error>0) && (sync_error>s)) { - debug(1,"Extra stuff -1"); + //debug(1,"Extra stuff -1"); amount_to_stuff = -1; } if ((sync_error<0) && (sync_error<(-s))) { - debug(1,"Extra stuff +1"); + //debug(1,"Extra stuff +1"); amount_to_stuff = 1; } } From 3adcf000a02ddc0f618f15c9508ad51f79f55da6 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 15 Aug 2018 12:41:19 +0100 Subject: [PATCH 070/569] Quieten a debug message --- player.c | 1 - 1 file changed, 1 deletion(-) diff --git a/player.c b/player.c index 3b724ad8..5853a4ac 100644 --- a/player.c +++ b/player.c @@ -532,7 +532,6 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp conn->frames_inward_measurement_start_time = reception_time; conn->frames_inward_frames_received_at_measurement_start_time = timestamp; conn->input_frame_rate_starting_point_is_valid = 1; // valid now - debug(1, "input_frame_rate_starting_point_is_valid set"); } } From ab1f55fe2077371b5459a2031cc94c90fe941e82 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 15 Aug 2018 12:42:02 +0100 Subject: [PATCH 071/569] Log an entry if a different period or buffer size was requested and not obtained. --- audio_alsa.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/audio_alsa.c b/audio_alsa.c index 577c01c2..0cfe4a72 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -658,6 +658,26 @@ int open_alsa_device(void) { snd_strerror(ret)); return -7; } + + // check parameters after attempting to set them… + + if (set_period_size_request != 0) { + snd_pcm_uframes_t actual_period_size; + snd_pcm_hw_params_get_period_size(alsa_params, &actual_period_size, &dir); + if (actual_period_size != period_size_requested) + inform("Actual period size set to a different value than requested. Requested: %lu, actual " + "setting: %lu", + period_size_requested, actual_period_size); + } + + if (set_buffer_size_request != 0) { + snd_pcm_uframes_t actual_buffer_size; + snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_size); + if (actual_buffer_size != buffer_size_requested) + inform("Actual period size set to a different value than requested. Requested: %lu, actual " + "setting: %lu", + buffer_size_requested, actual_buffer_size); + } if (my_sample_rate != desired_sample_rate) { warn("Can't set the D/A converter to %d.", desired_sample_rate); From ac336974b6ca74147aae7253d9961213f119a665 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 15 Aug 2018 15:56:14 +0100 Subject: [PATCH 072/569] Quieten a debug message --- rtp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtp.c b/rtp.c index 9d3c6f76..f15cbf02 100644 --- a/rtp.c +++ b/rtp.c @@ -1050,7 +1050,7 @@ int sanitised_source_rate_information(int64_t *frames, uint64_t *time, rtsp_conn double calculated_frame_rate = ((1.0 * local_frames) / local_time) * (uint64_t)0x100000000; if (((calculated_frame_rate / conn->input_rate) > 1.001) || ((calculated_frame_rate / conn->input_rate) < 0.999)) { - debug(1, "input frame rate out of bounds at %.2f fps.", calculated_frame_rate); + // debug(1, "input frame rate out of bounds at %.2f fps.", calculated_frame_rate); result = 1; } else { *frames = local_frames; From 0cda8f963ce9da4338061ac87c8c029d62decf75 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 15 Aug 2018 15:57:41 +0100 Subject: [PATCH 073/569] Simplify some calculations and a bug so that the output calculation is correclty reset if there is a problem with output, e.g. an underflow. --- audio_alsa.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 0cfe4a72..ea6ab91f 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -882,6 +882,8 @@ int delay(long *the_delay) { debug(1, "Error %d in delay(): \"%s\". Delay reported is %d frames.", reply, snd_strerror(reply), *the_delay); snd_pcm_recover(alsa_handle, reply, 1); + frame_index = 0; + measurement_data_is_valid = 0; } } else { frame_index = 0; // we'll be starting over... @@ -980,20 +982,17 @@ static int play(void *buf, int samples) { long fl = 0; err2 = snd_pcm_delay(alsa_handle, &fl); if (err2 != 0) { - frame_index = 0; - measurement_data_is_valid = 0; debug(1, "Error %d in delay(): \"%s\". Delay reported is %d frames.", err2, snd_strerror(err2), fl); snd_pcm_recover(alsa_handle, err2, 1); + frame_index = 0; + measurement_data_is_valid = 0; } - uint64_t tf = get_absolute_time_in_fp(); + measurement_time = get_absolute_time_in_fp(); frames_played_at_measurement_time = frames_sent_for_playing - fl; if (frame_index == start_measurement_from_this_frame) { frames_played_at_measurement_start_time = frames_played_at_measurement_time; - measurement_start_time = tf; - } else { - frames_played_at_measurement_time = frames_played_at_measurement_time; - measurement_time = tf; + measurement_start_time = measurement_time; measurement_data_is_valid = 1; } } From c101d5adf672e507ba86d019d35c0b06def95637 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 15 Aug 2018 15:58:13 +0100 Subject: [PATCH 074/569] Quieten some error messages --- player.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/player.c b/player.c index 5853a4ac..3c2a1c28 100644 --- a/player.c +++ b/player.c @@ -2096,9 +2096,10 @@ void *player_thread_func(void *arg) { int64_t should_be_frame; local_time_to_frame(local_time_now, &should_be_frame, conn); - if (abs(td_in_frames + rt - should_be_frame) > 10 * conn->output_sample_ratio) - debug(1, "Difference between old and new frame number is %" PRId64 " frames.", - td_in_frames + rt - should_be_frame); + //if (abs(td_in_frames + rt - should_be_frame) > 10 * conn->output_sample_ratio) + // debug(1, "Difference between old and new frame number is %" PRId64 " frames.", + // td_in_frames + rt - should_be_frame); + // this is the actual delay, including the latency we actually want, which will // fluctuate a good bit about a potentially rising or falling trend. From c99591253a1362ecd1c06f4d5b596df4b3337e1b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 19 Aug 2018 11:54:03 +0100 Subject: [PATCH 075/569] Add an example of getting and setting the drift tolerance. --- documents/sample dbus commands | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/documents/sample dbus commands b/documents/sample dbus commands index 0a7c398d..23804159 100644 --- a/documents/sample dbus commands +++ b/documents/sample dbus commands @@ -20,4 +20,9 @@ dbus-send --system --print-reply --type=method_call --dest=org.gnome.ShairportSy #Remote Control commands include: Play, Pause, PlayPause, Resume, Stop, Next, Previous, VolumeUp, VolumeDown, ToggleMute, FastForward, Rewind, ShuffleSongs # Set Volume using Advanced Remote Control -dbus-send --system --print-reply --type=method_call --dest=org.gnome.ShairportSync '/org/gnome/ShairportSync' org.gnome.ShairportSync.AdvancedRemoteControl.SetVolume int32:50 \ No newline at end of file +dbus-send --system --print-reply --type=method_call --dest=org.gnome.ShairportSync '/org/gnome/ShairportSync' org.gnome.ShairportSync.AdvancedRemoteControl.SetVolume int32:50 + +# Get Drift Tolerance +dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync string:DriftTolerance +# Set Drift Tolerance to 1 millisecond +dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync string:DriftTolerance variant:double:0.001 From fe8480b74bc76732270206d33d2defeb4017c572 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 20 Aug 2018 18:50:58 +0100 Subject: [PATCH 076/569] Fix up System V initialisation file to start with user and group "shairport-sync" and to be able to locate the PID file correcly so as to kill it later. --- scripts/shairport-sync.in | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/scripts/shairport-sync.in b/scripts/shairport-sync.in index 6bcfe9fe..e65e51e6 100755 --- a/scripts/shairport-sync.in +++ b/scripts/shairport-sync.in @@ -23,8 +23,7 @@ DAEMON=@prefix@/bin/$NAME # impossible to pass as arguments. # Instead, we add the arguments directly to the relevant line in the do_start() function below - -PIDFILE=/var/run/$NAME.pid +PIDFILE=/var/run/$NAME/$NAME.pid SCRIPTNAME=/etc/init.d/$NAME # Exit if the package is not installed @@ -50,29 +49,16 @@ do_start() # 0 if daemon has been started # 1 if daemon was already running # 2 if daemon could not be started - start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ + start-stop-daemon -c shairport-sync:shairport-sync --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ || return 1 -# This script is set to start running after all other services have started. -# However, if you find that Shairport Sync is still being started before what it needs is ready, -# uncomment the next line to get the script to wait for three seconds before attempting to start Shairport Sync. -# sleep 3 + # This script is set to start running after all other services have started. + # However, if you find that Shairport Sync is still being started before what it needs is ready, + # uncomment the next line to get the script to wait for three seconds before attempting to start Shairport Sync. + # sleep 3 -# Uncomment just one of the following start-stop-daemon lines, or comment them all out and add your own. -# Shairport Sync will read settings from the configuration file (/etc/shairport-sync.conf by default) and will then apply any command line options. -# In the default script, the first line is uncommented, selecting daemon mode (-d). Settings will be taken from the configuration file. -# BTW, if you're using software volume control, you may have to use alsamixer or similar to set the output device's volume to its maximum level first -# BTW2, you can use alsamixer to find device identifiers (e.g. hw:1) and mixer names (e.g. "Speaker"). No need to change ALSA's defaults. -# BTW3, the argument after -a is simply the name the shairport service will be visible as. - start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d || return 2 -# start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d -a "Topping TP30 or Griffin iMic on Raspberry Pi" -- -d hw:1 -t hardware -c "PCM" || return 2 -# start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d -a "'3D Sound' USB Soundcard on Raspberry Pi" -- -d hw:1 -t hardware -c "Speaker" || return 2 -# BTW, that "3D Sound" USB soundcard sometimes has the mixer name "Headphone" rather than "Speaker" -- use alsamixer to check. -# start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d -a "IQaudIO" -- -d hw:1 -t hardware -c "Playback Digital" || return 2 -# BTW, newer versions of IQaudIO have a different mixer name -- use alsamixer to check. - # Add code here, if necessary, that waits for the process to be ready - # to handle requests from services started subsequently which depend - # on this one. As a last resort, sleep for some time. + # Shairport Sync reads settings from the configuration file (/etc/shairport-sync.conf by default) and will then apply any command line options. + start-stop-daemon -c shairport-sync:shairport-sync --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d || return 2 } # From d5197081ef0506b0ed16a4037d6fbfb79085c5f6 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 20 Aug 2018 19:53:16 +0100 Subject: [PATCH 077/569] Fix a stupid divide-by-zero error and remove unrecognised warning options for FreeBSD --- Makefile.am | 2 +- player.c | 6 +++++- rtp.c | 55 ++++++++++++++++++++++++++--------------------------- 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/Makefile.am b/Makefile.am index 83fc2132..cc88b530 100644 --- a/Makefile.am +++ b/Makefile.am @@ -8,7 +8,7 @@ shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp. AM_CFLAGS = -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\" if BUILD_FOR_FREEBSD - AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\" -O2 + AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\" -O2 else if BUILD_FOR_OPENBSD AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\" diff --git a/player.c b/player.c index 5853a4ac..47758a9a 100644 --- a/player.c +++ b/player.c @@ -2095,8 +2095,12 @@ void *player_thread_func(void *arg) { int64_t should_be_frame; local_time_to_frame(local_time_now, &should_be_frame, conn); + + int64_t absolute_difference_in_frames = td_in_frames + rt - should_be_frame; + if (absolute_difference_in_frames < 0) + absolute_difference_in_frames = -absolute_difference_in_frames; - if (abs(td_in_frames + rt - should_be_frame) > 10 * conn->output_sample_ratio) + if (absolute_difference_in_frames > 10 * conn->output_sample_ratio) debug(1, "Difference between old and new frame number is %" PRId64 " frames.", td_in_frames + rt - should_be_frame); // this is the actual delay, including the latency we actually want, which will diff --git a/rtp.c b/rtp.c index 9d3c6f76..5b5b3d3b 100644 --- a/rtp.c +++ b/rtp.c @@ -765,35 +765,34 @@ void *rtp_timing_receiver(void *arg) { x_bar += (conn->time_pings[cc].local_time >> 12); sample_count++; } - - y_bar = y_bar / sample_count; - x_bar = x_bar / sample_count; - - int64_t xid, yid; - int64_t mtl, mbl; - mtl = 0; - mbl = 0; - for (cc = 0; cc < conn->time_ping_count; cc++) - if ((conn->time_pings[cc].chosen) && - (conn->time_pings[cc].sequence_number > (settling_time / 3))) { - - uint64_t slt = conn->time_pings[cc].local_time >> 12; - if (slt > x_bar) - xid = slt - x_bar; - else - xid = -(x_bar - slt); - - uint64_t srt = conn->time_pings[cc].remote_time >> 12; - if (srt > y_bar) - yid = srt - y_bar; - else - yid = -(y_bar - srt); - - mtl = mtl + xid * yid; - mbl = mbl + xid * xid; - } - conn->local_to_remote_time_gradient_sample_count = sample_count; if (sample_count > sample_point_minimum) { + y_bar = y_bar / sample_count; + x_bar = x_bar / sample_count; + + int64_t xid, yid; + int64_t mtl, mbl; + mtl = 0; + mbl = 0; + for (cc = 0; cc < conn->time_ping_count; cc++) + if ((conn->time_pings[cc].chosen) && + (conn->time_pings[cc].sequence_number > (settling_time / 3))) { + + uint64_t slt = conn->time_pings[cc].local_time >> 12; + if (slt > x_bar) + xid = slt - x_bar; + else + xid = -(x_bar - slt); + + uint64_t srt = conn->time_pings[cc].remote_time >> 12; + if (srt > y_bar) + yid = srt - y_bar; + else + yid = -(y_bar - srt); + + mtl = mtl + xid * yid; + mbl = mbl + xid * xid; + } + conn->local_to_remote_time_gradient_sample_count = sample_count; conn->local_to_remote_time_gradient = (1.0 * mtl) / mbl; // debug(1,"Drift is %12.2f ppm, based on %d // samples.",(1.0-conn->local_to_remote_time_gradient)*1000000,sample_count); From 29e3890280d3726dfc563de53392ae3dc3229a19 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 20 Aug 2018 20:14:28 +0100 Subject: [PATCH 078/569] Put inclusion of sys/socket.h before net/if.h to prevent errors in OpenBSD compilation --- shairport.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shairport.c b/shairport.c index bd4025dc..057d4ed0 100644 --- a/shairport.c +++ b/shairport.c @@ -31,11 +31,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include #include From ae73f37b2416cf31f11ff6bee2745b69b9058901 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 20 Aug 2018 21:57:46 +0100 Subject: [PATCH 079/569] Update shairport-sync.freebsd --- scripts/shairport-sync.freebsd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/shairport-sync.freebsd b/scripts/shairport-sync.freebsd index 9088bd82..4e812dd0 100644 --- a/scripts/shairport-sync.freebsd +++ b/scripts/shairport-sync.freebsd @@ -14,7 +14,7 @@ stop_cmd="${name}_stop" shairport_sync_start() { - checkyesno shairport_sync_enable && echo "Starting shairport-sync." && \ + checkyesno shairport_sync_enable && echo "Starting shairport-sync." && \ su -m shairport-sync -c "/usr/local/bin/shairport-sync -d" } From 711b6e64139b8e3906b50c5e26fba4f503edcd7f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 20 Aug 2018 22:28:36 +0100 Subject: [PATCH 080/569] Move creation of the /var/run/shairport-sync directory from install time to just before launch -- it may be deleted when the machine is powered down. --- Makefile.am | 2 -- scripts/shairport-sync.in | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index cc88b530..635b4aea 100644 --- a/Makefile.am +++ b/Makefile.am @@ -144,8 +144,6 @@ endif if INSTALL_SYSTEMV getent group shairport-sync &>/dev/null || groupadd -r shairport-sync >/dev/null getent passwd shairport-sync &> /dev/null || useradd -r -M -g shairport-sync -s /usr/bin/nologin -G audio shairport-sync >/dev/null - [ -e /var/run/shairport-sync ] || mkdir -p /var/run/shairport-sync - chown shairport-sync:shairport-sync /var/run/shairport-sync [ -e $(DESTDIR)$(sysconfdir)/init.d ] || mkdir -p $(DESTDIR)$(sysconfdir)/init.d [ -f $(DESTDIR)$(sysconfdir)/init.d/shairport-sync ] || cp scripts/shairport-sync $(DESTDIR)$(sysconfdir)/init.d/ endif diff --git a/scripts/shairport-sync.in b/scripts/shairport-sync.in index e65e51e6..46d8c5fc 100755 --- a/scripts/shairport-sync.in +++ b/scripts/shairport-sync.in @@ -49,6 +49,7 @@ do_start() # 0 if daemon has been started # 1 if daemon was already running # 2 if daemon could not be started + [ -e /var/run/shairport-sync ] || ( mkdir -p /var/run/shairport-sync && chown shairport-sync:shairport-sync /var/run/shairport-sync ) ; \ start-stop-daemon -c shairport-sync:shairport-sync --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ || return 1 From 92bd0254e35c831cd2616a1df3b41cf9f0802366 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 20 Aug 2018 22:44:27 +0100 Subject: [PATCH 081/569] Tidy the init.d script up a little more to reflect the true situation upon (attempted) startup --- scripts/shairport-sync.in | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/shairport-sync.in b/scripts/shairport-sync.in index 46d8c5fc..f229f19b 100755 --- a/scripts/shairport-sync.in +++ b/scripts/shairport-sync.in @@ -47,19 +47,19 @@ do_start() { # Return # 0 if daemon has been started - # 1 if daemon was already running - # 2 if daemon could not be started - [ -e /var/run/shairport-sync ] || ( mkdir -p /var/run/shairport-sync && chown shairport-sync:shairport-sync /var/run/shairport-sync ) ; \ - start-stop-daemon -c shairport-sync:shairport-sync --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ - || return 1 + # 1 if PID directory didn't exist and couldn't be created with approproate permission + # 2 if daemon was already running + # 3 if daemon could not be started + [ -e /var/run/shairport-sync ] || ( mkdir -p /var/run/shairport-sync && chown shairport-sync:shairport-sync /var/run/shairport-sync ) || return 1 + start-stop-daemon -c shairport-sync:shairport-sync --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null || return 2 # This script is set to start running after all other services have started. # However, if you find that Shairport Sync is still being started before what it needs is ready, # uncomment the next line to get the script to wait for three seconds before attempting to start Shairport Sync. # sleep 3 - # Shairport Sync reads settings from the configuration file (/etc/shairport-sync.conf by default) and will then apply any command line options. - start-stop-daemon -c shairport-sync:shairport-sync --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d || return 2 + # Settings from the configuration file (@sysconfdir@/shairport-sync.conf by default) + start-stop-daemon -c shairport-sync:shairport-sync --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d || return 3 } # From 1ee806a53ac2c0d37bd803421fc8fedb941e47c8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 22 Aug 2018 13:02:42 +0100 Subject: [PATCH 082/569] Fix display of drift -- the sign is correct now. All timing is relative to the local clock. Add a rough estimate of the correction necessary. Use the most recent 128 timing samples. --- audio_alsa.c | 15 +++++- player.c | 142 +++++++++++++++++++++++++++++++++------------------ player.h | 2 +- rtp.c | 3 +- 4 files changed, 107 insertions(+), 55 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 0cfe4a72..5b4d608f 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -882,7 +882,13 @@ int delay(long *the_delay) { debug(1, "Error %d in delay(): \"%s\". Delay reported is %d frames.", reply, snd_strerror(reply), *the_delay); snd_pcm_recover(alsa_handle, reply, 1); - } + } else { + if (*the_delay==0) { + // there's nothing in the pipeline, so we can't measure frame rate. + frame_index = 0; // we'll be starting over... + measurement_data_is_valid = 0; + } + } } else { frame_index = 0; // we'll be starting over... measurement_data_is_valid = 0; @@ -985,7 +991,14 @@ static int play(void *buf, int samples) { debug(1, "Error %d in delay(): \"%s\". Delay reported is %d frames.", err2, snd_strerror(err2), fl); snd_pcm_recover(alsa_handle, err2, 1); + } else { + if (fl==0) { + // there's nothing in the pipeline, so we can't measure frame rate. + frame_index = 0; // we'll be starting over... + measurement_data_is_valid = 0; + } } + uint64_t tf = get_absolute_time_in_fp(); frames_played_at_measurement_time = frames_sent_for_playing - fl; if (frame_index == start_measurement_from_this_frame) { diff --git a/player.c b/player.c index 47758a9a..ce1056d8 100644 --- a/player.c +++ b/player.c @@ -1282,6 +1282,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (notified_buffer_empty == 0) { debug(3, "Buffers exhausted."); notified_buffer_empty = 1; + conn->initial_reference_time = 0; + conn->initial_reference_timestamp = 0; + conn->input_frame_rate_starting_point_is_valid = 0; } do_wait = 1; } @@ -1813,10 +1816,12 @@ void *player_thread_func(void *arg) { "min DAC queue size, " "min buffer occupancy, " "max buffer occupancy, " - "source frames per second, " - "average input frames per second, " + "source nominal frames per second, " + "source actual frames per second, " "output frames per second, " - "client:local clock drift in ppm"); + "source clock drift in ppm, " + "source clock drift sample count, " + "rough calculated correction in ppm"); } else { inform("sync error in milliseconds, " "total packets, " @@ -1827,7 +1832,10 @@ void *player_thread_func(void *arg) { "min DAC queue size, " "min buffer occupancy, " "max buffer occupancy, " - "input frames per second."); + "source nominal frames per second, " + "source actual frames per second, " + "source clock drift in ppm, " + "source clock drift sample count"); } } else { inform("sync error in milliseconds, " @@ -1838,7 +1846,10 @@ void *player_thread_func(void *arg) { "resend requests, " "min buffer occupancy, " "max buffer occupancy, " - "input frames per second."); + "source nominal frames per second, " + "source actual frames per second, " + "source clock drift in ppm, " + "source clock drift sample count"); } } @@ -2466,63 +2477,92 @@ void *player_thread_func(void *arg) { "%*.1f," /* net correction in ppm */ "%*.1f," /* corrections in ppm */ "%*d," /* total packets */ - "%*llu," /* missing packets */ - "%*llu," /* late packets */ - "%*llu," /* too late packets */ - "%*llu," /* resend requests */ - "%*lli," /* min DAC queue size */ - "%*d," /* min buffer occupancy */ - "%*d," /* max buffer occupancy */ - "%*.2f," /* remote frame rate */ - "%*.2f," /* actual (average) input frame rate */ - "%*.2f," /* output frame rate */ - "%*.2f," /* output frame rate */ - "%*d", /* sample count */ - 10, - 1000 * moving_average_sync_error / config.output_rate, 10, - moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), + "%*" PRIu64 "," /* missing packets */ + "%*" PRIu64 "," /* late packets */ + "%*" PRIu64 "," /* too late packets */ + "%*" PRIu64 "," /* resend requests */ + "%*" PRId64 "," /* min DAC queue size */ + "%*" PRId32 "," /* min buffer occupancy */ + "%*" PRId32 "," /* max buffer occupancy */ + "%*.2f," /* source nominal frame rate */ + "%*.2f," /* source actual (average) frame rate */ + "%*.2f," /* output frame rate */ + "%*.2f," /* source clock drift */ + "%*d," /* source clock drift sample count */ + "%*.2f", /* rough calculated correction in ppm */ + 10, 1000 * moving_average_sync_error / config.output_rate, + 10, moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), 10, moving_average_insertions_plus_deletions * 1000000 / (352 * conn->output_sample_ratio), - 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, - conn->too_late_packets, 7, conn->resend_requests, 7, - minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, - maximum_buffer_occupancy, 11, conn->remote_frame_rate, 11, - conn->input_frame_rate, 11, conn->frame_rate, 10, - (1.0 - conn->local_to_remote_time_gradient) * 1000000, 6, - conn->local_to_remote_time_gradient_sample_count); + 12, play_number, + 7, conn->missing_packets, + 7, conn->late_packets, + 7, conn->too_late_packets, + 7, conn->resend_requests, + 7, minimum_dac_queue_size, + 5, minimum_buffer_occupancy, + 5, maximum_buffer_occupancy, + 11, conn->remote_frame_rate, + 11, conn->input_frame_rate, + 11, conn->frame_rate, + 10, (conn->local_to_remote_time_gradient-1.0) * 1000000, + 6, conn->local_to_remote_time_gradient_sample_count, + 10, (conn->frame_rate > 0.0) ? ((conn->frame_rate - conn->remote_frame_rate * conn->output_sample_ratio * conn->local_to_remote_time_gradient)*1000000)/conn->frame_rate : 0.0); } else { inform("%*.2f," /* Sync error in milliseconds */ "%*d," /* total packets */ - "%*llu," /* missing packets */ - "%*llu," /* late packets */ - "%*llu," /* too late packets */ - "%*llu," /* resend requests */ - "%*lli," /* min DAC queue size */ - "%*d," /* min buffer occupancy */ - "%*d," /* max buffer occupancy */ - "%*.2f", /* input frame rate */ + "%*" PRIu64 "," /* missing packets */ + "%*" PRIu64 "," /* late packets */ + "%*" PRIu64 "," /* too late packets */ + "%*" PRIu64 "," /* resend requests */ + "%*" PRId64 "," /* min DAC queue size */ + "%*" PRId32 "," /* min buffer occupancy */ + "%*" PRId32 "," /* max buffer occupancy */ + "%*.2f," /* source nominal frame rate */ + "%*.2f," /* source actual (average) frame rate */ + "%*.2f," /* source clock drift */ + "%*d", /* source clock drift sample count */ 10, - 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, - conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, - conn->resend_requests, 7, minimum_dac_queue_size, 5, - minimum_buffer_occupancy, 5, maximum_buffer_occupancy, 11, - conn->input_frame_rate); + 1000 * moving_average_sync_error / config.output_rate, + 12, play_number, + 7, conn->missing_packets, + 7, conn->late_packets, + 7, conn->too_late_packets, + 7, conn->resend_requests, + 7, minimum_dac_queue_size, + 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); } } else { inform("%*.2f," /* Sync error in milliseconds */ "%*d," /* total packets */ - "%*llu," /* missing packets */ - "%*llu," /* late packets */ - "%*llu," /* too late packets */ - "%*llu," /* resend requests */ - "%*d," /* min buffer occupancy */ - "%*d," /* max buffer occupancy */ - "%*.2f", /* input frame rate */ + "%*" PRIu64 "," /* missing packets */ + "%*" PRIu64 "," /* late packets */ + "%*" PRIu64 "," /* too late packets */ + "%*" PRIu64 "," /* resend requests */ + "%*" PRId32 "," /* min buffer occupancy */ + "%*" PRId32 "," /* max buffer occupancy */ + "%*.2f," /* source nominal frame rate */ + "%*.2f," /* source actual (average) frame rate */ + "%*.2f," /* source clock drift */ + "%*d", /* source clock drift sample count */ 10, - 1000 * moving_average_sync_error / config.output_rate, 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->input_frame_rate); + 1000 * moving_average_sync_error / config.output_rate, + 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); } } else { inform("No frames received in the last sampling interval."); diff --git a/player.h b/player.h index 078a96a0..3bc415e4 100644 --- a/player.h +++ b/player.h @@ -24,7 +24,7 @@ #include "alac.h" #include "audio.h" -#define time_ping_history 64 // at 1 per three seconds, approximately three minutes of records +#define time_ping_history 128 // at 1 per three seconds, approximately six minutes of records typedef struct time_ping_record { uint64_t local_to_remote_difference; diff --git a/rtp.c b/rtp.c index 5b5b3d3b..41d1747d 100644 --- a/rtp.c +++ b/rtp.c @@ -794,11 +794,10 @@ void *rtp_timing_receiver(void *arg) { } conn->local_to_remote_time_gradient_sample_count = sample_count; conn->local_to_remote_time_gradient = (1.0 * mtl) / mbl; - // debug(1,"Drift is %12.2f ppm, based on %d - // samples.",(1.0-conn->local_to_remote_time_gradient)*1000000,sample_count); } else { conn->local_to_remote_time_gradient = 1.0; } + // debug(1,"local to remote time gradient is %12.2f ppm, based on %d samples.",conn->local_to_remote_time_gradient*1000000,sample_count); } else { debug(2, "Time ping turnaround time: %lld us -- it looks like a timing ping was lost.", (return_time * 1000000) >> 32); From 754155ce0f3a7b33c6e4e97da72bd33d495017a4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 22 Aug 2018 13:07:04 +0100 Subject: [PATCH 083/569] clang format --- audio_alsa.c | 10 +-- player.c | 176 ++++++++++++++++++++++++--------------------------- rtp.c | 3 +- shairport.c | 2 +- 4 files changed, 92 insertions(+), 99 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 5b4d608f..cee43e9d 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -658,7 +658,7 @@ int open_alsa_device(void) { snd_strerror(ret)); return -7; } - + // check parameters after attempting to set them… if (set_period_size_request != 0) { @@ -883,12 +883,12 @@ int delay(long *the_delay) { snd_strerror(reply), *the_delay); snd_pcm_recover(alsa_handle, reply, 1); } else { - if (*the_delay==0) { + if (*the_delay == 0) { // there's nothing in the pipeline, so we can't measure frame rate. frame_index = 0; // we'll be starting over... measurement_data_is_valid = 0; } - } + } } else { frame_index = 0; // we'll be starting over... measurement_data_is_valid = 0; @@ -992,13 +992,13 @@ static int play(void *buf, int samples) { snd_strerror(err2), fl); snd_pcm_recover(alsa_handle, err2, 1); } else { - if (fl==0) { + if (fl == 0) { // there's nothing in the pipeline, so we can't measure frame rate. frame_index = 0; // we'll be starting over... measurement_data_is_valid = 0; } } - + uint64_t tf = get_absolute_time_in_fp(); frames_played_at_measurement_time = frames_sent_for_playing - fl; if (frame_index == start_measurement_from_this_frame) { diff --git a/player.c b/player.c index ce1056d8..bdc5ffe1 100644 --- a/player.c +++ b/player.c @@ -1296,7 +1296,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { time_to_wait_for_wakeup_fp *= 4 * 352; // four full 352-frame packets time_to_wait_for_wakeup_fp /= 3; // four thirds of a packet time - time_to_wait_for_wakeup_fp = (uint64_t)0x200000000/(uint64_t)1000; // two millisecond + time_to_wait_for_wakeup_fp = (uint64_t)0x200000000 / (uint64_t)1000; // two millisecond #ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD uint64_t time_of_wakeup_fp = local_time_now + time_to_wait_for_wakeup_fp; @@ -2106,7 +2106,7 @@ void *player_thread_func(void *arg) { int64_t should_be_frame; local_time_to_frame(local_time_now, &should_be_frame, conn); - + int64_t absolute_difference_in_frames = td_in_frames + rt - should_be_frame; if (absolute_difference_in_frames < 0) absolute_difference_in_frames = -absolute_difference_in_frames; @@ -2133,8 +2133,8 @@ void *player_thread_func(void *arg) { delay - (conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate)); // int64_t from int64_t - int32_t, so okay - - //debug(1,"%" PRId64 "",sync_error,inbuflength); + + // debug(1,"%" PRId64 "",sync_error,inbuflength); // not too sure if abs() is implemented for int64_t, so we'll do it manually int64_t abs_sync_error = sync_error; @@ -2195,24 +2195,25 @@ void *player_thread_func(void *arg) { amount_to_stuff = 1; } */ - - - if (amount_to_stuff==0) { + + if (amount_to_stuff == 0) { // use a "V" shaped function to decide if stuffing should occur int64_t s = r64i(); - s = s>>31; + s = s >> 31; s = s * config.tolerance * config.output_rate; - s = (s>>32)+config.tolerance * config.output_rate; //should be a number from 0 to config.tolerance * config.output_rate; - if ((sync_error>0) && (sync_error>s)) { - //debug(1,"Extra stuff -1"); + s = (s >> 32) + config.tolerance * config.output_rate; // should be a number from 0 + // to config.tolerance * + // config.output_rate; + if ((sync_error > 0) && (sync_error > s)) { + // debug(1,"Extra stuff -1"); amount_to_stuff = -1; } - if ((sync_error<0) && (sync_error<(-s))) { - //debug(1,"Extra stuff +1"); + if ((sync_error < 0) && (sync_error < (-s))) { + // debug(1,"Extra stuff +1"); amount_to_stuff = 1; - } + } } - + // try to keep the corrections definitely below 1 in 1000 audio frames // calculate the time elapsed since the play session started. @@ -2290,14 +2291,15 @@ void *player_thread_func(void *arg) { tbuf32[2 * i + 1] = fbuf_r[i]; } } - - if ((current_delay < DAC_BUFFER_QUEUE_MINIMUM_LENGTH) || (config.packet_stuffing == ST_basic)) { + + if ((current_delay < DAC_BUFFER_QUEUE_MINIMUM_LENGTH) || + (config.packet_stuffing == ST_basic)) { play_samples = stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format, - conn->outbuf, amount_to_stuff, enable_dither, conn); + conn->outbuf, amount_to_stuff, enable_dither, conn); } #ifdef HAVE_LIBSOXR - else if (config.packet_stuffing == ST_soxr) { + else if (config.packet_stuffing == ST_soxr) { // if (amount_to_stuff) debug(1,"Soxr stuff..."); play_samples = stuff_buffer_soxr_32((int32_t *)conn->tbuf, (int32_t *)conn->sbuf, inbuflength, config.output_format, conn->outbuf, @@ -2451,10 +2453,10 @@ void *player_thread_func(void *arg) { conn->frame_rate = (1.0 * frames_played) / elapsed_play_time; // an IEEE double calculation with two 64-bit integers - conn->frame_rate = conn->frame_rate * - (uint64_t)0x100000000; // this should just change the [binary] - // exponent in the IEEE FP representation; - // the mantissa should be unaffected. + conn->frame_rate = + conn->frame_rate * (uint64_t)0x100000000; // this should just change the [binary] + // exponent in the IEEE FP representation; + // the mantissa should be unaffected. } else { conn->frame_rate = 0.0; } @@ -2473,96 +2475,86 @@ void *player_thread_func(void *arg) { if ((config.output->delay)) { if (config.no_sync == 0) { - inform("%*.2f," /* Sync error in milliseconds */ - "%*.1f," /* net correction in ppm */ - "%*.1f," /* corrections in ppm */ - "%*d," /* total packets */ + inform("%*.2f," /* Sync error in milliseconds */ + "%*.1f," /* net correction in ppm */ + "%*.1f," /* corrections in ppm */ + "%*d," /* total packets */ "%*" PRIu64 "," /* missing packets */ "%*" PRIu64 "," /* late packets */ "%*" PRIu64 "," /* too late packets */ "%*" PRIu64 "," /* resend requests */ "%*" PRId64 "," /* min DAC queue size */ - "%*" PRId32 "," /* min buffer occupancy */ - "%*" PRId32 "," /* max buffer occupancy */ - "%*.2f," /* source nominal frame rate */ - "%*.2f," /* source actual (average) frame rate */ - "%*.2f," /* output frame rate */ - "%*.2f," /* source clock drift */ - "%*d," /* source clock drift sample count */ - "%*.2f", /* rough calculated correction in ppm */ - 10, 1000 * moving_average_sync_error / config.output_rate, - 10, moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), + "%*" PRId32 "," /* min buffer occupancy */ + "%*" PRId32 "," /* max buffer occupancy */ + "%*.2f," /* source nominal frame rate */ + "%*.2f," /* source actual (average) frame rate */ + "%*.2f," /* output frame rate */ + "%*.2f," /* source clock drift */ + "%*d," /* source clock drift sample count */ + "%*.2f", /* rough calculated correction in ppm */ + 10, + 1000 * moving_average_sync_error / config.output_rate, 10, + moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), 10, moving_average_insertions_plus_deletions * 1000000 / (352 * conn->output_sample_ratio), - 12, play_number, - 7, conn->missing_packets, - 7, conn->late_packets, - 7, conn->too_late_packets, - 7, conn->resend_requests, - 7, minimum_dac_queue_size, - 5, minimum_buffer_occupancy, - 5, maximum_buffer_occupancy, - 11, conn->remote_frame_rate, - 11, conn->input_frame_rate, - 11, conn->frame_rate, - 10, (conn->local_to_remote_time_gradient-1.0) * 1000000, - 6, conn->local_to_remote_time_gradient_sample_count, - 10, (conn->frame_rate > 0.0) ? ((conn->frame_rate - conn->remote_frame_rate * conn->output_sample_ratio * conn->local_to_remote_time_gradient)*1000000)/conn->frame_rate : 0.0); + 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, + conn->too_late_packets, 7, conn->resend_requests, 7, + minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, + maximum_buffer_occupancy, 11, conn->remote_frame_rate, 11, + conn->input_frame_rate, 11, conn->frame_rate, 10, + (conn->local_to_remote_time_gradient - 1.0) * 1000000, 6, + conn->local_to_remote_time_gradient_sample_count, 10, + (conn->frame_rate > 0.0) + ? ((conn->frame_rate - + conn->remote_frame_rate * conn->output_sample_ratio * + conn->local_to_remote_time_gradient) * + 1000000) / + conn->frame_rate + : 0.0); } else { - inform("%*.2f," /* Sync error in milliseconds */ - "%*d," /* total packets */ + inform("%*.2f," /* Sync error in milliseconds */ + "%*d," /* total packets */ "%*" PRIu64 "," /* missing packets */ "%*" PRIu64 "," /* late packets */ "%*" PRIu64 "," /* too late packets */ "%*" PRIu64 "," /* resend requests */ "%*" PRId64 "," /* min DAC queue size */ - "%*" PRId32 "," /* min buffer occupancy */ - "%*" PRId32 "," /* max buffer occupancy */ - "%*.2f," /* source nominal frame rate */ - "%*.2f," /* source actual (average) frame rate */ - "%*.2f," /* source clock drift */ - "%*d", /* source clock drift sample count */ + "%*" PRId32 "," /* min buffer occupancy */ + "%*" PRId32 "," /* max buffer occupancy */ + "%*.2f," /* source nominal frame rate */ + "%*.2f," /* source actual (average) frame rate */ + "%*.2f," /* source clock drift */ + "%*d", /* source clock drift sample count */ 10, - 1000 * moving_average_sync_error / config.output_rate, - 12, play_number, - 7, conn->missing_packets, - 7, conn->late_packets, - 7, conn->too_late_packets, - 7, conn->resend_requests, - 7, minimum_dac_queue_size, - 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); + 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, + conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, + conn->resend_requests, 7, minimum_dac_queue_size, 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); } } else { - inform("%*.2f," /* Sync error in milliseconds */ - "%*d," /* total packets */ + inform("%*.2f," /* Sync error in milliseconds */ + "%*d," /* total packets */ "%*" PRIu64 "," /* missing packets */ "%*" PRIu64 "," /* late packets */ "%*" PRIu64 "," /* too late packets */ "%*" PRIu64 "," /* resend requests */ - "%*" PRId32 "," /* min buffer occupancy */ - "%*" PRId32 "," /* max buffer occupancy */ - "%*.2f," /* source nominal frame rate */ - "%*.2f," /* source actual (average) frame rate */ - "%*.2f," /* source clock drift */ - "%*d", /* source clock drift sample count */ + "%*" PRId32 "," /* min buffer occupancy */ + "%*" PRId32 "," /* max buffer occupancy */ + "%*.2f," /* source nominal frame rate */ + "%*.2f," /* source actual (average) frame rate */ + "%*.2f," /* source clock drift */ + "%*d", /* source clock drift sample count */ 10, - 1000 * moving_average_sync_error / config.output_rate, - 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); + 1000 * moving_average_sync_error / config.output_rate, 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); } } else { inform("No frames received in the last sampling interval."); diff --git a/rtp.c b/rtp.c index 41d1747d..c13c3d25 100644 --- a/rtp.c +++ b/rtp.c @@ -797,7 +797,8 @@ void *rtp_timing_receiver(void *arg) { } else { conn->local_to_remote_time_gradient = 1.0; } - // debug(1,"local to remote time gradient is %12.2f ppm, based on %d samples.",conn->local_to_remote_time_gradient*1000000,sample_count); + // debug(1,"local to remote time gradient is %12.2f ppm, based on %d + // samples.",conn->local_to_remote_time_gradient*1000000,sample_count); } else { debug(2, "Time ping turnaround time: %lld us -- it looks like a timing ping was lost.", (return_time * 1000000) >> 32); diff --git a/shairport.c b/shairport.c index 057d4ed0..bd4025dc 100644 --- a/shairport.c +++ b/shairport.c @@ -31,11 +31,11 @@ #include #include #include -#include #include #include #include #include +#include #include #include #include From 347e63ba906e80bfc01596bacfbd18fbe0b6f940 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 22 Aug 2018 14:32:54 +0100 Subject: [PATCH 084/569] Update RELEASENOTES.md --- RELEASENOTES.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 06bb2ce2..e42a8bed 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,6 +1,8 @@ Version 3.3d2 ==== -Modify the code that synchronises the Shairport Sync system's clock with the source clock to try to take account of the sources's true rate, which (oddly, e.g. iTunes on a Mac) might not be exactly 44,100 fps. Also try to interpolate for the measured drift between the standard three-second timing snapshots. These changes make a very slight difference from time to time, of the order of microseconds, and it's not clear yet how reliable the drift interpolation is. +Modify the code that synchronises the Shairport Sync system's clock with the source clock to try to take account of the sources's nominal rate, which (oddly, e.g. iTunes on a Mac) might not be exactly 44,100 fps. Also try to interpolate for the measured drift between the standard three-second timing snapshots. These changes make a very slight difference from time to time, of the order of microseconds, and it's not clear yet how reliable the drift interpolation is. + +Add some extra fields to the `statistics` output, including nominal source rate, actual input rate, actual output rate, source clock drift, calculated correction needed in ppm. All thesrt numbers agree to a high degree, but the generation of them is fragile and susceptible to errors when there are problems like underrun, and they are not actually running averages, which would be genuinely useful. But they show promise! **Bug Fixes** * Fix a bug in the `dbus` native interface which would silently switch `soxr` interpolation to `basic`. From 13510625ba2cdebd1db1d9dfe667ce0fcf06bf40 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 22 Aug 2018 14:47:57 +0100 Subject: [PATCH 085/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e42a8bed..55374d18 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,4 +1,4 @@ -Version 3.3d2 +Version 3.3d3 ==== Modify the code that synchronises the Shairport Sync system's clock with the source clock to try to take account of the sources's nominal rate, which (oddly, e.g. iTunes on a Mac) might not be exactly 44,100 fps. Also try to interpolate for the measured drift between the standard three-second timing snapshots. These changes make a very slight difference from time to time, of the order of microseconds, and it's not clear yet how reliable the drift interpolation is. From 44c61835b8a7d32e36aefdea3bc71987f8696f3c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 22 Aug 2018 15:10:38 +0100 Subject: [PATCH 086/569] Include a few fixes for FreeBSD warnings and quieten some debug messages --- audio_alsa.c | 3 ++- player.c | 17 +++++------------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 1abc2961..652b9a66 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1001,9 +1001,10 @@ static int play(void *buf, int samples) { } } - uint64_t tf = get_absolute_time_in_fp(); + measurement_time = get_absolute_time_in_fp(); frames_played_at_measurement_time = frames_sent_for_playing - fl; if (frame_index == start_measurement_from_this_frame) { + debug(1,"Start frame counting"); frames_played_at_measurement_start_time = frames_played_at_measurement_time; measurement_start_time = measurement_time; measurement_data_is_valid = 1; diff --git a/player.c b/player.c index 3f3fa505..ad8a8a41 100644 --- a/player.c +++ b/player.c @@ -2107,20 +2107,13 @@ void *player_thread_func(void *arg) { int64_t should_be_frame; local_time_to_frame(local_time_now, &should_be_frame, conn); -<<<<<<< HEAD - //if (abs(td_in_frames + rt - should_be_frame) > 10 * conn->output_sample_ratio) + // int64_t absolute_difference_in_frames = td_in_frames + rt - should_be_frame; + // if (absolute_difference_in_frames < 0) + // absolute_difference_in_frames = -absolute_difference_in_frames; + + // if (absolute_difference_in_frames > 10 * conn->output_sample_ratio) // debug(1, "Difference between old and new frame number is %" PRId64 " frames.", // td_in_frames + rt - should_be_frame); - -======= - int64_t absolute_difference_in_frames = td_in_frames + rt - should_be_frame; - if (absolute_difference_in_frames < 0) - absolute_difference_in_frames = -absolute_difference_in_frames; - - if (absolute_difference_in_frames > 10 * conn->output_sample_ratio) - debug(1, "Difference between old and new frame number is %" PRId64 " frames.", - td_in_frames + rt - should_be_frame); ->>>>>>> 347e63ba906e80bfc01596bacfbd18fbe0b6f940 // this is the actual delay, including the latency we actually want, which will // fluctuate a good bit about a potentially rising or falling trend. From de972c1d66532e345ac7f4b61073efb1fe87ce2e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 22 Aug 2018 15:10:53 +0100 Subject: [PATCH 087/569] Include a few fixes for FreeBSD warnings and quieten some debug messages --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 888f2285..0c917a43 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.3d2], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d3], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 5c6a7025a48a8f4494a818e7815de884ccdbc05d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 22 Aug 2018 15:13:46 +0100 Subject: [PATCH 088/569] Update RELEASENOTES.md --- RELEASENOTES.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 55374d18..af602d28 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,8 +1,9 @@ Version 3.3d3 ==== -Modify the code that synchronises the Shairport Sync system's clock with the source clock to try to take account of the sources's nominal rate, which (oddly, e.g. iTunes on a Mac) might not be exactly 44,100 fps. Also try to interpolate for the measured drift between the standard three-second timing snapshots. These changes make a very slight difference from time to time, of the order of microseconds, and it's not clear yet how reliable the drift interpolation is. +Modify the code that synchronises the Shairport Sync system's clock with the source clock to try to take account of the sources's nominal rate, which (oddly, e.g. iTunes on a Mac) might not be exactly 44,100 fps. There may be a 32-bit unsigned overflow error here somewhere! +Also try to interpolate for the measured drift between the standard three-second timing snapshots. These changes make a very slight difference from time to time, of the order of microseconds, and it's not clear yet how reliable the drift interpolation is. -Add some extra fields to the `statistics` output, including nominal source rate, actual input rate, actual output rate, source clock drift, calculated correction needed in ppm. All thesrt numbers agree to a high degree, but the generation of them is fragile and susceptible to errors when there are problems like underrun, and they are not actually running averages, which would be genuinely useful. But they show promise! +Add some extra fields to the `statistics` output, including nominal source rate, actual input rate, actual output rate, source clock drift, calculated correction needed in ppm. All these numbers agree to a high degree, but the generation of them is fragile and susceptible to errors when there are problems like underrun, and they are not actually running averages, which would be genuinely useful. But they show promise! **Bug Fixes** * Fix a bug in the `dbus` native interface which would silently switch `soxr` interpolation to `basic`. From d1614f7789a483c2dae17b69f7a4cc5ffc827bc9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 22 Aug 2018 15:14:37 +0100 Subject: [PATCH 089/569] Update RELEASENOTES.md --- RELEASENOTES.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index af602d28..63aeb9ce 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,9 +1,10 @@ Version 3.3d3 ==== -Modify the code that synchronises the Shairport Sync system's clock with the source clock to try to take account of the sources's nominal rate, which (oddly, e.g. iTunes on a Mac) might not be exactly 44,100 fps. There may be a 32-bit unsigned overflow error here somewhere! -Also try to interpolate for the measured drift between the standard three-second timing snapshots. These changes make a very slight difference from time to time, of the order of microseconds, and it's not clear yet how reliable the drift interpolation is. +* Modify the code that synchronises the Shairport Sync system's clock with the source clock to try to take account of the sources's nominal rate, which (oddly, e.g. iTunes on a Mac) might not be exactly 44,100 fps. There may be a 32-bit unsigned overflow error here somewhere! -Add some extra fields to the `statistics` output, including nominal source rate, actual input rate, actual output rate, source clock drift, calculated correction needed in ppm. All these numbers agree to a high degree, but the generation of them is fragile and susceptible to errors when there are problems like underrun, and they are not actually running averages, which would be genuinely useful. But they show promise! +* Try to interpolate for the measured drift between the standard three-second timing snapshots. These changes make a very slight difference from time to time, of the order of microseconds, and it's not clear yet how reliable the drift interpolation is. + +* Add some extra fields to the `statistics` output, including nominal source rate, actual input rate, actual output rate, source clock drift, calculated correction needed in ppm. All these numbers agree to a high degree, but the generation of them is fragile and susceptible to errors when there are problems like underrun, and they are not actually running averages, which would be genuinely useful. But they show promise! **Bug Fixes** * Fix a bug in the `dbus` native interface which would silently switch `soxr` interpolation to `basic`. From 5101ab2d13e2b89ea3c1276df5fb7413634eeccd Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 25 Aug 2018 13:08:37 +0100 Subject: [PATCH 090/569] Sometimes libsoxr is built to rely on libavutil. If libavutil is detected, link to it when building libsoxr. Add pgk_config support to soxr selection. --- configure.ac | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 0c917a43..2b6fddb3 100644 --- a/configure.ac +++ b/configure.ac @@ -176,7 +176,20 @@ AC_ARG_WITH(ssl, [ choose --with-ssl=openssl, --with-ssl=mbedtls or --with-ssl=p # Look for soxr flag AC_ARG_WITH(soxr, [ --with-soxr = choose libsoxr for high-quality interpolation], [ AC_MSG_RESULT(>>Including support for soxr-based interpolation) - AC_CHECK_LIB([soxr],[soxr_create], , AC_MSG_ERROR(soxr support requested but libsoxr not found!))], ) + if test "x${with_pkg_config}" = xyes ; then + PKG_CHECK_MODULES( + [SOXR], [soxr], + [LIBS="${SOXR_LIBS} ${LIBS}"]) + else + AC_CHECK_LIB([avutil],[av_get_cpu_flags]) + if test "x${ac_cv_lib_avutil_av_get_cpu_flags}" = xyes ; then + # soxr may link against libavutil, depending on the architecture, but for the sake of simplicity link with it if it is found + AC_CHECK_LIB([soxr],[soxr_create], , AC_MSG_ERROR(soxr support requested but libsoxr not found!), [-lavutil]) + else + AC_CHECK_LIB([soxr],[soxr_create], , AC_MSG_ERROR(soxr support requested but libsoxr not found!)) + fi + fi +], ) # Look for metadata flag -- set flag for conditional compilation AC_ARG_WITH(metadata, [ --with-metadata = include support for a metadata feed], [ From 39c46dbc77717f1ddd8c3fcef7ecf4dcb30e70be Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 25 Aug 2018 13:09:43 +0100 Subject: [PATCH 091/569] Sometimes libsoxr is built to rely on libavutil. If libavutil is detected, link to it when building libsoxr. Add pgk_config support to soxr selection. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 2b6fddb3..508faf9b 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.3d3], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3avs0], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 19e572e94aa45f6cd78de3f9b295a559f3648c69 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 28 Aug 2018 09:38:44 +0100 Subject: [PATCH 092/569] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 508faf9b..50d8e006 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.3avs0], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d4], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 805b1d701c5fe2fb8d47d8b715255c5ddffa23a7 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 28 Aug 2018 09:47:07 +0100 Subject: [PATCH 093/569] Update RELEASENOTES.md --- RELEASENOTES.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 63aeb9ce..bbc81907 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,10 @@ +Version 3.3d4 +==== +* Sometimes `libsoxr` is built to rely on `libavutil`. With the present changes to `configure.ac`, if `libavutil` is present, link to it when linking to `libsoxr`, but if it's not present, don't link to it when linking to `libsoxr`. +* Add `pgk_config` support to `libsoxr` selection. + +Thanks to [Jörg Krause](https://github.com/joerg-krause) for identifying these issues and proposing fixes. + Version 3.3d3 ==== * Modify the code that synchronises the Shairport Sync system's clock with the source clock to try to take account of the sources's nominal rate, which (oddly, e.g. iTunes on a Mac) might not be exactly 44,100 fps. There may be a 32-bit unsigned overflow error here somewhere! From 8d152bf2d505d320951db671cedb98d8cf437c7c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 28 Aug 2018 17:09:31 +0100 Subject: [PATCH 094/569] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 802fc671..bfd61851 100644 --- a/README.md +++ b/README.md @@ -67,9 +67,11 @@ See [here](https://github.com/mikebrady/shairport-sync/blob/development/INSTALL. The following procedures will build and install the `shairport-sync` application into your system. -**Remove Old Versions Of Shairport Sync** +**Remove Old Versions of Shairport Sync and its Startup Scripts** -You should check to see if `shairport-sync` is already installed – you can use the command `$ which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere. You should also remove the file `/etc/systemd/system/shairport-sync.service` if it exists. +You should check to see if `shairport-sync` is already installed – you can use the command `$ which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere. + +You should also remove the initialisation script files `/etc/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed in necessary. **FreeBSD** From 8cabb16f259f77951478987f905a692e68de4452 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 31 Aug 2018 12:39:18 +0100 Subject: [PATCH 095/569] Initial very rough attempt at a jackaudio backend. --- Makefile.am | 4 + audio.c | 6 + audio.h | 3 + audio_alsa.c | 1 + audio_ao.c | 1 + audio_dummy.c | 1 + audio_jack.c | 303 ++++++++++++++++++++++++++++++++++++++++++++++++ audio_pa.c | 1 + audio_pipe.c | 1 + audio_sndio.c | 1 + audio_soundio.c | 1 + audio_stdout.c | 1 + configure.ac | 14 +++ player.c | 42 +++++-- 14 files changed, 370 insertions(+), 10 deletions(-) create mode 100644 audio_jack.c diff --git a/Makefile.am b/Makefile.am index 635b4aea..e8e4a341 100644 --- a/Makefile.am +++ b/Makefile.am @@ -38,6 +38,10 @@ if USE_ALSA shairport_sync_SOURCES += audio_alsa.c endif +if USE_JACK +shairport_sync_SOURCES += audio_jack.c +endif + if USE_SNDIO shairport_sync_SOURCES += audio_sndio.c endif diff --git a/audio.c b/audio.c index ce317f13..382d46d2 100644 --- a/audio.c +++ b/audio.c @@ -31,6 +31,9 @@ #include #include +#ifdef CONFIG_JACK +extern audio_output audio_jack; +#endif #ifdef CONFIG_SNDIO extern audio_output audio_sndio; #endif @@ -66,6 +69,9 @@ static audio_output *outputs[] = { #ifdef CONFIG_PA &audio_pa, #endif +#ifdef CONFIG_JACK + &audio_jack, +#endif #ifdef CONFIG_AO &audio_ao, #endif diff --git a/audio.h b/audio.h index fc06a04c..171e366b 100644 --- a/audio.h +++ b/audio.h @@ -24,6 +24,9 @@ typedef struct { // block of samples int (*play)(void *buf, int samples); void (*stop)(void); + + // may be null if no implemented + int (*is_running)(void); // if implemented, will return 0 if everything is okay, non-zero otherwise // may be null if not implemented void (*flush)(void); diff --git a/audio_alsa.c b/audio_alsa.c index 652b9a66..d0b62532 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -63,6 +63,7 @@ audio_output audio_alsa = { .deinit = &deinit, .start = &start, .stop = &stop, + .is_running = NULL, .flush = &flush, .delay = &delay, .play = &play, diff --git a/audio_ao.c b/audio_ao.c index 25ed83e9..42147bab 100644 --- a/audio_ao.c +++ b/audio_ao.c @@ -131,6 +131,7 @@ audio_output audio_ao = {.name = "ao", .deinit = &deinit, .start = &start, .stop = &stop, + .is_running = NULL, .flush = NULL, .delay = NULL, .play = &play, diff --git a/audio_dummy.c b/audio_dummy.c index 3f30cb4b..d8dea87f 100644 --- a/audio_dummy.c +++ b/audio_dummy.c @@ -62,6 +62,7 @@ audio_output audio_dummy = {.name = "dummy", .deinit = &deinit, .start = &start, .stop = &stop, + .is_running = NULL, .flush = NULL, .delay = NULL, .play = &play, diff --git a/audio_jack.c b/audio_jack.c new file mode 100644 index 00000000..98085a18 --- /dev/null +++ b/audio_jack.c @@ -0,0 +1,303 @@ +/* + * jack output driver. This file is part of Shairport Sync. + * Copyright (c) 2018 Mike Brady + * + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "audio.h" +#include "common.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +enum ift_type { + IFT_frame_left_sample = 0, + IFT_frame_right_sample, +} ift_type; + +// Four seconds buffer -- should be plenty +#define buffer_size 44100 * 4 * 2 * 2 + +static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER; + +char *audio_lmb, *audio_umb, *audio_toq, *audio_eoq; +size_t audio_occupancy; // this is in frames, not bytes. A frame is a left and + // right sample, each 16 bits, hence 4 bytes + +// static void help(void); +int init(int, char **); +// static void onmove_cb(void *, int); +// static void deinit(void); +void jack_start(int, int); +int play(void *, int); +void jack_stop(void); +int jack_is_running(void); +// static void onmove_cb(void *, int); +int jack_delay(long *); +void jack_flush(void); + +audio_output audio_jack = {.name = "jack", + .help = NULL, + .init = &init, + .deinit = NULL, + .start = &jack_start, + .stop = &jack_stop, + .is_running = &jack_is_running, + .flush = &jack_flush, + .delay = &jack_delay, + .play = &play, + .volume = NULL, + .parameters = NULL, + .mute = NULL}; + +typedef jack_default_audio_sample_t sample_t; + +const double PI = 3.14; + +jack_port_t *left_port; +jack_port_t *right_port; +long offset = 0; +int transport_aware = 0; +jack_transport_state_t transport_state; + +int client_is_open; +jack_client_t *client; +jack_nframes_t sample_rate; + +jack_latency_range_t latest_latency_range; +int64_t time_of_latest_latency_range; + +int play(void *buf, int samples) { + // debug(1,"jack_play of %d samples.",samples); + // copy the samples into the queue + size_t bytes_to_transfer = samples * 2 * 2; + size_t space_to_end_of_buffer = audio_umb - audio_eoq; + if (space_to_end_of_buffer >= bytes_to_transfer) { + memcpy(audio_eoq, buf, bytes_to_transfer); + pthread_mutex_lock(&buffer_mutex); + audio_occupancy += samples; + audio_eoq += bytes_to_transfer; + pthread_mutex_unlock(&buffer_mutex); + } else { + memcpy(audio_eoq, buf, space_to_end_of_buffer); + buf += space_to_end_of_buffer; + memcpy(audio_lmb, buf, bytes_to_transfer - space_to_end_of_buffer); + pthread_mutex_lock(&buffer_mutex); + audio_occupancy += samples; + audio_eoq = audio_lmb + bytes_to_transfer - space_to_end_of_buffer; + pthread_mutex_unlock(&buffer_mutex); + } + + if ((audio_occupancy >= 11025 * 2 * 2)) { + } + + return 0; +} + +void deinterleave_and_convert_stream(const char *interleaved_frames, + const sample_t *jack_frame_buffer, + jack_nframes_t number_of_frames, enum ift_type side) { + jack_nframes_t i; + short *ifp = (short *)interleaved_frames; + sample_t *fp = (sample_t *)jack_frame_buffer; + if (side == IFT_frame_right_sample) + ifp++; + for (i = 0; i < number_of_frames; i++) { + short sample = *ifp; + sample_t converted_value; + if (sample >= 0) + converted_value = (1.0 * sample) / SHRT_MAX; + else + converted_value = -(1.0 * sample) / SHRT_MIN; + *fp = converted_value; + ifp++; + ifp++; + fp++; + } +} + +int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *arg) { + + sample_t *left_buffer = (sample_t *)jack_port_get_buffer(left_port, nframes); + sample_t *right_buffer = (sample_t *)jack_port_get_buffer(right_port, nframes); + + size_t frames_we_can_transfer = nframes; + // lock + pthread_mutex_lock(&buffer_mutex); + if (audio_occupancy < frames_we_can_transfer) { + frames_we_can_transfer = audio_occupancy; + } + + // frames we can transfer will never be greater than the frames available + + if (frames_we_can_transfer * 2 * 2 <= (size_t)(audio_umb - audio_toq)) { + // the bytes are all in a row in the audio buffer + deinterleave_and_convert_stream(audio_toq, &left_buffer[0], frames_we_can_transfer, + IFT_frame_left_sample); + deinterleave_and_convert_stream(audio_toq, &right_buffer[0], frames_we_can_transfer, + IFT_frame_right_sample); + audio_toq += frames_we_can_transfer * 2 * 2; + } else { + // the bytes are in two places in the audio buffer + size_t first_portion_to_write = (audio_umb - audio_toq) / (2 * 2); + if (first_portion_to_write != 0) { + deinterleave_and_convert_stream(audio_toq, &left_buffer[0], first_portion_to_write, + IFT_frame_left_sample); + deinterleave_and_convert_stream(audio_toq, &right_buffer[0], first_portion_to_write, + IFT_frame_right_sample); + } + deinterleave_and_convert_stream(audio_lmb, &left_buffer[first_portion_to_write], + frames_we_can_transfer - first_portion_to_write, + IFT_frame_left_sample); + deinterleave_and_convert_stream(audio_lmb, &right_buffer[first_portion_to_write], + frames_we_can_transfer - first_portion_to_write, + IFT_frame_right_sample); + audio_toq = audio_lmb + (frames_we_can_transfer - first_portion_to_write) * 2 * 2; + } + // debug(1,"transferring %u frames",frames_we_can_transfer); + audio_occupancy -= frames_we_can_transfer; + pthread_mutex_unlock(&buffer_mutex); + // unlock + + // now, if there are any more frames to put into the buffer, fill them with + // silence + jack_nframes_t i; + + for (i = frames_we_can_transfer; i < nframes; i++) { + left_buffer[i] = 0.0; + right_buffer[i] = 0.0; + } + + jack_port_get_latency_range(left_port, JackPlaybackLatency, &latest_latency_range); + time_of_latest_latency_range = get_absolute_time_in_fp(); + + return 0; +} + +void default_jack_error_callback(const char *desc) { + debug(2,"jackd error: \"%s\"",desc); +} + +void default_jack_info_callback(const char *desc) { + inform("jackd information: \"%s\"",desc); +} + +int jack_is_running() { + int reply = -1; + // if the client is open and initialised, see if the status is "rolling" + if (client_is_open) { + jack_position_t pos; + jack_transport_state_t transport_state = jack_transport_query (client, &pos); + if (transport_state == JackTransportRolling) + reply = 0; + else + reply = -2; + } + return reply; +} + +int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) { + config.audio_backend_latency_offset = 0; + config.audio_backend_buffer_desired_length = 0.15; + + // get settings from settings file first, allow them to be overridden by + // command line options + + // do the "general" audio options. Note, these options are in the "general" stanza! + parse_general_audio_options(); + + // other options would be picked up here... + + jack_set_error_function(default_jack_error_callback); + jack_set_info_function(default_jack_info_callback); + client_is_open = 0; + + // allocate space for the audio buffer + audio_lmb = malloc(buffer_size); + if (audio_lmb == NULL) + die("Can't allocate %d bytes for jackaudio buffer.", buffer_size); + audio_toq = audio_eoq = audio_lmb; + audio_umb = audio_lmb + buffer_size; + audio_occupancy = 0; // frames + return 0; +} + +void jack_start(__attribute__((unused)) int i_sample_rate, __attribute__((unused)) int i_sample_format) { + debug(1,"jack start"); + // int reply = -1; + + // see if the client is running. If not, try to open and initialise it + if (client_is_open == 0) { + jack_status_t status; + client = jack_client_open("Shairport Sync", JackNoStartServer, &status); + if (client) { + jack_set_process_callback(client, jack_stream_write_cb, 0); + left_port = jack_port_register(client, "Left", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + right_port = jack_port_register(client, "Right", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + sample_rate = jack_get_sample_rate(client); + debug(1, "jackaudio sample rate = %" PRId32 ".", sample_rate); + if (jack_activate(client)) { + debug(1, "jackaudio cannot activate client"); + } else { + client_is_open = 1; + debug(1, "jackaudio client opened."); + } + } + } + + if (client_is_open == 0) + debug(1,"cannot open a jack client for a play session"); +} + +int jack_delay(long *the_delay) { + int64_t time_now = get_absolute_time_in_fp(); + int64_t delta = time_now - time_of_latest_latency_range; + + int64_t frames_processed_since_latest_latency_check = (delta * 44100) >> 32; + + // debug(1,"delta: %" PRId64 " frames.",frames_processed_since_latest_latency_check); + + *the_delay = latest_latency_range.min + audio_occupancy - frames_processed_since_latest_latency_check; + + // debug(1,"reporting a delay of %d frames",*the_delay); + + return 0; +} + +void jack_flush() { + debug(1,"jack flush"); + // lock + pthread_mutex_lock(&buffer_mutex); + audio_toq = audio_eoq = audio_lmb; + audio_umb = audio_lmb + buffer_size; + audio_occupancy = 0; // frames + pthread_mutex_unlock(&buffer_mutex); + // unlock +} + +void jack_stop(void) { + debug(1,"jack stop"); +} \ No newline at end of file diff --git a/audio_pa.c b/audio_pa.c index aa4251bd..d8f8a9d3 100644 --- a/audio_pa.c +++ b/audio_pa.c @@ -288,6 +288,7 @@ audio_output audio_pa = {.name = "pa", .deinit = &deinit, .start = &start, .stop = &stop, + .is_running = NULL, .flush = &flush, .delay = &pa_delay, .play = &play, diff --git a/audio_pipe.c b/audio_pipe.c index 057a975f..89a09a0c 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -135,6 +135,7 @@ audio_output audio_pipe = {.name = "pipe", .deinit = &deinit, .start = &start, .stop = &stop, + .is_running = NULL, .flush = NULL, .delay = NULL, .play = &play, diff --git a/audio_sndio.c b/audio_sndio.c index 203a7dbc..d8037edb 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -45,6 +45,7 @@ audio_output audio_sndio = {.name = "sndio", .deinit = &deinit, .start = &start, .stop = &stop, + .is_running = NULL, .flush = &flush, .delay = &delay, .play = &play, diff --git a/audio_soundio.c b/audio_soundio.c index 2fed5ee5..7d3b48ad 100644 --- a/audio_soundio.c +++ b/audio_soundio.c @@ -217,6 +217,7 @@ audio_output audio_soundio = {.name = "soundio", .deinit = &deinit, .start = &start, .stop = &stop, + .is_running = NULL, .flush = &flush, .delay = NULL, .play = &play, diff --git a/audio_stdout.c b/audio_stdout.c index 72f10c85..0438b1af 100644 --- a/audio_stdout.c +++ b/audio_stdout.c @@ -82,6 +82,7 @@ audio_output audio_stdout = {.name = "stdout", .deinit = &deinit, .start = &start, .stop = &stop, + .is_running = NULL, .flush = NULL, .delay = NULL, .play = &play, diff --git a/configure.ac b/configure.ac index 0c917a43..6dbd9dbf 100644 --- a/configure.ac +++ b/configure.ac @@ -220,6 +220,20 @@ AC_ARG_WITH(alsa, [ --with-alsa = choose ALSA API support (GNU/Linux only)], fi ]) AM_CONDITIONAL([USE_ALSA], [test "x$HAS_ALSA" = "x1"]) +# Look for jack flag +AC_ARG_WITH(jack, [ --with-jack = include a Jack Audio Connection Kit (jack) backend], [ + AC_MSG_RESULT(>>Including a Jack Audio Connection Kit (jack) back end) + HAS_JACK=1 + AC_DEFINE([CONFIG_JACK], 1, [Needed by the compiler.]) + if test "x${with_pkg_config}" = xyes ; then + PKG_CHECK_MODULES( + [JACK], [jack], + [LIBS="${JACK_LIBS} ${LIBS}"],[AC_MSG_ERROR(Jack Audio Connection Kit support requires the jack library -- libjack-dev suggested!)]) + else + AC_CHECK_LIB([jack], [jack_client_open], , AC_MSG_ERROR(Jack Audio Connection Kit support requires the jack library -- libjack-dev suggested!)) + fi ]) +AM_CONDITIONAL([USE_JACK], [test "x$HAS_JACK" = "x1"]) + # Look for SNDIO flag AC_ARG_WITH(sndio, [ --with-sndio = choose SNDIO API support], [ AC_MSG_RESULT(>>Including a SNDIO back end) diff --git a/player.c b/player.c index ad8a8a41..c23ca94e 100644 --- a/player.c +++ b/player.c @@ -841,6 +841,14 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { debug_mutex_unlock(&conn->flush_mutex, 3); } } + + if (config.output->is_running) + if (config.output->is_running() !=0 ) { // if the back end isn't running for any reason + debug(1,"not running"); + debug_mutex_lock(&conn->flush_mutex, 1000, 1); + conn->flush_requested = 1; + debug_mutex_unlock(&conn->flush_mutex, 3); + } debug_mutex_lock(&conn->flush_mutex, 1000, 1); if (conn->flush_requested == 1) { @@ -905,11 +913,13 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // player int have_sent_prefiller_silence = 1; // set true when we have sent some silent frames to // the DAC + /* int64_t reference_timestamp; uint64_t reference_timestamp_time, remote_reference_timestamp_time; get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time, conn); reference_timestamp *= conn->output_sample_ratio; + */ if (conn->first_packet_timestamp == 0) { // if this is the very first packet // debug(1,"First frame seen, time %u, with %d // frames...",curframe->timestamp,seq_diff(ab_read, ab_write)); @@ -959,6 +969,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // what we are asking for here is "what is the local time at which time the calculated // frame should be played" +/* int64_t delta = (conn->first_packet_timestamp - reference_timestamp) + conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate); @@ -973,13 +984,14 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { (abs_delta << 32) / config.output_rate; // int64_t which is positive conn->first_packet_time_to_play = reference_timestamp_time - delta_fp_sec; } +*/ uint64_t should_be_time; frame_to_local_time( conn->first_packet_timestamp + conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate), &should_be_time, conn); - +/* if (should_be_time >= conn->first_packet_time_to_play) { if ((((should_be_time - conn->first_packet_time_to_play) * 1000000) >> 32) > 10) debug(2, "New time for first packet timestamp %" PRId64 @@ -993,8 +1005,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { curframe->timestamp, ((conn->first_packet_time_to_play - should_be_time) * 1000000) >> 32); } - - // cut over to new calculation scheme +*/ conn->first_packet_time_to_play = should_be_time; if (local_time_now >= conn->first_packet_time_to_play) { @@ -1009,6 +1020,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (conn->first_packet_time_to_play != 0) { // recalculate conn->first_packet_time_to_play -- the latency might change +/* int64_t delta = (conn->first_packet_timestamp - reference_timestamp) + conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate); @@ -1023,13 +1035,13 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { (abs_delta << 32) / config.output_rate; // int64_t which is positive conn->first_packet_time_to_play = reference_timestamp_time - delta_fp_sec; } - +*/ uint64_t should_be_time; frame_to_local_time( conn->first_packet_timestamp + conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate), &should_be_time, conn); - +/* if (should_be_time >= conn->first_packet_time_to_play) { if ((((should_be_time - conn->first_packet_time_to_play) * 1000000) >> 32) > 50) debug(2, "New time for recalculated first packet timestamp %" PRId64 @@ -1043,7 +1055,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { curframe->timestamp, ((conn->first_packet_time_to_play - should_be_time) * 1000000) >> 32); } - +*/ + conn->first_packet_time_to_play = should_be_time; + // now, the size of the initial silence must be affected by the lead-in time. // it must be somewhat less than the lead-in time so that dynamic adjustments can be // made @@ -1226,12 +1240,18 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { 1; // if the current frame exists and is ready, then wait unless it's time to let it go... // here, get the time to play the current frame. + + /* int64_t reference_timestamp; uint64_t reference_timestamp_time, remote_reference_timestamp_time; get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time, conn); // all types okay reference_timestamp *= conn->output_sample_ratio; + */ + if (have_timestamp_timing_information(conn)) { // if we have a reference time + uint64_t time_to_play; + /* int64_t packet_timestamp = curframe->timestamp; // types okay int64_t delta = packet_timestamp - reference_timestamp; int64_t offset = @@ -1241,7 +1261,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { config.output_rate; // all arguments are int32_t, so expression promotion okay int64_t net_offset = delta + offset; // okay - uint64_t time_to_play = reference_timestamp_time; // type okay + time_to_play = reference_timestamp_time; // type okay if (net_offset >= 0) { uint64_t net_offset_fp_sec = (net_offset << 32) / config.output_rate; // int64_t which is positive @@ -1256,8 +1276,10 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { } uint64_t new_time_to_play = 0; - frame_to_local_time(packet_timestamp + offset, &new_time_to_play, conn); + */ + frame_to_local_time(curframe->timestamp + conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate) - config.audio_backend_buffer_desired_length * config.output_rate, &time_to_play, conn); +/* if (new_time_to_play >= time_to_play) { if ((((new_time_to_play - time_to_play) * 1000000) >> 32) > 100) debug(2, "New time for frame %" PRId64 " is later than calculated time by %" PRId64 @@ -1269,8 +1291,10 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { " microseconds.", curframe->timestamp, ((time_to_play - new_time_to_play) * 1000000) >> 32); } + // cut over to the new calculation system time_to_play = new_time_to_play; +*/ if (local_time_now >= time_to_play) { do_wait = 0; @@ -1296,8 +1320,6 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { time_to_wait_for_wakeup_fp *= 4 * 352; // four full 352-frame packets time_to_wait_for_wakeup_fp /= 3; // four thirds of a packet time - time_to_wait_for_wakeup_fp = (uint64_t)0x200000000 / (uint64_t)1000; // two millisecond - #ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD uint64_t time_of_wakeup_fp = local_time_now + time_to_wait_for_wakeup_fp; uint64_t sec = time_of_wakeup_fp >> 32; From e7dfecb1f216ceee9df49174f6f2923454a168b7 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 31 Aug 2018 12:40:35 +0100 Subject: [PATCH 096/569] Initial very rough attempt at a jackaudio backend. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 6dbd9dbf..40df449f 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.3d3], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d4], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 6bad1b3b8c34d2e5ccf20fef6b947ea1f3100d7f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 31 Aug 2018 12:46:48 +0100 Subject: [PATCH 097/569] remove some now-redundant code --- player.c | 118 ++----------------------------------------------------- 1 file changed, 4 insertions(+), 114 deletions(-) diff --git a/player.c b/player.c index c23ca94e..26dae07c 100644 --- a/player.c +++ b/player.c @@ -969,43 +969,12 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // what we are asking for here is "what is the local time at which time the calculated // frame should be played" -/* - int64_t delta = (conn->first_packet_timestamp - reference_timestamp) + - conn->latency * conn->output_sample_ratio + - (int64_t)(config.audio_backend_latency_offset * config.output_rate); - - if (delta >= 0) { - int64_t delta_fp_sec = - (delta << 32) / config.output_rate; // int64_t which is positive - conn->first_packet_time_to_play = reference_timestamp_time + delta_fp_sec; - } else { - int64_t abs_delta = -delta; - int64_t delta_fp_sec = - (abs_delta << 32) / config.output_rate; // int64_t which is positive - conn->first_packet_time_to_play = reference_timestamp_time - delta_fp_sec; - } -*/ - uint64_t should_be_time; frame_to_local_time( conn->first_packet_timestamp + conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate), &should_be_time, conn); -/* - if (should_be_time >= conn->first_packet_time_to_play) { - if ((((should_be_time - conn->first_packet_time_to_play) * 1000000) >> 32) > 10) - debug(2, "New time for first packet timestamp %" PRId64 - " is later than calculated time by %" PRId64 " microseconds.", - curframe->timestamp, - ((should_be_time - conn->first_packet_time_to_play) * 1000000) >> 32); - } else { - if ((((conn->first_packet_time_to_play - should_be_time) * 1000000) >> 32) > 10) - debug(2, "New time for first packet timestamp %" PRId64 - " is earlier than calculated time by %" PRId64 " microseconds.", - curframe->timestamp, - ((conn->first_packet_time_to_play - should_be_time) * 1000000) >> 32); - } -*/ + conn->first_packet_time_to_play = should_be_time; if (local_time_now >= conn->first_packet_time_to_play) { @@ -1020,42 +989,13 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (conn->first_packet_time_to_play != 0) { // recalculate conn->first_packet_time_to_play -- the latency might change -/* - int64_t delta = (conn->first_packet_timestamp - reference_timestamp) + - conn->latency * conn->output_sample_ratio + - (int64_t)(config.audio_backend_latency_offset * config.output_rate); - if (delta >= 0) { - int64_t delta_fp_sec = - (delta << 32) / config.output_rate; // int64_t which is positive - conn->first_packet_time_to_play = reference_timestamp_time + delta_fp_sec; - } else { - int64_t abs_delta = -delta; - int64_t delta_fp_sec = - (abs_delta << 32) / config.output_rate; // int64_t which is positive - conn->first_packet_time_to_play = reference_timestamp_time - delta_fp_sec; - } -*/ uint64_t should_be_time; frame_to_local_time( conn->first_packet_timestamp + conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate), &should_be_time, conn); -/* - if (should_be_time >= conn->first_packet_time_to_play) { - if ((((should_be_time - conn->first_packet_time_to_play) * 1000000) >> 32) > 50) - debug(2, "New time for recalculated first packet timestamp %" PRId64 - " is later than calculated time by %" PRId64 " microseconds.", - curframe->timestamp, - ((should_be_time - conn->first_packet_time_to_play) * 1000000) >> 32); - } else { - if ((((conn->first_packet_time_to_play - should_be_time) * 1000000) >> 32) > 50) - debug(2, "New time for recalculated first packet timestamp %" PRId64 - " is earlier than calculated time by %" PRId64 " microseconds.", - curframe->timestamp, - ((conn->first_packet_time_to_play - should_be_time) * 1000000) >> 32); - } -*/ + conn->first_packet_time_to_play = should_be_time; // now, the size of the initial silence must be affected by the lead-in time. @@ -1241,61 +1181,11 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // here, get the time to play the current frame. - /* - int64_t reference_timestamp; - uint64_t reference_timestamp_time, remote_reference_timestamp_time; - get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, - &remote_reference_timestamp_time, conn); // all types okay - reference_timestamp *= conn->output_sample_ratio; - */ - if (have_timestamp_timing_information(conn)) { // if we have a reference time + uint64_t time_to_play; - /* - int64_t packet_timestamp = curframe->timestamp; // types okay - int64_t delta = packet_timestamp - reference_timestamp; - int64_t offset = - conn->latency * conn->output_sample_ratio + - (int64_t)(config.audio_backend_latency_offset * config.output_rate) - - config.audio_backend_buffer_desired_length * - config.output_rate; // all arguments are int32_t, so expression promotion okay - int64_t net_offset = delta + offset; // okay - - time_to_play = reference_timestamp_time; // type okay - if (net_offset >= 0) { - uint64_t net_offset_fp_sec = - (net_offset << 32) / config.output_rate; // int64_t which is positive - time_to_play += net_offset_fp_sec; // using the latency requested... - // debug(2,"Net Offset: %lld, adjusted: %lld.",net_offset,net_offset_fp_sec); - } else { - int64_t abs_net_offset = -net_offset; - uint64_t net_offset_fp_sec = - (abs_net_offset << 32) / config.output_rate; // int64_t which is positive - time_to_play -= net_offset_fp_sec; - // debug(2,"Net Offset: %lld, adjusted: -%lld.",net_offset,net_offset_fp_sec); - } - - uint64_t new_time_to_play = 0; - */ frame_to_local_time(curframe->timestamp + conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate) - config.audio_backend_buffer_desired_length * config.output_rate, &time_to_play, conn); - -/* - if (new_time_to_play >= time_to_play) { - if ((((new_time_to_play - time_to_play) * 1000000) >> 32) > 100) - debug(2, "New time for frame %" PRId64 " is later than calculated time by %" PRId64 - " microseconds.", - curframe->timestamp, ((new_time_to_play - time_to_play) * 1000000) >> 32); - } else { - if ((((time_to_play - new_time_to_play) * 1000000) >> 32) > 100) - debug(2, "New time for frame %" PRId64 " is earlier than calculated time by %" PRId64 - " microseconds.", - curframe->timestamp, ((time_to_play - new_time_to_play) * 1000000) >> 32); - } - - // cut over to the new calculation system - time_to_play = new_time_to_play; -*/ - + if (local_time_now >= time_to_play) { do_wait = 0; } From 2528cedc6cba8da57885adf4e20dc2a9535f488a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 31 Aug 2018 12:50:05 +0100 Subject: [PATCH 098/569] Some confusion here, so 3.3d5 marks the introduction of a jack backend. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 9ebc70d3..4cad62fb 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.3d4], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d5], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From aed439fe95842fefb2d5579d85635c990c02aeb7 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 31 Aug 2018 12:56:41 +0100 Subject: [PATCH 099/569] Update RELEASENOTES.md --- RELEASENOTES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index bbc81907..425b12d6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,7 @@ +Version 3.3d5 +==== +* Introducing a very rough implementation of a [Jack Audio](http://jackaudio.org) backend. The JACK Audio Connection Kit "provides a basic infrastructure for audio applications to communicate with each other and with audio hardware. Through JACK, users are enabled to build powerful systems for signal processing and music production." + Version 3.3d4 ==== * Sometimes `libsoxr` is built to rely on `libavutil`. With the present changes to `configure.ac`, if `libavutil` is present, link to it when linking to `libsoxr`, but if it's not present, don't link to it when linking to `libsoxr`. From 959aa08b8f11c44e61a665c24afbaa9e2ee49de5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 31 Aug 2018 21:41:25 +0100 Subject: [PATCH 100/569] Clean up the jack backend and add a few options. --- audio_jack.c | 204 +++++++++++++++++++++++------------- common.h | 4 + player.c | 2 +- scripts/shairport-sync.conf | 8 ++ 4 files changed, 144 insertions(+), 74 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 98085a18..e8cd51a8 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -50,7 +50,7 @@ size_t audio_occupancy; // this is in frames, not bytes. A frame is a left and // static void help(void); int init(int, char **); // static void onmove_cb(void *, int); -// static void deinit(void); +void jack_deinit(void); void jack_start(int, int); int play(void *, int); void jack_stop(void); @@ -62,7 +62,7 @@ void jack_flush(void); audio_output audio_jack = {.name = "jack", .help = NULL, .init = &init, - .deinit = NULL, + .deinit = &jack_deinit, .start = &jack_start, .stop = &jack_stop, .is_running = &jack_is_running, @@ -73,22 +73,17 @@ audio_output audio_jack = {.name = "jack", .parameters = NULL, .mute = NULL}; -typedef jack_default_audio_sample_t sample_t; - -const double PI = 3.14; - jack_port_t *left_port; jack_port_t *right_port; + long offset = 0; -int transport_aware = 0; -jack_transport_state_t transport_state; int client_is_open; jack_client_t *client; jack_nframes_t sample_rate; -jack_latency_range_t latest_latency_range; -int64_t time_of_latest_latency_range; +jack_latency_range_t latest_left_latency_range,latest_right_latency_range; +int64_t time_of_latest_transfer; int play(void *buf, int samples) { // debug(1,"jack_play of %d samples.",samples); @@ -110,24 +105,20 @@ int play(void *buf, int samples) { audio_eoq = audio_lmb + bytes_to_transfer - space_to_end_of_buffer; pthread_mutex_unlock(&buffer_mutex); } - - if ((audio_occupancy >= 11025 * 2 * 2)) { - } - return 0; } void deinterleave_and_convert_stream(const char *interleaved_frames, - const sample_t *jack_frame_buffer, + const jack_default_audio_sample_t *jack_frame_buffer, jack_nframes_t number_of_frames, enum ift_type side) { jack_nframes_t i; short *ifp = (short *)interleaved_frames; - sample_t *fp = (sample_t *)jack_frame_buffer; + jack_default_audio_sample_t *fp = (jack_default_audio_sample_t *)jack_frame_buffer; if (side == IFT_frame_right_sample) ifp++; for (i = 0; i < number_of_frames; i++) { short sample = *ifp; - sample_t converted_value; + jack_default_audio_sample_t converted_value; if (sample >= 0) converted_value = (1.0 * sample) / SHRT_MAX; else @@ -141,8 +132,10 @@ void deinterleave_and_convert_stream(const char *interleaved_frames, int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *arg) { - sample_t *left_buffer = (sample_t *)jack_port_get_buffer(left_port, nframes); - sample_t *right_buffer = (sample_t *)jack_port_get_buffer(right_port, nframes); + jack_default_audio_sample_t *left_buffer = + (jack_default_audio_sample_t *)jack_port_get_buffer(left_port, nframes); + jack_default_audio_sample_t *right_buffer = + (jack_default_audio_sample_t *)jack_port_get_buffer(right_port, nframes); size_t frames_we_can_transfer = nframes; // lock @@ -179,10 +172,13 @@ int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *a } // debug(1,"transferring %u frames",frames_we_can_transfer); audio_occupancy -= frames_we_can_transfer; + jack_port_get_latency_range(left_port, JackPlaybackLatency, &latest_left_latency_range); + jack_port_get_latency_range(right_port, JackPlaybackLatency, &latest_right_latency_range); + time_of_latest_transfer = get_absolute_time_in_fp(); pthread_mutex_unlock(&buffer_mutex); // unlock - - // now, if there are any more frames to put into the buffer, fill them with + + // now, if there are any more frames to put into the buffer, fill them with // silence jack_nframes_t i; @@ -190,35 +186,77 @@ int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *a left_buffer[i] = 0.0; right_buffer[i] = 0.0; } - - jack_port_get_latency_range(left_port, JackPlaybackLatency, &latest_latency_range); - time_of_latest_latency_range = get_absolute_time_in_fp(); - return 0; } -void default_jack_error_callback(const char *desc) { - debug(2,"jackd error: \"%s\"",desc); -} +void default_jack_error_callback(const char *desc) { debug(2, "jackd error: \"%s\"", desc); } -void default_jack_info_callback(const char *desc) { - inform("jackd information: \"%s\"",desc); -} +void default_jack_info_callback(const char *desc) { inform("jackd information: \"%s\"", desc); } int jack_is_running() { - int reply = -1; + int reply = -1; // if the client is open and initialised, see if the status is "rolling" if (client_is_open) { jack_position_t pos; - jack_transport_state_t transport_state = jack_transport_query (client, &pos); - if (transport_state == JackTransportRolling) - reply = 0; - else - reply = -2; + jack_transport_state_t transport_state = jack_transport_query(client, &pos); + if (transport_state == JackTransportRolling) { + // check if either port has a zero latency -- if so, then it's disconnected. This might be too + // much + jack_latency_range_t left_latency_range, right_latency_range; + jack_port_get_latency_range(left_port, JackPlaybackLatency, &left_latency_range); + jack_port_get_latency_range(right_port, JackPlaybackLatency, &right_latency_range); + + if ((left_latency_range.min == 0) && (left_latency_range.max == 0) && + (right_latency_range.min == 0) && (right_latency_range.max == 0)) { + reply = -3; // not connected + } else { + reply = 0; + } + } else { + reply = -2; // not rolling + } } return reply; } +int jack_client_open_if_needed(void) { + if (client_is_open == 0) { + jack_status_t status; + client = jack_client_open(config.jack_client_name, JackNoStartServer, &status); + if (client) { + jack_set_process_callback(client, jack_stream_write_cb, 0); + left_port = jack_port_register(client, config.jack_left_channel_name, JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + right_port = jack_port_register(client, config.jack_right_channel_name, + JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + sample_rate = jack_get_sample_rate(client); + // debug(1, "jackaudio sample rate = %" PRId32 ".", sample_rate); + if (sample_rate == 44100) { + if (jack_activate(client)) { + debug(1, "jackaudio cannot activate client"); + } else { + client_is_open = 1; + debug(2, "jackaudio client opened."); + } + } else { + inform( + "jackaudio is running at the wrong speed (%d) for Shairport Sync, which must be 44100", + sample_rate); + } + } + } + return client_is_open; +} + +void jack_deinit() { + if (client_is_open) { + if (jack_deactivate(client)) + debug(1, "Error deactivating jack client"); + if (jack_client_close(client)) + debug(1, "Error closing jack client"); + } +} + int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) { config.audio_backend_latency_offset = 0; config.audio_backend_buffer_desired_length = 0.15; @@ -228,12 +266,35 @@ int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) // do the "general" audio options. Note, these options are in the "general" stanza! parse_general_audio_options(); - + // other options would be picked up here... - jack_set_error_function(default_jack_error_callback); + // now the specific options + if (config.cfg != NULL) { + const char *str; + /* Get the Client Name. */ + if (config_lookup_string(config.cfg, "jack.client_name", &str)) { + config.jack_client_name = (char *)str; + } + /* Get the Left Channel Name. */ + if (config_lookup_string(config.cfg, "jack.left_channel_name", &str)) { + config.jack_left_channel_name = (char *)str; + } + /* Get the Right Channel Name. */ + if (config_lookup_string(config.cfg, "jack.right_channel_name", &str)) { + config.jack_right_channel_name = (char *)str; + } + } + + if (config.jack_client_name == NULL) + config.jack_client_name = strdup("Shairport Sync"); + if (config.jack_left_channel_name == NULL) + config.jack_left_channel_name = strdup("left"); + if (config.jack_right_channel_name == NULL) + config.jack_right_channel_name = strdup("right"); + + jack_set_error_function(default_jack_error_callback); jack_set_info_function(default_jack_info_callback); - client_is_open = 0; // allocate space for the audio buffer audio_lmb = malloc(buffer_size); @@ -242,53 +303,52 @@ int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) audio_toq = audio_eoq = audio_lmb; audio_umb = audio_lmb + buffer_size; audio_occupancy = 0; // frames + + client_is_open = 0; + jack_client_open_if_needed(); + return 0; } -void jack_start(__attribute__((unused)) int i_sample_rate, __attribute__((unused)) int i_sample_format) { - debug(1,"jack start"); +void jack_start(__attribute__((unused)) int i_sample_rate, + __attribute__((unused)) int i_sample_format) { + debug(1, "jack start"); // int reply = -1; - - // see if the client is running. If not, try to open and initialise it - if (client_is_open == 0) { - jack_status_t status; - client = jack_client_open("Shairport Sync", JackNoStartServer, &status); - if (client) { - jack_set_process_callback(client, jack_stream_write_cb, 0); - left_port = jack_port_register(client, "Left", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); - right_port = jack_port_register(client, "Right", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); - sample_rate = jack_get_sample_rate(client); - debug(1, "jackaudio sample rate = %" PRId32 ".", sample_rate); - if (jack_activate(client)) { - debug(1, "jackaudio cannot activate client"); - } else { - client_is_open = 1; - debug(1, "jackaudio client opened."); - } - } - } - - if (client_is_open == 0) - debug(1,"cannot open a jack client for a play session"); + + // see if the client is running. If not, try to open and initialise it + + if (jack_client_open_if_needed() == 0) + debug(1, "cannot open a jack client for a play session"); } int jack_delay(long *the_delay) { + + // without the mutex, we could get the time of what is the last transfer of data to a jack buffer, + // but then a transfer could occur and we would get the buffer occupancy after another transfer had occurred + // so we could "lose" a full transfer (e.g. 1024 frames @ 44,100 fps ~ 23.2 milliseconds) + pthread_mutex_lock(&buffer_mutex); int64_t time_now = get_absolute_time_in_fp(); - int64_t delta = time_now - time_of_latest_latency_range; - + int64_t delta = time_now - time_of_latest_transfer; // this is the time back to the last time data was transferred into a jack buffer + size_t audio_occupancy_now = audio_occupancy; // this is the buffer occupancy before any subsequent transfer because transfer is blocked by the mutex + pthread_mutex_unlock(&buffer_mutex); + int64_t frames_processed_since_latest_latency_check = (delta * 44100) >> 32; - + // debug(1,"delta: %" PRId64 " frames.",frames_processed_since_latest_latency_check); - - *the_delay = latest_latency_range.min + audio_occupancy - frames_processed_since_latest_latency_check; + jack_nframes_t base_latency = (latest_left_latency_range.min + latest_left_latency_range.max)/2; + if (base_latency == 0) + base_latency = (latest_right_latency_range.min + latest_right_latency_range.max)/2; + *the_delay = + base_latency + audio_occupancy_now - frames_processed_since_latest_latency_check; // debug(1,"reporting a delay of %d frames",*the_delay); return 0; } + void jack_flush() { - debug(1,"jack flush"); + // debug(1,"jack flush"); // lock pthread_mutex_lock(&buffer_mutex); audio_toq = audio_eoq = audio_lmb; @@ -298,6 +358,4 @@ void jack_flush() { // unlock } -void jack_stop(void) { - debug(1,"jack stop"); -} \ No newline at end of file +void jack_stop(void) { debug(1, "jack stop"); } \ No newline at end of file diff --git a/common.h b/common.h index 8b2baffa..10d8d1f8 100644 --- a/common.h +++ b/common.h @@ -203,6 +203,10 @@ typedef struct { int disable_resend_requests; // set this to stop resend request being made for missing packets double diagnostic_drop_packet_fraction; // pseudo randomly drop this fraction of packets, for // debugging. Currently audio packets only... +#ifdef CONFIG_JACK + char *jack_client_name, *jack_left_channel_name, *jack_right_channel_name; +#endif + } shairport_cfg; uint32_t nctohl(const uint8_t *p); // read 4 characters from *p and do ntohl on them diff --git a/player.c b/player.c index 26dae07c..73995fc4 100644 --- a/player.c +++ b/player.c @@ -844,7 +844,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (config.output->is_running) if (config.output->is_running() !=0 ) { // if the back end isn't running for any reason - debug(1,"not running"); + debug(3,"not running"); debug_mutex_lock(&conn->flush_mutex, 1000, 1); conn->flush_requested = 1; debug_mutex_unlock(&conn->flush_mutex, 3); diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 4ff2bd4c..23314d67 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -91,6 +91,14 @@ pa = // application_name = "Shairport Sync"; //Set this to the name that should appear in the Sounds "Applications" tab when Shairport Sync is active. }; +// Parameters for the "jack" JACK Audio Connection Kit backend. +jack = +{ +// client_name = "Shairport Sync"; //Set this to the name of the client that should appear in "Connections" when Shairport Sync is active. +// left_channel_name = "left"; //Set this to the name of the left output port that should appear in "Connections" when Shairport Sync is active. +// right_channel_name = "right"; //Set this to the name of the right output port that should appear in "Connections" when Shairport Sync is active. +}; + // Parameters for the "pipe" audio back end, a back end that directs raw CD-style audio output to a pipe. No interpolation is done. pipe = { From 6c6dda9cce491faa4c871ddcd747de67492db2ac Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 2 Sep 2018 17:25:29 +0100 Subject: [PATCH 101/569] Add ability for the jack back end to automaticlly connect a client when the jack server comes up and to drop the client when a play session is finished. --- audio_jack.c | 67 +++++++++++++++++++++++++++++++++---- common.c | 20 +++++++++++ common.h | 4 +++ scripts/shairport-sync.conf | 2 ++ shairport.c | 18 ---------- 5 files changed, 87 insertions(+), 24 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index e8cd51a8..ec36ed19 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -42,10 +42,13 @@ enum ift_type { #define buffer_size 44100 * 4 * 2 * 2 static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER; char *audio_lmb, *audio_umb, *audio_toq, *audio_eoq; size_t audio_occupancy; // this is in frames, not bytes. A frame is a left and // right sample, each 16 bits, hence 4 bytes +pthread_t *open_client_if_necessary_thread = NULL; + // static void help(void); int init(int, char **); @@ -220,6 +223,7 @@ int jack_is_running() { } int jack_client_open_if_needed(void) { + pthread_mutex_lock(&client_mutex); if (client_is_open == 0) { jack_status_t status; client = jack_client_open(config.jack_client_name, JackNoStartServer, &status); @@ -245,21 +249,46 @@ int jack_client_open_if_needed(void) { } } } + pthread_mutex_unlock(&client_mutex); return client_is_open; } -void jack_deinit() { +void jack_close(void) { + pthread_mutex_lock(&client_mutex); if (client_is_open) { if (jack_deactivate(client)) debug(1, "Error deactivating jack client"); if (jack_client_close(client)) debug(1, "Error closing jack client"); + client_is_open = 0; } + pthread_mutex_unlock(&client_mutex); +} + +void jack_deinit() { + jack_close(); + if (open_client_if_necessary_thread) { + pthread_cancel(*open_client_if_necessary_thread); + free((char *)open_client_if_necessary_thread); + } +} + +void *open_client_if_necessary_thread_function(void *arg) { + int *interval = (int *)arg; + while (*interval != 0) { + if (client_is_open == 0) { + debug(1, "Try to open the jack client"); + jack_client_open_if_needed(); + } + sleep(*interval); + } + pthread_exit(NULL); } int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) { config.audio_backend_latency_offset = 0; config.audio_backend_buffer_desired_length = 0.15; + config.jack_auto_client_open_interval = 1; // check every second // get settings from settings file first, allow them to be overridden by // command line options @@ -272,6 +301,7 @@ int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) // now the specific options if (config.cfg != NULL) { const char *str; + int value; /* Get the Client Name. */ if (config_lookup_string(config.cfg, "jack.client_name", &str)) { config.jack_client_name = (char *)str; @@ -284,6 +314,21 @@ int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) if (config_lookup_string(config.cfg, "jack.right_channel_name", &str)) { config.jack_right_channel_name = (char *)str; } + + /* See if we should attempt to connect to the jack server automatically, and, if so, how often we should try. */ + if (config_lookup_int(config.cfg, "jack.auto_client_open_interval", &value)) { + if ((value < 0) || (value > 300)) + die("Invalid jack auto_client_open_interval \"%sd\". It should be between 0 and 300, default is %d", + value,config.jack_auto_client_open_interval); + else + config.jack_auto_client_open_interval = value; + } + + /* See if we should close the client at then end of a play session. */ + config_set_lookup_bool(config.cfg, "jack.auto_client_disconnect", &config.jack_auto_client_disconnect); + + // now, start a thread to automatically open a client when there is a server. + } if (config.jack_client_name == NULL) @@ -305,16 +350,22 @@ int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) audio_occupancy = 0; // frames client_is_open = 0; - jack_client_open_if_needed(); + + if (config.jack_auto_client_open_interval != 0) { + open_client_if_necessary_thread = malloc(sizeof(pthread_t)); + if (open_client_if_necessary_thread == NULL) + die("Couldn't allocate space for jack server scanner thread"); + pthread_create(open_client_if_necessary_thread, NULL, open_client_if_necessary_thread_function, &config.jack_auto_client_open_interval); + } else { + jack_client_open_if_needed(); + } return 0; } void jack_start(__attribute__((unused)) int i_sample_rate, __attribute__((unused)) int i_sample_format) { - debug(1, "jack start"); - // int reply = -1; - + // debug(1, "jack start"); // see if the client is running. If not, try to open and initialise it if (jack_client_open_if_needed() == 0) @@ -358,4 +409,8 @@ void jack_flush() { // unlock } -void jack_stop(void) { debug(1, "jack stop"); } \ No newline at end of file +void jack_stop(void) { + // debug(1, "jack stop"); + if (config.jack_auto_client_disconnect) + jack_close(); +} \ No newline at end of file diff --git a/common.c b/common.c index 85aeceec..3ecb7751 100644 --- a/common.c +++ b/common.c @@ -568,6 +568,26 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { } #endif +int config_set_lookup_bool(config_t *cfg, char *where, int *dst) { + const char *str = 0; + if (config_lookup_string(cfg, where, &str)) { + if (strcasecmp(str, "no") == 0) { + (*dst) = 0; + return 1; + } else if (strcasecmp(str, "yes") == 0) { + (*dst) = 1; + return 1; + } else { + die("Invalid %s option choice \"%s\". It should be \"yes\" or \"no\"", where, str); + return 0; + } + } else { + return 0; + } +} + + + void command_set_volume(double volume) { if (config.cmd_set_volume) { /*Spawn a child to run the program.*/ diff --git a/common.h b/common.h index 10d8d1f8..fccd4b8c 100644 --- a/common.h +++ b/common.h @@ -205,6 +205,8 @@ typedef struct { // debugging. Currently audio packets only... #ifdef CONFIG_JACK char *jack_client_name, *jack_left_channel_name, *jack_right_channel_name; + int jack_auto_client_open_interval; // will try to open a client automatically every second + int jack_auto_client_disconnect; // will disconnect from the server on end of session if set, normally clear. #endif } shairport_cfg; @@ -276,6 +278,8 @@ pthread_t main_thread_id; shairport_cfg config; config_t config_file_stuff; +int config_set_lookup_bool(config_t *cfg, char *where, int *dst); + void command_start(void); void command_stop(void); void command_set_volume(double volume); diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 23314d67..028a3758 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -97,6 +97,8 @@ jack = // client_name = "Shairport Sync"; //Set this to the name of the client that should appear in "Connections" when Shairport Sync is active. // left_channel_name = "left"; //Set this to the name of the left output port that should appear in "Connections" when Shairport Sync is active. // right_channel_name = "right"; //Set this to the name of the right output port that should appear in "Connections" when Shairport Sync is active. +// auto_client_open_interval = 1; // Set this to the interval in seconds between automatic attempts to open a client on the jack server. Set to zero to disable this behaviour. +// auto_client_disconnet = "no"; // Set this to "yes" to close the jack client automatically at then end of a play session. Default is no -- the client stays connected. }; // Parameters for the "pipe" audio back end, a back end that directs raw CD-style audio output to a pipe. No interpolation is done. diff --git a/shairport.c b/shairport.c index bd4025dc..214e0d6e 100644 --- a/shairport.c +++ b/shairport.c @@ -94,24 +94,6 @@ #include #endif -static inline int config_set_lookup_bool(config_t *cfg, char *where, int *dst) { - const char *str = 0; - if (config_lookup_string(cfg, where, &str)) { - if (strcasecmp(str, "no") == 0) { - (*dst) = 0; - return 1; - } else if (strcasecmp(str, "yes") == 0) { - (*dst) = 1; - return 1; - } else { - die("Invalid %s option choice \"%s\". It should be \"yes\" or \"no\"", where, str); - return 0; - } - } else { - return 0; - } -} - static int shutting_down = 0; char configuration_file_path[4096 + 1]; char actual_configuration_file_path[4096 + 1]; From 8ff2b1bfb5f76b0a6373180a0efec2b0516e0b96 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 2 Sep 2018 17:26:56 +0100 Subject: [PATCH 102/569] Add ability for the jack back end to automaticlly connect a client when the jack server comes up and to drop the client when a play session is finished. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 4cad62fb..0464b8f7 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.3d5], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d6], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 39545cdfb1cf8dacfea1dfb1cb05f672dee940ec Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 2 Sep 2018 17:28:20 +0100 Subject: [PATCH 103/569] Add ability for the jack back end to automaticlly connect a client when the jack server comes up and to drop the client when a play session is finished. Clang formatted. --- audio_jack.c | 50 +++++++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index ec36ed19..eaad2b6b 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -49,7 +49,6 @@ size_t audio_occupancy; // this is in frames, not bytes. A frame is a left and // right sample, each 16 bits, hence 4 bytes pthread_t *open_client_if_necessary_thread = NULL; - // static void help(void); int init(int, char **); // static void onmove_cb(void *, int); @@ -85,7 +84,7 @@ int client_is_open; jack_client_t *client; jack_nframes_t sample_rate; -jack_latency_range_t latest_left_latency_range,latest_right_latency_range; +jack_latency_range_t latest_left_latency_range, latest_right_latency_range; int64_t time_of_latest_transfer; int play(void *buf, int samples) { @@ -281,7 +280,7 @@ void *open_client_if_necessary_thread_function(void *arg) { jack_client_open_if_needed(); } sleep(*interval); - } + } pthread_exit(NULL); } @@ -314,21 +313,23 @@ int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) if (config_lookup_string(config.cfg, "jack.right_channel_name", &str)) { config.jack_right_channel_name = (char *)str; } - - /* See if we should attempt to connect to the jack server automatically, and, if so, how often we should try. */ + + /* See if we should attempt to connect to the jack server automatically, and, if so, how often + * we should try. */ if (config_lookup_int(config.cfg, "jack.auto_client_open_interval", &value)) { if ((value < 0) || (value > 300)) - die("Invalid jack auto_client_open_interval \"%sd\". It should be between 0 and 300, default is %d", - value,config.jack_auto_client_open_interval); + die("Invalid jack auto_client_open_interval \"%sd\". It should be between 0 and 300, " + "default is %d", + value, config.jack_auto_client_open_interval); else config.jack_auto_client_open_interval = value; } /* See if we should close the client at then end of a play session. */ - config_set_lookup_bool(config.cfg, "jack.auto_client_disconnect", &config.jack_auto_client_disconnect); - + config_set_lookup_bool(config.cfg, "jack.auto_client_disconnect", + &config.jack_auto_client_disconnect); + // now, start a thread to automatically open a client when there is a server. - } if (config.jack_client_name == NULL) @@ -350,12 +351,13 @@ int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) audio_occupancy = 0; // frames client_is_open = 0; - + if (config.jack_auto_client_open_interval != 0) { open_client_if_necessary_thread = malloc(sizeof(pthread_t)); if (open_client_if_necessary_thread == NULL) die("Couldn't allocate space for jack server scanner thread"); - pthread_create(open_client_if_necessary_thread, NULL, open_client_if_necessary_thread_function, &config.jack_auto_client_open_interval); + pthread_create(open_client_if_necessary_thread, NULL, open_client_if_necessary_thread_function, + &config.jack_auto_client_open_interval); } else { jack_client_open_if_needed(); } @@ -373,31 +375,33 @@ void jack_start(__attribute__((unused)) int i_sample_rate, } int jack_delay(long *the_delay) { - + // without the mutex, we could get the time of what is the last transfer of data to a jack buffer, - // but then a transfer could occur and we would get the buffer occupancy after another transfer had occurred - // so we could "lose" a full transfer (e.g. 1024 frames @ 44,100 fps ~ 23.2 milliseconds) - pthread_mutex_lock(&buffer_mutex); + // but then a transfer could occur and we would get the buffer occupancy after another transfer + // had occurred + // so we could "lose" a full transfer (e.g. 1024 frames @ 44,100 fps ~ 23.2 milliseconds) + pthread_mutex_lock(&buffer_mutex); int64_t time_now = get_absolute_time_in_fp(); - int64_t delta = time_now - time_of_latest_transfer; // this is the time back to the last time data was transferred into a jack buffer - size_t audio_occupancy_now = audio_occupancy; // this is the buffer occupancy before any subsequent transfer because transfer is blocked by the mutex + int64_t delta = time_now - time_of_latest_transfer; // this is the time back to the last time data + // was transferred into a jack buffer + size_t audio_occupancy_now = audio_occupancy; // this is the buffer occupancy before any + // subsequent transfer because transfer is blocked + // by the mutex pthread_mutex_unlock(&buffer_mutex); int64_t frames_processed_since_latest_latency_check = (delta * 44100) >> 32; // debug(1,"delta: %" PRId64 " frames.",frames_processed_since_latest_latency_check); - jack_nframes_t base_latency = (latest_left_latency_range.min + latest_left_latency_range.max)/2; + jack_nframes_t base_latency = (latest_left_latency_range.min + latest_left_latency_range.max) / 2; if (base_latency == 0) - base_latency = (latest_right_latency_range.min + latest_right_latency_range.max)/2; - *the_delay = - base_latency + audio_occupancy_now - frames_processed_since_latest_latency_check; + base_latency = (latest_right_latency_range.min + latest_right_latency_range.max) / 2; + *the_delay = base_latency + audio_occupancy_now - frames_processed_since_latest_latency_check; // debug(1,"reporting a delay of %d frames",*the_delay); return 0; } - void jack_flush() { // debug(1,"jack flush"); // lock From dc46e16075d53750069a16502058804daddd9302 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 2 Sep 2018 17:50:48 +0100 Subject: [PATCH 104/569] Update RELEASENOTES.md --- RELEASENOTES.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 425b12d6..97c078e4 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,6 +1,21 @@ +Version 3.3d6 +==== +An improved [Jack Audio](http://jackaudio.org) backend. Testing notes: +* At the `./configure` step, include `--with-jack`. This should not require any extra packages or libraries when it is being compiled on an AVLinux machine. No need for any other backend to be included, so you can omit `--with-alsa`, etc. +* Tested on the latest AVLinux only. +* Jack Audio will not run correctly on a virtual machine; it will only work properly on a real device. +* A new set of configuration options are present in the configuration file. +* The Shairport Sync backend will attempt, every second, to open a client on the Jack server until it succeeds. This behaviour can be stopped or the interval extended with the `jack` `auto_client_open_interval` configuration option. +* Once a Jack client has been opened by the Shairport Sync backend, it will remain open. This behaviour can be stopped or the interval extended with the `jack` `auto_client_disconnect` configuration option. +* Other Jack congfiguration options include the ability to change the name of the client – `client_name` – and the names of the channels – `left_channel_name` and `right_channel_name`. + +Testing needed -- these arrangements may not conform to the norms of the Jack community, so all feedback gratefully received. + +Some information, e.g. the true output rate, not implemented yet. + Version 3.3d5 ==== -* Introducing a very rough implementation of a [Jack Audio](http://jackaudio.org) backend. The JACK Audio Connection Kit "provides a basic infrastructure for audio applications to communicate with each other and with audio hardware. Through JACK, users are enabled to build powerful systems for signal processing and music production." +* Introducing a very rough implementation of a Jack Audio backend. The JACK Audio Connection Kit "provides a basic infrastructure for audio applications to communicate with each other and with audio hardware. Through JACK, users are enabled to build powerful systems for signal processing and music production." Version 3.3d4 ==== From 8e61fbd6667b6c44eb49c9b8a48602c2659a96d7 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 2 Sep 2018 17:53:23 +0100 Subject: [PATCH 105/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 97c078e4..29bb7c13 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -6,7 +6,7 @@ An improved [Jack Audio](http://jackaudio.org) backend. Testing notes: * Jack Audio will not run correctly on a virtual machine; it will only work properly on a real device. * A new set of configuration options are present in the configuration file. * The Shairport Sync backend will attempt, every second, to open a client on the Jack server until it succeeds. This behaviour can be stopped or the interval extended with the `jack` `auto_client_open_interval` configuration option. -* Once a Jack client has been opened by the Shairport Sync backend, it will remain open. This behaviour can be stopped or the interval extended with the `jack` `auto_client_disconnect` configuration option. +* Once a Jack client has been opened by the Shairport Sync backend, it will remain open. This behaviour can be changed with the `jack` `auto_client_disconnect` configuration option. * Other Jack congfiguration options include the ability to change the name of the client – `client_name` – and the names of the channels – `left_channel_name` and `right_channel_name`. Testing needed -- these arrangements may not conform to the norms of the Jack community, so all feedback gratefully received. From 97920454d01e0918fba23cc52cf4a7d2dca93a86 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 2 Sep 2018 17:54:27 +0100 Subject: [PATCH 106/569] Fix typo --- scripts/shairport-sync.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 028a3758..b63eb229 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -98,7 +98,7 @@ jack = // left_channel_name = "left"; //Set this to the name of the left output port that should appear in "Connections" when Shairport Sync is active. // right_channel_name = "right"; //Set this to the name of the right output port that should appear in "Connections" when Shairport Sync is active. // auto_client_open_interval = 1; // Set this to the interval in seconds between automatic attempts to open a client on the jack server. Set to zero to disable this behaviour. -// auto_client_disconnet = "no"; // Set this to "yes" to close the jack client automatically at then end of a play session. Default is no -- the client stays connected. +// auto_client_disconnect = "no"; // Set this to "yes" to close the jack client automatically at then end of a play session. Default is no -- the client stays connected. }; // Parameters for the "pipe" audio back end, a back end that directs raw CD-style audio output to a pipe. No interpolation is done. From 175d91a47faf2c52ac72336c8ad0044997e8462b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 3 Sep 2018 11:45:14 +0100 Subject: [PATCH 107/569] Tidy up some comments, stop making it die for small problems. --- audio_jack.c | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index eaad2b6b..09e74c6a 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -49,15 +49,12 @@ size_t audio_occupancy; // this is in frames, not bytes. A frame is a left and // right sample, each 16 bits, hence 4 bytes pthread_t *open_client_if_necessary_thread = NULL; -// static void help(void); int init(int, char **); -// static void onmove_cb(void *, int); void jack_deinit(void); void jack_start(int, int); int play(void *, int); void jack_stop(void); int jack_is_running(void); -// static void onmove_cb(void *, int); int jack_delay(long *); void jack_flush(void); @@ -144,10 +141,11 @@ int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *a pthread_mutex_lock(&buffer_mutex); if (audio_occupancy < frames_we_can_transfer) { frames_we_can_transfer = audio_occupancy; + // This means we effectively have underflow from the Shairport Sync source. + // In fact, it may be that there is nothing at all coming from the source, + // but the Shairport Sync client is open and active, so it must continue to output something. } - // frames we can transfer will never be greater than the frames available - if (frames_we_can_transfer * 2 * 2 <= (size_t)(audio_umb - audio_toq)) { // the bytes are all in a row in the audio buffer deinterleave_and_convert_stream(audio_toq, &left_buffer[0], frames_we_can_transfer, @@ -183,7 +181,6 @@ int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *a // now, if there are any more frames to put into the buffer, fill them with // silence jack_nframes_t i; - for (i = frames_we_can_transfer; i < nframes; i++) { left_buffer[i] = 0.0; right_buffer[i] = 0.0; @@ -318,8 +315,8 @@ int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) * we should try. */ if (config_lookup_int(config.cfg, "jack.auto_client_open_interval", &value)) { if ((value < 0) || (value > 300)) - die("Invalid jack auto_client_open_interval \"%sd\". It should be between 0 and 300, " - "default is %d", + debug(1,"Invalid jack auto_client_open_interval \"%sd\". It should be between 0 and 300, " + "default is %d.", value, config.jack_auto_client_open_interval); else config.jack_auto_client_open_interval = value; @@ -328,8 +325,6 @@ int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) /* See if we should close the client at then end of a play session. */ config_set_lookup_bool(config.cfg, "jack.auto_client_disconnect", &config.jack_auto_client_disconnect); - - // now, start a thread to automatically open a client when there is a server. } if (config.jack_client_name == NULL) @@ -352,12 +347,16 @@ int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) client_is_open = 0; + // now, if selected, start a thread to automatically open a client when there is a server. if (config.jack_auto_client_open_interval != 0) { open_client_if_necessary_thread = malloc(sizeof(pthread_t)); - if (open_client_if_necessary_thread == NULL) - die("Couldn't allocate space for jack server scanner thread"); - pthread_create(open_client_if_necessary_thread, NULL, open_client_if_necessary_thread_function, + if (open_client_if_necessary_thread == NULL) { + debug(1,"Couldn't allocate space for jack server scanner thread"); + jack_client_open_if_needed(); + } else { + pthread_create(open_client_if_necessary_thread, NULL, open_client_if_necessary_thread_function, &config.jack_auto_client_open_interval); + } } else { jack_client_open_if_needed(); } @@ -390,13 +389,11 @@ int jack_delay(long *the_delay) { pthread_mutex_unlock(&buffer_mutex); int64_t frames_processed_since_latest_latency_check = (delta * 44100) >> 32; - // debug(1,"delta: %" PRId64 " frames.",frames_processed_since_latest_latency_check); jack_nframes_t base_latency = (latest_left_latency_range.min + latest_left_latency_range.max) / 2; if (base_latency == 0) base_latency = (latest_right_latency_range.min + latest_right_latency_range.max) / 2; *the_delay = base_latency + audio_occupancy_now - frames_processed_since_latest_latency_check; - // debug(1,"reporting a delay of %d frames",*the_delay); return 0; @@ -404,13 +401,11 @@ int jack_delay(long *the_delay) { void jack_flush() { // debug(1,"jack flush"); - // lock pthread_mutex_lock(&buffer_mutex); audio_toq = audio_eoq = audio_lmb; audio_umb = audio_lmb + buffer_size; audio_occupancy = 0; // frames pthread_mutex_unlock(&buffer_mutex); - // unlock } void jack_stop(void) { From 76138873c6d81f1873c69237eb9d4452a410eb2d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 7 Sep 2018 12:08:58 +0100 Subject: [PATCH 108/569] Stop requiring jack to be "rolling" --- audio_jack.c | 46 +++++++++++++++++++++------------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 09e74c6a..e27c5884 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -193,26 +193,21 @@ void default_jack_error_callback(const char *desc) { debug(2, "jackd error: \"%s void default_jack_info_callback(const char *desc) { inform("jackd information: \"%s\"", desc); } int jack_is_running() { - int reply = -1; + int reply = -1; // meaning jack is not running // if the client is open and initialised, see if the status is "rolling" if (client_is_open) { - jack_position_t pos; - jack_transport_state_t transport_state = jack_transport_query(client, &pos); - if (transport_state == JackTransportRolling) { - // check if either port has a zero latency -- if so, then it's disconnected. This might be too - // much - jack_latency_range_t left_latency_range, right_latency_range; - jack_port_get_latency_range(left_port, JackPlaybackLatency, &left_latency_range); - jack_port_get_latency_range(right_port, JackPlaybackLatency, &right_latency_range); - if ((left_latency_range.min == 0) && (left_latency_range.max == 0) && - (right_latency_range.min == 0) && (right_latency_range.max == 0)) { - reply = -3; // not connected - } else { - reply = 0; - } + // check if the ports have a zero latency -- if they both have, then it's disconnected. + + jack_latency_range_t left_latency_range, right_latency_range; + jack_port_get_latency_range(left_port, JackPlaybackLatency, &left_latency_range); + jack_port_get_latency_range(right_port, JackPlaybackLatency, &right_latency_range); + + if ((left_latency_range.min == 0) && (left_latency_range.max == 0) && + (right_latency_range.min == 0) && (right_latency_range.max == 0)) { + reply = -2; // meaning Shairport Sync is not connected } else { - reply = -2; // not rolling + reply = 0; // meaning jack is open and Shairport Sync is connected to it } } return reply; @@ -283,7 +278,7 @@ void *open_client_if_necessary_thread_function(void *arg) { int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) { config.audio_backend_latency_offset = 0; - config.audio_backend_buffer_desired_length = 0.15; + config.audio_backend_buffer_desired_length = 0.500; config.jack_auto_client_open_interval = 1; // check every second // get settings from settings file first, allow them to be overridden by @@ -315,9 +310,9 @@ int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) * we should try. */ if (config_lookup_int(config.cfg, "jack.auto_client_open_interval", &value)) { if ((value < 0) || (value > 300)) - debug(1,"Invalid jack auto_client_open_interval \"%sd\". It should be between 0 and 300, " - "default is %d.", - value, config.jack_auto_client_open_interval); + debug(1, "Invalid jack auto_client_open_interval \"%sd\". It should be between 0 and 300, " + "default is %d.", + value, config.jack_auto_client_open_interval); else config.jack_auto_client_open_interval = value; } @@ -351,11 +346,12 @@ int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) if (config.jack_auto_client_open_interval != 0) { open_client_if_necessary_thread = malloc(sizeof(pthread_t)); if (open_client_if_necessary_thread == NULL) { - debug(1,"Couldn't allocate space for jack server scanner thread"); + debug(1, "Couldn't allocate space for jack server scanner thread"); jack_client_open_if_needed(); } else { - pthread_create(open_client_if_necessary_thread, NULL, open_client_if_necessary_thread_function, - &config.jack_auto_client_open_interval); + pthread_create(open_client_if_necessary_thread, NULL, + open_client_if_necessary_thread_function, + &config.jack_auto_client_open_interval); } } else { jack_client_open_if_needed(); @@ -384,8 +380,8 @@ int jack_delay(long *the_delay) { int64_t delta = time_now - time_of_latest_transfer; // this is the time back to the last time data // was transferred into a jack buffer size_t audio_occupancy_now = audio_occupancy; // this is the buffer occupancy before any - // subsequent transfer because transfer is blocked - // by the mutex + // subsequent transfer because transfer is blocked + // by the mutex pthread_mutex_unlock(&buffer_mutex); int64_t frames_processed_since_latest_latency_check = (delta * 44100) >> 32; From 89ec9fcd0e0f58820cf4a38becceb0b42061b9ba Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 7 Sep 2018 12:09:20 +0100 Subject: [PATCH 109/569] Stop requiring jack to be "rolling" --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 0464b8f7..ceec4af6 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.3d6], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d7], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From cd6a99a7cfde1c5e1c1cc74ee6a77041bb4012d9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 8 Sep 2018 15:06:18 +0100 Subject: [PATCH 110/569] Include a definition of HAVE_LIBSOXR with using pkg_config --- configure.ac | 1 + 1 file changed, 1 insertion(+) diff --git a/configure.ac b/configure.ac index ceec4af6..690a774f 100644 --- a/configure.ac +++ b/configure.ac @@ -180,6 +180,7 @@ AC_ARG_WITH(soxr, [ --with-soxr = choose libsoxr for high-quality interpolation PKG_CHECK_MODULES( [SOXR], [soxr], [LIBS="${SOXR_LIBS} ${LIBS}"]) + AC_DEFINE([HAVE_LIBSOXR],[1],[Define to 1 if you are including support for soxr interpolation]) else AC_CHECK_LIB([avutil],[av_get_cpu_flags]) if test "x${ac_cv_lib_avutil_av_get_cpu_flags}" = xyes ; then From 9adea74f45dda56ba1f6d0591f5bd2aa4920bcd0 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 8 Sep 2018 15:08:45 +0100 Subject: [PATCH 111/569] Include a definition of HAVE_LIBSOXR with using pkg_config --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 690a774f..9ecdefa8 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.3d7], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d8], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 709007f30ba2eceb9e49651c12f1d9af6444a3ea Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 8 Sep 2018 15:12:53 +0100 Subject: [PATCH 112/569] Update RELEASENOTES.md --- RELEASENOTES.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 29bb7c13..a94c3e36 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,9 @@ +Version 3.3d7 +==== +**Bug Fix** + +Ensure the compiler flag HAVE_LIBSOXR is defined if the `--with-soxr` configuration flag is used and `pkg-config` is in use. + Version 3.3d6 ==== An improved [Jack Audio](http://jackaudio.org) backend. Testing notes: @@ -20,7 +26,7 @@ Version 3.3d5 Version 3.3d4 ==== * Sometimes `libsoxr` is built to rely on `libavutil`. With the present changes to `configure.ac`, if `libavutil` is present, link to it when linking to `libsoxr`, but if it's not present, don't link to it when linking to `libsoxr`. -* Add `pgk_config` support to `libsoxr` selection. +* Add `pgk-config` support to `libsoxr` selection. Thanks to [Jörg Krause](https://github.com/joerg-krause) for identifying these issues and proposing fixes. From 1a09a3a963bc8ea2a574be21b593008ea854d447 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 8 Sep 2018 15:13:19 +0100 Subject: [PATCH 113/569] Update RELEASENOTES.md --- RELEASENOTES.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a94c3e36..a1edf7fd 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,8 +1,7 @@ Version 3.3d7 ==== **Bug Fix** - -Ensure the compiler flag HAVE_LIBSOXR is defined if the `--with-soxr` configuration flag is used and `pkg-config` is in use. +* Ensure the compiler flag HAVE_LIBSOXR is defined if the `--with-soxr` configuration flag is used and `pkg-config` is in use. Version 3.3d6 ==== From a4d2053017795e7f2de5c4e0119776b4ae778518 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 8 Sep 2018 15:20:11 +0100 Subject: [PATCH 114/569] Update RELEASENOTES.md --- RELEASENOTES.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a1edf7fd..2be0a2c3 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,8 +1,15 @@ -Version 3.3d7 +Version 3.3d8 ==== **Bug Fix** * Ensure the compiler flag HAVE_LIBSOXR is defined if the `--with-soxr` configuration flag is used and `pkg-config` is in use. +Version 3.3d7 +==== +* Relax the "rolling" requirement for Jack output to be sent. +* Set the default buffer size for Jack to 0.5 seconds. This may be temporary! + +**Bug Fix** +* Ensure the compiler flag HAVE_LIBSOXR is defined if the `--with-soxr` configuration flag is used and `pkg-config` is in use. Version 3.3d6 ==== An improved [Jack Audio](http://jackaudio.org) backend. Testing notes: From 57e560a900f8f06ab91cd71acf849b22f404d97c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 8 Sep 2018 15:21:14 +0100 Subject: [PATCH 115/569] Update RELEASENOTES.md --- RELEASENOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 2be0a2c3..3def2067 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -10,6 +10,7 @@ Version 3.3d7 **Bug Fix** * Ensure the compiler flag HAVE_LIBSOXR is defined if the `--with-soxr` configuration flag is used and `pkg-config` is in use. + Version 3.3d6 ==== An improved [Jack Audio](http://jackaudio.org) backend. Testing notes: From 07b7fc0dad3197983718e8f1788e6eb906382b5c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 8 Sep 2018 15:21:37 +0100 Subject: [PATCH 116/569] Update RELEASENOTES.md --- RELEASENOTES.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 3def2067..1f559d39 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -8,9 +8,6 @@ Version 3.3d7 * Relax the "rolling" requirement for Jack output to be sent. * Set the default buffer size for Jack to 0.5 seconds. This may be temporary! -**Bug Fix** -* Ensure the compiler flag HAVE_LIBSOXR is defined if the `--with-soxr` configuration flag is used and `pkg-config` is in use. - Version 3.3d6 ==== An improved [Jack Audio](http://jackaudio.org) backend. Testing notes: From 64a5c558966d509b8226d26535a31016a51c2cdc Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 8 Sep 2018 15:23:15 +0100 Subject: [PATCH 117/569] Update RELEASENOTES.md --- RELEASENOTES.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1f559d39..72de5bdd 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,11 +1,11 @@ Version 3.3d8 ==== **Bug Fix** -* Ensure the compiler flag HAVE_LIBSOXR is defined if the `--with-soxr` configuration flag is used and `pkg-config` is in use. +* Ensure the compiler flag `HAVE_LIBSOXR` is defined if the `--with-soxr` configuration flag is used and `pkg-config` is in use. Version 3.3d7 ==== -* Relax the "rolling" requirement for Jack output to be sent. +* Remove the "rolling" requirement for Jack output to be sent. * Set the default buffer size for Jack to 0.5 seconds. This may be temporary! Version 3.3d6 @@ -14,7 +14,7 @@ An improved [Jack Audio](http://jackaudio.org) backend. Testing notes: * At the `./configure` step, include `--with-jack`. This should not require any extra packages or libraries when it is being compiled on an AVLinux machine. No need for any other backend to be included, so you can omit `--with-alsa`, etc. * Tested on the latest AVLinux only. * Jack Audio will not run correctly on a virtual machine; it will only work properly on a real device. -* A new set of configuration options are present in the configuration file. +* A new set of configuration options is present in the configuration file. * The Shairport Sync backend will attempt, every second, to open a client on the Jack server until it succeeds. This behaviour can be stopped or the interval extended with the `jack` `auto_client_open_interval` configuration option. * Once a Jack client has been opened by the Shairport Sync backend, it will remain open. This behaviour can be changed with the `jack` `auto_client_disconnect` configuration option. * Other Jack congfiguration options include the ability to change the name of the client – `client_name` – and the names of the channels – `left_channel_name` and `right_channel_name`. From 69642bb7de2380a307d8710938bb161d96be845a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 9 Sep 2018 14:12:20 +0100 Subject: [PATCH 118/569] Partly clean up use of automake, autoconfig and compiler flags. Works but still not consistent. --- Makefile.am | 2 +- common.c | 2 +- common.h | 4 +- configure.ac | 132 ++++++++++++++++++++++--------------------------- dbus-service.c | 4 +- mdns_avahi.c | 6 +-- player.c | 24 ++++----- rtsp.c | 8 +-- shairport.c | 28 +++++------ 9 files changed, 94 insertions(+), 116 deletions(-) diff --git a/Makefile.am b/Makefile.am index e8e4a341..3b17f585 100644 --- a/Makefile.am +++ b/Makefile.am @@ -17,7 +17,7 @@ else endif endif -if USE_APPLE_ALAC +if USE_ALAC shairport_sync_SOURCES += apple_alac.cpp endif diff --git a/common.c b/common.c index 3ecb7751..da28cb8f 100644 --- a/common.c +++ b/common.c @@ -1218,7 +1218,7 @@ char *get_version_string() { #ifdef CONFIG_METADATA strcat(version_string, "-metadata"); #endif -#ifdef CONFIG_MQTT +#ifdef HAVE_LIBMOSQUITTO strcat(version_string, "-mqtt"); #endif #ifdef HAVE_DBUS diff --git a/common.h b/common.h index fccd4b8c..4aca72c7 100644 --- a/common.h +++ b/common.h @@ -94,7 +94,7 @@ typedef struct { size_t metadata_sockmsglength; int get_coverart; #endif -#ifdef CONFIG_MQTT +#ifdef HAVE_LIBMOSQUITTO int mqtt_enabled; char *mqtt_hostname; int mqtt_port; @@ -189,7 +189,7 @@ typedef struct { enum dbus_session_type mpris_service_bus_type; #endif -#ifdef HAVE_METADATA_HUB +#ifdef CONFIG_METADATA_HUB char *cover_art_cache_dir; int scan_interval_when_active; // number of seconds between DACP server scans when playing // something (1) diff --git a/configure.ac b/configure.ac index 9ecdefa8..cf569a57 100644 --- a/configure.ac +++ b/configure.ac @@ -64,10 +64,9 @@ if test "x${with_pkg_config}" = xyes ; then [POPT], [popt], [CFLAGS="${POPT_CFLAGS} ${CFLAGS}" LIBS="${POPT_LIBS} ${LIBS}" - AC_DEFINE([HAVE_LIBPOPT],[1],[Define to 1 if you have popt])]) + AC_DEFINE([HAVE_LIBPOPT],[1],[Define to 1 if you have the popt library])]) else AC_CHECK_LIB([popt],[poptGetContext], , AC_MSG_ERROR(libpopt needed)) - AC_DEFINE([HAVE_LIBPOPT],[1],[Define to 1 if you have popt]) fi AC_ARG_WITH([dummy],[ --with-dummy = include the dummy audio back end ],[AC_MSG_RESULT(>>Including the dummy audio back end) AC_DEFINE([CONFIG_DUMMY], 1, [Needed by the compiler.]) ], ) @@ -112,19 +111,16 @@ AM_CONDITIONAL([INSTALL_CONFIG_FILES], [test "x$with_configfiles" = "xyes"]) # Look for Apple ALAC flag AC_ARG_WITH(apple-alac, [ --with-apple-alac = include support for the Apple ALAC decoder], [AC_MSG_RESULT(>>Including the Apple ALAC Decoder) - HAS_APPLE_ALAC=1 - AM_CONDITIONAL([USE_APPLE_ALAC], [test 0]) - AC_DEFINE([CONFIG_APPLE_ALAC], 1, [Needed by the compiler.]) + REQUESTED_ALAC=1 if test "x${with_pkg_config}" = xyes ; then PKG_CHECK_MODULES( [ALAC], [alac], [LIBS="${ALAC_LIBS} ${LIBS}" - AC_DEFINE([HAVE_APPLE_ALAC],[1],[Define to 1 if you are using the Apple ALAC Decoder])]) + AC_DEFINE([HAVE_ALAC],[1],[Define to 1 if you are using the Apple ALAC Decoder])]) else AC_CHECK_LIB([alac], [BitBufferInit], , AC_MSG_ERROR(Apple ALAC Decoder support requires the alac library!)) - AC_DEFINE([HAVE_APPLE_ALAC],[1],[Define to 1 if you have the Apple ALAC library]) fi ]) -AM_CONDITIONAL([USE_APPLE_ALAC], [test "x$HAS_APPLE_ALAC" = "x1"]) +AM_CONDITIONAL([USE_ALAC], [test "x$REQUESTED_ALAC" = "x1"]) # Look for piddir flag AC_ARG_WITH(piddir, [ --with-piddir= Specify a pathname to a directory in which to write the PID file.], [ @@ -134,10 +130,10 @@ AC_ARG_WITH(piddir, [ --with-piddir= Specify a pathname to a directory AC_MSG_ERROR(when you use the --with-piddir directive you must specify the pathname of the directory into which the PID file will be written) fi AC_MSG_RESULT(${with_piddir}) - HAS_CUSTOMPIDDIR=1 + REQUESTED_CUSTOMPIDDIR=1 AC_SUBST(CUSTOM_PID_DIR,["${with_piddir}"]) - AC_DEFINE([USE_CUSTOM_PID_DIR],1,[Hook up special proc to libdaemon to point to this directory])], ) -AM_CONDITIONAL([USE_CUSTOMPIDDIR], [test "x$HAS_CUSTOMPIDDIR" = "x1"]) + AC_DEFINE([DEFINED_CUSTOM_PID_DIR],1,[Hook up special proc to libdaemon to point to this directory])], ) +AM_CONDITIONAL([USE_CUSTOMPIDDIR], [test "x$REQUESTED_CUSTOMPIDDIR" = "x1"]) # Check --with-ssl=argument AC_ARG_WITH(ssl, [ choose --with-ssl=openssl, --with-ssl=mbedtls or --with-ssl=polarssl (deprecated) for encryption services ], [ @@ -156,8 +152,6 @@ AC_ARG_WITH(ssl, [ choose --with-ssl=openssl, --with-ssl=mbedtls or --with-ssl=p else AC_CHECK_LIB([crypto], [main], , AC_MSG_ERROR(libcrypto selected but the library cannot be found!)) AC_CHECK_LIB([ssl], [main], , AC_MSG_ERROR(libssl selected but the library cannot be found!)) - AC_DEFINE([HAVE_LIBCRYPTO],[1],[Define to 1 if you have libcrypto]) - AC_DEFINE([HAVE_LIBSSL],[1],[Define to 1 if you have libssl]) fi elif test "x${with_ssl}" = xmbedtls ; then AC_CHECK_LIB([mbedtls],[mbedtls_ssl_init],, @@ -179,8 +173,9 @@ AC_ARG_WITH(soxr, [ --with-soxr = choose libsoxr for high-quality interpolation if test "x${with_pkg_config}" = xyes ; then PKG_CHECK_MODULES( [SOXR], [soxr], - [LIBS="${SOXR_LIBS} ${LIBS}"]) - AC_DEFINE([HAVE_LIBSOXR],[1],[Define to 1 if you are including support for soxr interpolation]) + [LIBS="${SOXR_LIBS} ${LIBS}" + AC_DEFINE([HAVE_LIBSOXR],[1],[Define to 1 if you are including support for soxr interpolation])]) + else AC_CHECK_LIB([avutil],[av_get_cpu_flags]) if test "x${ac_cv_lib_avutil_av_get_cpu_flags}" = xyes ; then @@ -192,11 +187,9 @@ AC_ARG_WITH(soxr, [ --with-soxr = choose libsoxr for high-quality interpolation fi ], ) -# Look for metadata flag -- set flag for conditional compilation +# Look for metadata flag and resolve it further down the script AC_ARG_WITH(metadata, [ --with-metadata = include support for a metadata feed], [ - AC_MSG_RESULT(>>Including metadata support) - AC_DEFINE([CONFIG_METADATA], 1, [Needed by the compiler.])], ) -AM_CONDITIONAL([USE_METADATA], [test "x$with_metadata" = "xyes"]) + REQUESTED_METADATA=1], ) # What follows is a bit messy, because if the relevant library is requested, a compiler flag is defined, a file is included in the compilation # and the relevant link files are added. @@ -204,24 +197,23 @@ AM_CONDITIONAL([USE_METADATA], [test "x$with_metadata" = "xyes"]) # Look for avahi flag AC_ARG_WITH(avahi, [ --with-avahi = choose Avahi-based mDNS support], [ AC_MSG_RESULT(>>Including Avahi mDNS support) - HAS_AVAHI=1 + REQUESTED_AVAHI=1 AC_DEFINE([CONFIG_AVAHI], 1, [Needed by the compiler.]) AC_CHECK_LIB([avahi-client], [avahi_client_new], , AC_MSG_ERROR(Avahi support requires the avahi-client library!)) AC_CHECK_LIB([avahi-common],[avahi_strerror], , AC_MSG_ERROR(Avahi support requires the avahi-common library!))], ) -AM_CONDITIONAL([USE_AVAHI], [test "x$HAS_AVAHI" = "x1"]) +AM_CONDITIONAL([USE_AVAHI], [test "x$REQUESTED_AVAHI" = "x1"]) # Look for tinysvcmdns flag AC_ARG_WITH(tinysvcmdns, [ --with-tinysvcmdns = choose tinysvcmdns-based mDNS support], [ AC_MSG_RESULT(>>Including tinysvcmdns mDNS support) - HAS_TINYSVCMDNS=1 + REQUESTED_TINYSVCMDNS=1 AC_DEFINE([CONFIG_TINYSVCMDNS], 1, [Needed by the compiler.])], ) -AM_CONDITIONAL([USE_TINYSVCMDNS], [test "x$HAS_TINYSVCMDNS" = "x1"]) +AM_CONDITIONAL([USE_TINYSVCMDNS], [test "x$REQUESTED_TINYSVCMDNS" = "x1"]) # Look for ALSA flag AC_ARG_WITH(alsa, [ --with-alsa = choose ALSA API support (GNU/Linux only)], [AC_MSG_RESULT(>>Including an ALSA back end) - HAS_ALSA=1 - AM_CONDITIONAL([USE_ALSA], [test 0]) + REQUESTED_ALSA=1 AC_DEFINE([CONFIG_ALSA], 1, [Needed by the compiler.]) if test "x${with_pkg_config}" = xyes ; then PKG_CHECK_MODULES( @@ -230,52 +222,53 @@ AC_ARG_WITH(alsa, [ --with-alsa = choose ALSA API support (GNU/Linux only)], AC_DEFINE([HAVE_LIBASOUND],[1],[Define to 1 if you have ALSA])]) else AC_CHECK_LIB([asound], [snd_pcm_open], , AC_MSG_ERROR(ALSA support requires the asound library!)) - AC_DEFINE([HAVE_LIBASOUND],[1],[Define to 1 if you have ALSA]) fi ]) -AM_CONDITIONAL([USE_ALSA], [test "x$HAS_ALSA" = "x1"]) +AM_CONDITIONAL([USE_ALSA], [test "x$REQUESTED_ALSA" = "x1"]) # Look for jack flag AC_ARG_WITH(jack, [ --with-jack = include a Jack Audio Connection Kit (jack) backend], [ AC_MSG_RESULT(>>Including a Jack Audio Connection Kit (jack) back end) - HAS_JACK=1 + REQUESTED_JACK=1 AC_DEFINE([CONFIG_JACK], 1, [Needed by the compiler.]) if test "x${with_pkg_config}" = xyes ; then PKG_CHECK_MODULES( [JACK], [jack], - [LIBS="${JACK_LIBS} ${LIBS}"],[AC_MSG_ERROR(Jack Audio Connection Kit support requires the jack library -- libjack-dev suggested!)]) + [LIBS="${JACK_LIBS} ${LIBS}" + AC_DEFINE([HAVE_JACK],[1],[Define to 1 if you have Jack audio])], + [AC_MSG_ERROR(Jack Audio Connection Kit support requires the jack library -- libjack-dev suggested!)]) else AC_CHECK_LIB([jack], [jack_client_open], , AC_MSG_ERROR(Jack Audio Connection Kit support requires the jack library -- libjack-dev suggested!)) fi ]) -AM_CONDITIONAL([USE_JACK], [test "x$HAS_JACK" = "x1"]) +AM_CONDITIONAL([USE_JACK], [test "x$REQUESTED_JACK" = "x1"]) # Look for SNDIO flag AC_ARG_WITH(sndio, [ --with-sndio = choose SNDIO API support], [ AC_MSG_RESULT(>>Including a SNDIO back end) - HAS_SNDIO=1 + REQUESTED_SNDIO=1 AC_DEFINE([CONFIG_SNDIO], 1, [Needed by the compiler.]) AC_CHECK_LIB([sndio], [sio_open], , AC_MSG_ERROR(SNDIO support requires the sndio library -- libsndio-dev suggested))], ) -AM_CONDITIONAL([USE_SNDIO], [test "x$HAS_SNDIO" = "x1"]) +AM_CONDITIONAL([USE_SNDIO], [test "x$REQUESTED_SNDIO" = "x1"]) # Look for AO flag AC_ARG_WITH(ao, [ --with-ao = choose AO (Audio Output?) API support. N.B. no synchronisation -- so underflow or overflow is inevitable!], [ AC_MSG_RESULT(>>Including an AO back end. N.B. no synchronisation -- so underflow or overflow is inevitable!) - HAS_AO=1 + REQUESTED_AO=1 AC_DEFINE([CONFIG_AO], 1, [Needed by the compiler.]) AC_CHECK_LIB([ao], [ao_initialize], , AC_MSG_ERROR(AO support requires the ao library -- libao-dev suggested))], ) -AM_CONDITIONAL([USE_AO], [test "x$HAS_AO" = "x1"]) +AM_CONDITIONAL([USE_AO], [test "x$REQUESTED_AO" = "x1"]) # Look for Soundio flag AC_ARG_WITH(soundio, [ --with-soundio = choose soundio API support.], [ AC_MSG_RESULT(>>Including an soundio back end) - HAS_SOUNDIO=1 + REQUESTED_SOUNDIO=1 AC_DEFINE([CONFIG_SOUNDIO], 1, [Needed by the compiler.]) AC_CHECK_LIB([soundio], [soundio_create], , AC_MSG_ERROR(soundio support requires the soundio library -- libsoundio-dev suggested))], ) -AM_CONDITIONAL([USE_SOUNDIO], [test "x$HAS_SOUNDIO" = "x1"]) +AM_CONDITIONAL([USE_SOUNDIO], [test "x$REQUESTED_SOUNDIO" = "x1"]) # Look for pulseaudio flag AC_ARG_WITH(pa, [ --with-pa = choose PulseAudio support.], [ AC_MSG_RESULT(>>Including a PulseAudio back end.) - HAS_PA=1 + REQUESTED_PA=1 AC_DEFINE([CONFIG_PA], 1, [Needed by the compiler.]) if test "x${with_pkg_config}" = xyes ; then PKG_CHECK_MODULES( @@ -285,92 +278,86 @@ AC_ARG_WITH(pa, [ --with-pa = choose PulseAudio support.], [ AC_CHECK_LIB([pulse-simple], [pa_simple_new], , AC_MSG_ERROR(PulseAudio support requires the libpulse library!)) AC_CHECK_LIB([pulse], [pa_stream_peek], , AC_MSG_ERROR(PulseAudio support requires the libpulse-dev library.)) fi ]) -AM_CONDITIONAL([USE_PA], [test "x$HAS_PA" = "x1"]) +AM_CONDITIONAL([USE_PA], [test "x$REQUESTED_PA" = "x1"]) # Look for Convolution flag AC_ARG_WITH(convolution, [ --with-convolution = choose audio DSP convolution support], [ AC_MSG_RESULT(>>Including convolution support) - HAS_CONVOLUTION=1 + REQUESTED_CONVOLUTION=1 AM_INIT_AUTOMAKE([subdir-objects]) AC_DEFINE([CONFIG_CONVOLUTION], 1, [Needed by the compiler.]) AC_CHECK_LIB([sndfile], [sf_open], , AC_MSG_ERROR(Convolution support requires the sndfile library!))], ) -AM_CONDITIONAL([USE_CONVOLUTION], [test "x$HAS_CONVOLUTION" = "x1"]) +AM_CONDITIONAL([USE_CONVOLUTION], [test "x$REQUESTED_CONVOLUTION" = "x1"]) # Look for dns_sd flag AC_ARG_WITH(dns_sd, [ --with-dns_sd = choose dns_sd mDNS support], [ AC_MSG_RESULT(>>Including dns_sd for mDNS support) - HAS_DNS_SD=1 + REQUESTED_DNS_SD=1 AC_DEFINE([CONFIG_HAVE_DNS_SD_H], 1, [Needed by the compiler.]) AC_SEARCH_LIBS([DNSServiceRefDeallocate], [dns_sd], , AC_MSG_ERROR(dns_sd support requires the dns_sd library!))], ) -AM_CONDITIONAL([USE_DNS_SD], [test "x$HAS_DNS_SD" = "x1"]) +AM_CONDITIONAL([USE_DNS_SD], [test "x$REQUESTED_DNS_SD" = "x1"]) # Look for dbus flag AC_ARG_WITH(dbus-interface, [ --with-dbus-interface = include support for the native Shairport Sync D-Bus interface], [ AC_MSG_RESULT(>>Including dbus support) - HAS_DBUS=1 + REQUESTED_DBUS=1 AC_DEFINE([HAVE_DBUS], 1, [Needed by the compiler.]) PKG_CHECK_MODULES([GIO_UNIX], [gio-unix-2.0 >= 2.30.0],[CFLAGS="${GIO_UNIX_CFLAGS} ${CFLAGS}" LIBS="${GIO_UNIX_LIBS} ${LIBS}"],[AC_MSG_ERROR(dbus messaging support requires the glib 2.0 library -- libglib2.0-dev suggested!)]) ], ) -AM_CONDITIONAL([USE_DBUS], [test "x$HAS_DBUS" = "x1"]) +AM_CONDITIONAL([USE_DBUS], [test "x$REQUESTED_DBUS" = "x1"]) # Look for dbus test client flag AC_ARG_WITH(dbus-test-client, [ --with-dbus-test-client = compile dbus test client], [ AC_MSG_RESULT(>>Including dbus test client) - HAS_DBUS_CLIENT=1 + REQUESTED_DBUS_CLIENT=1 AC_DEFINE([HAVE_DBUS_CLIENT], 1, [Needed by the compiler.]) PKG_CHECK_MODULES([GIO_UNIX], [gio-unix-2.0 >= 2.30.0],[CFLAGS="${GIO_UNIX_CFLAGS} ${CFLAGS}" LIBS="${GIO_UNIX_LIBS} ${LIBS}"],[AC_MSG_ERROR(dbus client support requires the glib 2.0 library -- libglib2.0-dev suggested!)]) ], ) -AM_CONDITIONAL([USE_DBUS_CLIENT], [test "x$HAS_DBUS_CLIENT" = "x1"]) +AM_CONDITIONAL([USE_DBUS_CLIENT], [test "x$REQUESTED_DBUS_CLIENT" = "x1"]) # Look for mpris flag AC_ARG_WITH(mpris-interface, [ --with-mpris-interface = include support for a Shairport Sync D-Bus interface conforming to the MPRIS standard], [ AC_MSG_RESULT(>>Including the MPRIS D-Bus Interface) - HAS_MPRIS=1 + REQUESTED_MPRIS=1 AC_DEFINE([HAVE_MPRIS], 1, [Needed by the compiler.]) PKG_CHECK_MODULES([GIO_UNIX], [gio-unix-2.0 >= 2.30.0],[CFLAGS="${GIO_UNIX_CFLAGS} ${CFLAGS}" LIBS="${GIO_UNIX_LIBS} ${LIBS}"],[AC_MSG_ERROR(dbus messaging support for mpris requires the glib 2.0 library -- libglib2.0-dev suggested!)]) ], ) -AM_CONDITIONAL([USE_MPRIS], [test "x$HAS_MPRIS" = "x1"]) +AM_CONDITIONAL([USE_MPRIS], [test "x$REQUESTED_MPRIS" = "x1"]) # Look for mpris test client flag AC_ARG_WITH(mpris-test-client, [ --with-mpris-test-client = compile mpris test client], [ AC_MSG_RESULT(>>Including mpris test client) - HAS_MPRIS_CLIENT=1 + REQUESTED_MPRIS_CLIENT=1 AC_DEFINE([HAVE_MPRIS_CLIENT], 1, [Needed by the compiler.]) PKG_CHECK_MODULES([GIO_UNIX], [gio-unix-2.0 >= 2.30.0],[CFLAGS="${GIO_UNIX_CFLAGS} ${CFLAGS}" LIBS="${GIO_UNIX_LIBS} ${LIBS}"],[AC_MSG_ERROR(mpris client support requires the glib 2.0 library -- libglib2.0-dev suggested!)]) ], ) -AM_CONDITIONAL([USE_MPRIS_CLIENT], [test "x$HAS_MPRIS_CLIENT" = "x1"]) +AM_CONDITIONAL([USE_MPRIS_CLIENT], [test "x$REQUESTED_MPRIS_CLIENT" = "x1"]) -#AM_CONDITIONAL([USE_DBUS_CORE_AND_DACP], [test "x$HAS_MPRIS" = "x1" -o "x$HAS_DBUS" = "x1"]) +#AM_CONDITIONAL([USE_DBUS_CORE_AND_DACP], [test "x$REQUESTED_MPRIS" = "x1" -o "x$REQUESTED_DBUS" = "x1"]) # Look for mqtt flag AC_ARG_WITH(mqtt-client, [ --with-mqtt-client = include support for the native Shairport Sync MQTT interface], [ AC_MSG_RESULT(>>Including MQTT support) - HAS_MQTT=1 - AC_DEFINE([HAVE_MQTT], 1, [Needed by the compiler.]) - AC_DEFINE([CONFIG_MQTT], 1, [Needed by the compiler.]) + REQUESTED_MQTT=1 AC_CHECK_LIB([mosquitto], [mosquitto_lib_init], , AC_MSG_ERROR(MQTT support requires the mosquitto library -- libmosquitto-dev suggested!)) - AC_CHECK_HEADERS([mosquitto.h],,[AC_MSG_ERROR([ header missing])]) - AC_DEFINE([HAVE_MOSQUITTO],[1],[Define to 1 if you have the mosquitto library]) - - AC_MSG_RESULT(>>Enabling metadata as it is required by mqtt) - AC_DEFINE([CONFIG_METADATA], 1, [Needed by the compiler.]) - AC_DEFINE([USE_METADATA], 1, [Needed by the compiler.]) - PKG_CHECK_MODULES([GIO_UNIX], [gio-unix-2.0 >= 2.30.0],[CFLAGS="${GIO_UNIX_CFLAGS} ${CFLAGS}" LIBS="${GIO_UNIX_LIBS} ${LIBS}"],[AC_MSG_ERROR(dbus messaging support for mpris requires the glib 2.0 library -- libglib2.0-dev suggested!)]) ],) -AM_CONDITIONAL([USE_MQTT], [test "x$HAS_MQTT" = "x1"]) +AM_CONDITIONAL([USE_MQTT], [test "x$REQUESTED_MQTT" = "x1"]) -if test "x$HAS_MPRIS" = "x1" || test "x$HAS_DBUS" = "x1" || test "x$HAS_MQTT" = "x1"; then - AC_MSG_RESULT(>>Including the metadata hub) - HAS_METADATA_HUB=1 - AC_DEFINE([HAVE_METADATA_HUB], 1, [Needed by the compiler.]) - AC_MSG_RESULT(>>Including the DACP client) - HAS_DACP_CLIENT=1 - AC_DEFINE([HAVE_DACP_CLIENT], 1, [Needed by the compiler.]) +if test "x$REQUESTED_MPRIS" = "x1" || test "x$REQUESTED_DBUS" = "x1" || test "x$REQUESTED_MQTT" = "x1"; then + AC_MSG_RESULT(>>Including extended metadata and DACP client support) + REQUESTED_EXTENDED_METADATA_SUPPORT=1 + AC_DEFINE([CONFIG_METADATA_HUB], 1, [Needed by the compiler.]) + AC_DEFINE([CONFIG_DACP_CLIENT], 1, [Needed by the compiler.]) fi -AM_CONDITIONAL([USE_METADATA_HUB], [test "x$HAS_METADATA_HUB" = "x1"]) -AM_CONDITIONAL([USE_DACP_CLIENT], [test "x$HAS_DACP_CLIENT" = "x1"]) +AM_CONDITIONAL([USE_METADATA_HUB], [test "x$REQUESTED_EXTENDED_METADATA_SUPPORT" = "x1"]) +AM_CONDITIONAL([USE_DACP_CLIENT], [test "x$REQUESTED_EXTENDED_METADATA_SUPPORT" = "x1"]) +if test "x$REQUESTED_EXTENDED_METADATA_SUPPORT" = "x1" || test "x$REQUESTED_METADATA" = "x1"; then + AC_MSG_RESULT(>>Including metadata support) + AC_DEFINE([CONFIG_METADATA], 1, [Needed by the compiler.]) +fi +AM_CONDITIONAL([USE_METADATA], [test "x$REQUESTED_METADATA" = "x1"]) if test "x${with_systemd}" = xyes ; then # Find systemd unit dir @@ -387,9 +374,6 @@ if test "x${with_systemd}" = xyes ; then [with_systemdsystemunitdir="$def_systemdsystemunitdir"])]) AS_IF([test "x$with_systemdsystemunitdir" != "xno"], [AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])]) - AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"]) -else - AM_CONDITIONAL([HAVE_SYSTEMD], false) fi # Look for xmltoman diff --git a/dbus-service.c b/dbus-service.c index daf94e0f..0a66aed7 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -420,7 +420,7 @@ gboolean notify_drift_tolerance_callback(ShairportSync *skeleton, gboolean notify_alacdecoder_callback(ShairportSync *skeleton, __attribute__((unused)) gpointer user_data) { char *th = (char *)shairport_sync_get_alacdecoder(skeleton); -#ifdef HAVE_APPLE_ALAC +#ifdef HAVE_ALAC if (strcasecmp(th, "hammerton") == 0) config.use_apple_decoder = 0; else if (strcasecmp(th, "apple") == 0) @@ -677,7 +677,7 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name config.loudness_reference_volume_db); shairport_sync_set_drift_tolerance(SHAIRPORT_SYNC(shairportSyncSkeleton), config.tolerance); -#ifdef HAVE_APPLE_ALAC +#ifdef HAVE_ALAC if (config.use_apple_decoder == 0) { shairport_sync_set_alacdecoder(SHAIRPORT_SYNC(shairportSyncSkeleton), "hammerton"); debug(1, ">> ALACDecoder set to \"hammerton\""); diff --git a/mdns_avahi.c b/mdns_avahi.c index d9eb4b02..b739e716 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -33,7 +33,7 @@ #include "common.h" #include "mdns.h" #include "rtsp.h" -#ifdef HAVE_DACP_CLIENT +#ifdef CONFIG_DACP_CLIENT #include "dacp.h" #endif #include @@ -96,7 +96,7 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn dacpid += strlen("iTunes_Ctrl_"); if (strcmp(dacpid, dbs->dacp_id) == 0) { debug(3, "Client's DACP port: %u.", port); -#ifdef HAVE_DACP_CLIENT +#ifdef CONFIG_DACP_CLIENT dacp_monitor_port_update_callback(dacpid, port); #endif #ifdef CONFIG_METADATA @@ -140,7 +140,7 @@ static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, Avah break; case AVAHI_BROWSER_REMOVE: debug(3, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'.", name, type, domain); -#ifdef HAVE_DACP_CLIENT +#ifdef CONFIG_DACP_CLIENT char *dacpid = strstr(name, "iTunes_Ctrl_"); if (dacpid) { dacpid += strlen("iTunes_Ctrl_"); diff --git a/player.c b/player.c index 73995fc4..a90ced91 100644 --- a/player.c +++ b/player.c @@ -67,18 +67,12 @@ #include #endif -#ifdef HAVE_METADATA_HUB +#ifdef CONFIG_METADATA_HUB #include "metadata_hub.h" #endif -#ifdef HAVE_DACP_CLIENT +#ifdef CONFIG_DACP_CLIENT #include "dacp.h" -#include -#endif - -#ifdef HAVE_DBUS -#include "dbus-interface.h" -#include "dbus-service.h" #endif #include "common.h" @@ -88,7 +82,7 @@ #include "alac.h" -#ifdef HAVE_APPLE_ALAC +#ifdef HAVE_ALAC #include "apple_alac.h" #endif @@ -276,7 +270,7 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_co AES_cbc_encrypt(buf, packet, aeslen, &conn->aes, iv, AES_DECRYPT); #endif memcpy(packet + aeslen, buf + aeslen, len - aeslen); -#ifdef HAVE_APPLE_ALAC +#ifdef HAVE_ALAC if (config.use_apple_decoder) { if (conn->decoder_in_use != 1 << decoder_apple_alac) { debug(2, "Apple ALAC Decoder used on encrypted audio."); @@ -295,7 +289,7 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_co } } else { // not encrypted -#ifdef HAVE_APPLE_ALAC +#ifdef HAVE_ALAC if (config.use_apple_decoder) { if (conn->decoder_in_use != 1 << decoder_apple_alac) { debug(2, "Apple ALAC Decoder used on unencrypted audio."); @@ -437,7 +431,7 @@ static int init_decoder(int32_t fmtp[12], rtsp_conn_info *conn) { alac->setinfo_8a_rate = fmtp[11]; alac_allocate_buffers(alac); // no pthread cancellation point in here -#ifdef HAVE_APPLE_ALAC +#ifdef HAVE_ALAC apple_alac_init(fmtp); // no pthread cancellation point in here #endif @@ -446,7 +440,7 @@ static int init_decoder(int32_t fmtp[12], rtsp_conn_info *conn) { static void terminate_decoders(rtsp_conn_info *conn) { alac_free(conn->decoder_info); -#ifdef HAVE_APPLE_ALAC +#ifdef HAVE_ALAC apple_alac_terminate(); #endif } @@ -1443,7 +1437,7 @@ void player_thread_cleanup_handler(void *arg) { elapsedHours, elapsedMin, elapsedSec, conn->input_frame_rate); } -#ifdef HAVE_DACP_CLIENT +#ifdef CONFIG_DACP_CLIENT relinquish_dacp_server_information( conn); // say it doesn't belong to this conversation thread any more... @@ -1694,7 +1688,7 @@ void *player_thread_func(void *arg) { 0; // number of times in a row that there's been a serious sync error // stop looking elsewhere for DACP stuff -#ifdef HAVE_DACP_CLIENT +#ifdef CONFIG_DACP_CLIENT // debug(1, "Set dacp server info"); // this does not have pthread cancellation points in it (assuming avahi doesn't) set_dacp_server_information(conn); // this will start scanning when a port is registered by the diff --git a/rtsp.c b/rtsp.c index 5c549e36..7feaaa37 100644 --- a/rtsp.c +++ b/rtsp.c @@ -63,11 +63,11 @@ #include "rtp.h" #include "rtsp.h" -#ifdef HAVE_METADATA_HUB +#ifdef CONFIG_METADATA_HUB #include "metadata_hub.h" #endif -#ifdef HAVE_MQTT +#ifdef HAVE_LIBMOSQUITTO #include "mqtt.h" #endif @@ -1298,10 +1298,10 @@ void *metadata_thread_function(__attribute__((unused)) void *ignore) { pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack); if (config.metadata_enabled) { metadata_process(pack.type, pack.code, pack.data, pack.length); -#ifdef HAVE_METADATA_HUB +#ifdef CONFIG_METADATA_HUB metadata_hub_process_metadata(pack.type, pack.code, pack.data, pack.length); #endif -#ifdef HAVE_MQTT +#ifdef HAVE_LIBMOSQUITTO if (config.mqtt_enabled) { mqtt_process_metadata(pack.type, pack.code, pack.data, pack.length); } diff --git a/shairport.c b/shairport.c index 214e0d6e..457d1426 100644 --- a/shairport.c +++ b/shairport.c @@ -64,11 +64,11 @@ #include "rtp.h" #include "rtsp.h" -#if defined(HAVE_DACP_CLIENT) +#if defined(CONFIG_DACP_CLIENT) #include "dacp.h" #endif -#if defined(HAVE_METADATA_HUB) +#if defined(CONFIG_METADATA_HUB) #include "metadata_hub.h" #endif @@ -76,7 +76,7 @@ #include "dbus-service.h" #endif -#ifdef HAVE_MQTT +#ifdef HAVE_LIBMOSQUITTO #include "mqtt.h" #endif @@ -331,7 +331,7 @@ int parse_options(int argc, char **argv) { config.fixedLatencyOffset = 11025; // this sounds like it works properly. config.diagnostic_drop_packet_fraction = 0.0; -#ifdef HAVE_METADATA_HUB +#ifdef CONFIG_METADATA_HUB config.cover_art_cache_dir = "/tmp/shairport-sync/.cache/coverart"; config.scan_interval_when_active = 1; // number of seconds between DACP server scans when playing something @@ -853,7 +853,7 @@ int parse_options(int argc, char **argv) { } #endif -#ifdef CONFIG_MQTT +#ifdef HAVE_LIBMOSQUITTO int tmpval = 0; config_set_lookup_bool(config.cfg, "mqtt.enabled", &config.mqtt_enabled); if (config.mqtt_enabled && !config.metadata_enabled) { @@ -1007,7 +1007,7 @@ int parse_options(int argc, char **argv) { free(i3); free(vs); -#ifdef CONFIG_MQTT +#ifdef HAVE_LIBMOSQUITTO // mqtt topic was not set. As we have the service name just now, set it if (config.mqtt_topic == NULL) { int topic_length = 1 + strlen(config.service_name) + 1; @@ -1018,7 +1018,7 @@ int parse_options(int argc, char **argv) { #endif // now, check and calculate the pid directory -#ifdef USE_CUSTOM_PID_DIR +#ifdef DEFINED_CUSTOM_PID_DIR char *use_this_pid_dir = PIDDIR; #else char *use_this_pid_dir = "/var/run/shairport-sync"; @@ -1124,7 +1124,7 @@ void exit_function() { void main_cleanup_handler(__attribute__((unused)) void *arg) { debug(1, "main cleanup handler called."); -#ifdef HAVE_MQTT +#ifdef HAVE_LIBMOSQUITTO if (config.mqtt_enabled) { // terminate_mqtt(); } @@ -1142,12 +1142,12 @@ void main_cleanup_handler(__attribute__((unused)) void *arg) { pthread_join(dbus_thread, NULL); #endif -#ifdef HAVE_DACP_CLIENT +#ifdef CONFIG_DACP_CLIENT debug(1, "Stopping DACP Monitor"); dacp_monitor_stop(); #endif -#ifdef HAVE_METADATA_HUB +#ifdef CONFIG_METADATA_HUB debug(1, "Stopping metadata hub"); metadata_hub_stop(); #endif @@ -1239,7 +1239,7 @@ int main(int argc, char **argv) { config.output_rate = 44100; // default config.decoders_supported = 1 << decoder_hammerton; // David Hammerton's decoder supported by default -#ifdef HAVE_APPLE_ALAC +#ifdef HAVE_ALAC config.decoders_supported += 1 << decoder_apple_alac; #endif @@ -1595,12 +1595,12 @@ int main(int argc, char **argv) { metadata_init(); // create the metadata pipe if necessary #endif -#ifdef HAVE_METADATA_HUB +#ifdef CONFIG_METADATA_HUB // debug(1, "Initialising metadata hub"); metadata_hub_init(); #endif -#ifdef HAVE_DACP_CLIENT +#ifdef CONFIG_DACP_CLIENT // debug(1, "Requesting DACP Monitor"); dacp_monitor_start(); #endif @@ -1617,7 +1617,7 @@ int main(int argc, char **argv) { #endif #endif -#ifdef HAVE_MQTT +#ifdef HAVE_LIBMOSQUITTO if (config.mqtt_enabled) { initialise_mqtt(); } From c9b3d2a2f44e4919e80340e3482497ac066506ff Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 9 Sep 2018 20:41:31 +0100 Subject: [PATCH 119/569] Many changes to compilation and linking flags. Stop using HAVE_* flags except where necessary, use CONFIG_* for optional stuff, use HAS_* for immediate definitions, use USE_* for AM definitions. Probably introduced bgs, sigh. --- Makefile.am | 6 +++--- common.c | 35 ++++++++++++++++----------------- common.h | 8 ++++---- configure.ac | 44 ++++++++++++++++++------------------------ dbus-service.c | 8 ++++---- mdns.c | 4 ++-- metadata_hub.c | 12 ++++++------ player.c | 34 ++++++++++++++++----------------- player.h | 12 ++++++------ rtsp.c | 22 ++++++++++----------- shairport.c | 52 +++++++++++++++++++++++++------------------------- 11 files changed, 116 insertions(+), 121 deletions(-) diff --git a/Makefile.am b/Makefile.am index 3b17f585..1feec23d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -17,7 +17,7 @@ else endif endif -if USE_ALAC +if USE_APPLE_ALAC shairport_sync_SOURCES += apple_alac.cpp endif @@ -104,7 +104,7 @@ noinst_HEADERS += $(BUILT_SOURCES) CLEANFILES += $(BUILT_SOURCES) dbus-interface.h dbus-interface.c: org.gnome.ShairportSync.xml - gdbus-codegen --interface-prefix org.gnome --generate-c-code dbus-interface org.gnome.ShairportSync.xml + gdbus-codegen --interface-prefix org.gnome --c-generate-autocleanup all --generate-c-code dbus-interface org.gnome.ShairportSync.xml endif if USE_MPRIS @@ -116,7 +116,7 @@ noinst_HEADERS += $(BUILT_SOURCES) CLEANFILES += $(BUILT_SOURCES) mpris-interface.h mpris-interface.c: org.mpris.MediaPlayer2.xml - gdbus-codegen --interface-prefix org.mpris --generate-c-code mpris-interface org.mpris.MediaPlayer2.xml + gdbus-codegen --interface-prefix org.mpris --c-generate-autocleanup all --generate-c-code mpris-interface org.mpris.MediaPlayer2.xml endif noinst_PROGRAMS = diff --git a/common.c b/common.c index da28cb8f..f895866c 100644 --- a/common.c +++ b/common.c @@ -2,6 +2,7 @@ * Utility routines. This file is part of Shairport. * Copyright (c) James Laird 2013 * The volume to attenuation function vol2attn copyright (c) Mike Brady 2014 + * Further changes (c) Mike Brady 2014 -- 2018 * All rights reserved. * * Permission is hereby granted, free of charge, to any person @@ -46,7 +47,7 @@ #include #endif -#ifdef HAVE_LIBSSL +#ifdef CONFIG_OPENSSL #include #include #include @@ -54,7 +55,7 @@ #include #endif -#ifdef HAVE_LIBPOLARSSL +#ifdef CONFIG_POLARSSL #include "polarssl/ctr_drbg.h" #include "polarssl/entropy.h" #include @@ -67,7 +68,7 @@ #endif #endif -#ifdef HAVE_LIBMBEDTLS +#ifdef CONFIG_MBEDTLS #include "mbedtls/ctr_drbg.h" #include "mbedtls/entropy.h" #include @@ -247,7 +248,7 @@ int mkpath(const char *path, mode_t mode) { return (status); } -#ifdef HAVE_LIBMBEDTLS +#ifdef CONFIG_MBEDTLS char *base64_enc(uint8_t *input, int length) { char *buf = NULL; size_t dlen = 0; @@ -300,7 +301,7 @@ uint8_t *base64_dec(char *input, int *outlen) { } #endif -#ifdef HAVE_LIBPOLARSSL +#ifdef CONFIG_POLARSSL char *base64_enc(uint8_t *input, int length) { char *buf = NULL; size_t dlen = 0; @@ -353,7 +354,7 @@ uint8_t *base64_dec(char *input, int *outlen) { } #endif -#ifdef HAVE_LIBSSL +#ifdef CONFIG_OPENSSL char *base64_enc(uint8_t *input, int length) { BIO *bmem, *b64; BUF_MEM *bptr; @@ -432,7 +433,7 @@ static char super_secret_key[] = "2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\n" "-----END RSA PRIVATE KEY-----\0"; -#ifdef HAVE_LIBSSL +#ifdef CONFIG_OPENSSL uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { RSA *rsa = NULL; if (!rsa) { @@ -456,7 +457,7 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { } #endif -#ifdef HAVE_LIBMBEDTLS +#ifdef CONFIG_MBEDTLS uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { mbedtls_pk_context pkctx; mbedtls_rsa_context *trsa; @@ -513,7 +514,7 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { } #endif -#ifdef HAVE_LIBPOLARSSL +#ifdef CONFIG_POLARSSL uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { rsa_context trsa; const char *pers = "rsa_encrypt"; @@ -1164,16 +1165,16 @@ int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *filename, const int void pthread_cleanup_debug_mutex_unlock(void *arg) { pthread_mutex_unlock((pthread_mutex_t *)arg); } char *get_version_string() { - char *version_string = malloc(200); + char *version_string = malloc(1024); if (version_string) { strcpy(version_string, PACKAGE_VERSION); -#ifdef HAVE_LIBMBEDTLS +#ifdef CONFIG_MBEDTLS strcat(version_string, "-mbedTLS"); #endif -#ifdef HAVE_LIBPOLARSSL +#ifdef CONFIG_POLARSSL strcat(version_string, "-PolarSSL"); #endif -#ifdef HAVE_LIBSSL +#ifdef CONFIG_OPENSSL strcat(version_string, "-OpenSSL"); #endif #ifdef CONFIG_TINYSVCMDNS @@ -1209,7 +1210,7 @@ char *get_version_string() { #ifdef CONFIG_PIPE strcat(version_string, "-pipe"); #endif -#ifdef HAVE_LIBSOXR +#ifdef CONFIG_SOXR strcat(version_string, "-soxr"); #endif #ifdef CONFIG_CONVOLUTION @@ -1218,13 +1219,13 @@ char *get_version_string() { #ifdef CONFIG_METADATA strcat(version_string, "-metadata"); #endif -#ifdef HAVE_LIBMOSQUITTO +#ifdef CONFIG_MQTT strcat(version_string, "-mqtt"); #endif -#ifdef HAVE_DBUS +#ifdef CONFIG_DBUS_INTERFACE strcat(version_string, "-dbus"); #endif -#ifdef HAVE_MPRIS +#ifdef CONFIG_MPRIS_INTERFACE strcat(version_string, "-mpris"); #endif strcat(version_string, "-sysconfdir:"); diff --git a/common.h b/common.h index 4aca72c7..41844b77 100644 --- a/common.h +++ b/common.h @@ -23,7 +23,7 @@ #define SAFAMILY sa_family #endif -#if defined(HAVE_DBUS) || defined(HAVE_MPRIS) +#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE) enum dbus_session_type { DBT_system = 0, // use the session bus DBT_session, // use the system bus @@ -94,7 +94,7 @@ typedef struct { size_t metadata_sockmsglength; int get_coverart; #endif -#ifdef HAVE_LIBMOSQUITTO +#ifdef CONFIG_MQTT int mqtt_enabled; char *mqtt_hostname; int mqtt_port; @@ -182,10 +182,10 @@ typedef struct { int loudness; float loudness_reference_volume_db; int alsa_use_hardware_mute; -#if defined(HAVE_DBUS) +#if defined(CONFIG_DBUS_INTERFACE) enum dbus_session_type dbus_service_bus_type; #endif -#if defined(HAVE_MPRIS) +#if defined(CONFIG_MPRIS_INTERFACE) enum dbus_session_type mpris_service_bus_type; #endif diff --git a/configure.ac b/configure.ac index cf569a57..8164384f 100644 --- a/configure.ac +++ b/configure.ac @@ -63,8 +63,7 @@ if test "x${with_pkg_config}" = xyes ; then PKG_CHECK_MODULES( [POPT], [popt], [CFLAGS="${POPT_CFLAGS} ${CFLAGS}" - LIBS="${POPT_LIBS} ${LIBS}" - AC_DEFINE([HAVE_LIBPOPT],[1],[Define to 1 if you have the popt library])]) + LIBS="${POPT_LIBS} ${LIBS}"]) else AC_CHECK_LIB([popt],[poptGetContext], , AC_MSG_ERROR(libpopt needed)) fi @@ -111,16 +110,16 @@ AM_CONDITIONAL([INSTALL_CONFIG_FILES], [test "x$with_configfiles" = "xyes"]) # Look for Apple ALAC flag AC_ARG_WITH(apple-alac, [ --with-apple-alac = include support for the Apple ALAC decoder], [AC_MSG_RESULT(>>Including the Apple ALAC Decoder) - REQUESTED_ALAC=1 + AC_DEFINE([CONFIG_APPLE_ALAC], 1, [Include support for using the Apple ALAC Decoder]) + REQUESTED_APPLE_ALAC=1 if test "x${with_pkg_config}" = xyes ; then PKG_CHECK_MODULES( [ALAC], [alac], - [LIBS="${ALAC_LIBS} ${LIBS}" - AC_DEFINE([HAVE_ALAC],[1],[Define to 1 if you are using the Apple ALAC Decoder])]) + [LIBS="${ALAC_LIBS} ${LIBS}"]) else AC_CHECK_LIB([alac], [BitBufferInit], , AC_MSG_ERROR(Apple ALAC Decoder support requires the alac library!)) fi ]) -AM_CONDITIONAL([USE_ALAC], [test "x$REQUESTED_ALAC" = "x1"]) +AM_CONDITIONAL([USE_APPLE_ALAC], [test "x$REQUESTED_APPLE_ALAC" = "x1"]) # Look for piddir flag AC_ARG_WITH(piddir, [ --with-piddir= Specify a pathname to a directory in which to write the PID file.], [ @@ -143,17 +142,17 @@ AC_ARG_WITH(ssl, [ choose --with-ssl=openssl, --with-ssl=mbedtls or --with-ssl=p AC_MSG_ERROR(choose "openssl", "mbedtls" or "polarssl" encryption) fi if test "x${with_ssl}" = xopenssl ; then + AC_DEFINE([CONFIG_OPENSSL], 1, [Use the OpenSSL libraries for encryption and encoding and decoding]) if test "x${with_pkg_config}" = xyes ; then PKG_CHECK_MODULES( [SSL], [libssl,libcrypto], - [LIBS="${SSL_LIBS} ${LIBS}" - AC_DEFINE([HAVE_LIBCRYPTO],[1],[Define to 1 if you have libcrypto]) - AC_DEFINE([HAVE_LIBSSL],[1],[Define to 1 if you have libssl])]) + [LIBS="${SSL_LIBS} ${LIBS}"]) else AC_CHECK_LIB([crypto], [main], , AC_MSG_ERROR(libcrypto selected but the library cannot be found!)) AC_CHECK_LIB([ssl], [main], , AC_MSG_ERROR(libssl selected but the library cannot be found!)) fi elif test "x${with_ssl}" = xmbedtls ; then + AC_DEFINE([CONFIG_MBEDTLS], 1, [Use the mbed TLS libraries for encryption and encoding and decoding]) AC_CHECK_LIB([mbedtls],[mbedtls_ssl_init],, [AC_MSG_ERROR([mbed tls support requires the mbedtls library -- libmbedtls-dev suggested],1)]) AC_CHECK_LIB([mbedcrypto], [mbedtls_entropy_func],, @@ -161,6 +160,7 @@ AC_ARG_WITH(ssl, [ choose --with-ssl=openssl, --with-ssl=mbedtls or --with-ssl=p AC_CHECK_LIB([mbedx509], [mbedtls_pk_init],, [AC_MSG_ERROR([mbed tls support requires the mbedx509 library -- libmbedx509-0 suggested],1)]) elif test "x${with_ssl}" = xpolarssl ; then + AC_DEFINE([CONFIG_POLARSSL], 1, [Use the PolarSSL libraries for encryption and encoding and decoding]) AC_CHECK_LIB([polarssl],[ssl_init], , AC_MSG_ERROR(PolarSSL is selected but the library cannot be found and is deprecated. Consider selecting mbed TLS instead using --with-ssl=mbedtls.)) else AC_MSG_ERROR(unknown option "${with_ssl}"." Please choose with "openssl", "mbedtls" or "polarssl") @@ -171,11 +171,10 @@ AC_ARG_WITH(ssl, [ choose --with-ssl=openssl, --with-ssl=mbedtls or --with-ssl=p AC_ARG_WITH(soxr, [ --with-soxr = choose libsoxr for high-quality interpolation], [ AC_MSG_RESULT(>>Including support for soxr-based interpolation) if test "x${with_pkg_config}" = xyes ; then + AC_DEFINE([CONFIG_SOXR], 1, [Include support for using the SoX Resampler library for interpolation]) PKG_CHECK_MODULES( [SOXR], [soxr], - [LIBS="${SOXR_LIBS} ${LIBS}" - AC_DEFINE([HAVE_LIBSOXR],[1],[Define to 1 if you are including support for soxr interpolation])]) - + [LIBS="${SOXR_LIBS} ${LIBS}"]) else AC_CHECK_LIB([avutil],[av_get_cpu_flags]) if test "x${ac_cv_lib_avutil_av_get_cpu_flags}" = xyes ; then @@ -218,8 +217,7 @@ AC_ARG_WITH(alsa, [ --with-alsa = choose ALSA API support (GNU/Linux only)], if test "x${with_pkg_config}" = xyes ; then PKG_CHECK_MODULES( [ALSA], [alsa], - [LIBS="${ALSA_LIBS} ${LIBS}" - AC_DEFINE([HAVE_LIBASOUND],[1],[Define to 1 if you have ALSA])]) + [LIBS="${ALSA_LIBS} ${LIBS}"]) else AC_CHECK_LIB([asound], [snd_pcm_open], , AC_MSG_ERROR(ALSA support requires the asound library!)) fi ]) @@ -233,8 +231,7 @@ AC_ARG_WITH(jack, [ --with-jack = include a Jack Audio Connection Kit (jack) ba if test "x${with_pkg_config}" = xyes ; then PKG_CHECK_MODULES( [JACK], [jack], - [LIBS="${JACK_LIBS} ${LIBS}" - AC_DEFINE([HAVE_JACK],[1],[Define to 1 if you have Jack audio])], + [LIBS="${JACK_LIBS} ${LIBS}"], [AC_MSG_ERROR(Jack Audio Connection Kit support requires the jack library -- libjack-dev suggested!)]) else AC_CHECK_LIB([jack], [jack_client_open], , AC_MSG_ERROR(Jack Audio Connection Kit support requires the jack library -- libjack-dev suggested!)) @@ -293,15 +290,15 @@ AM_CONDITIONAL([USE_CONVOLUTION], [test "x$REQUESTED_CONVOLUTION" = "x1"]) AC_ARG_WITH(dns_sd, [ --with-dns_sd = choose dns_sd mDNS support], [ AC_MSG_RESULT(>>Including dns_sd for mDNS support) REQUESTED_DNS_SD=1 - AC_DEFINE([CONFIG_HAVE_DNS_SD_H], 1, [Needed by the compiler.]) + AC_DEFINE([CONFIG_DNS_SD], 1, [Needed by the compiler.]) AC_SEARCH_LIBS([DNSServiceRefDeallocate], [dns_sd], , AC_MSG_ERROR(dns_sd support requires the dns_sd library!))], ) AM_CONDITIONAL([USE_DNS_SD], [test "x$REQUESTED_DNS_SD" = "x1"]) # Look for dbus flag AC_ARG_WITH(dbus-interface, [ --with-dbus-interface = include support for the native Shairport Sync D-Bus interface], [ AC_MSG_RESULT(>>Including dbus support) + AC_DEFINE([CONFIG_DBUS_INTERFACE], 1, [Include support for the native Shairport Sync D-Bus interface]) REQUESTED_DBUS=1 - AC_DEFINE([HAVE_DBUS], 1, [Needed by the compiler.]) PKG_CHECK_MODULES([GIO_UNIX], [gio-unix-2.0 >= 2.30.0],[CFLAGS="${GIO_UNIX_CFLAGS} ${CFLAGS}" LIBS="${GIO_UNIX_LIBS} ${LIBS}"],[AC_MSG_ERROR(dbus messaging support requires the glib 2.0 library -- libglib2.0-dev suggested!)]) ], ) AM_CONDITIONAL([USE_DBUS], [test "x$REQUESTED_DBUS" = "x1"]) @@ -310,16 +307,15 @@ AM_CONDITIONAL([USE_DBUS], [test "x$REQUESTED_DBUS" = "x1"]) AC_ARG_WITH(dbus-test-client, [ --with-dbus-test-client = compile dbus test client], [ AC_MSG_RESULT(>>Including dbus test client) REQUESTED_DBUS_CLIENT=1 - AC_DEFINE([HAVE_DBUS_CLIENT], 1, [Needed by the compiler.]) PKG_CHECK_MODULES([GIO_UNIX], [gio-unix-2.0 >= 2.30.0],[CFLAGS="${GIO_UNIX_CFLAGS} ${CFLAGS}" LIBS="${GIO_UNIX_LIBS} ${LIBS}"],[AC_MSG_ERROR(dbus client support requires the glib 2.0 library -- libglib2.0-dev suggested!)]) ], ) AM_CONDITIONAL([USE_DBUS_CLIENT], [test "x$REQUESTED_DBUS_CLIENT" = "x1"]) # Look for mpris flag -AC_ARG_WITH(mpris-interface, [ --with-mpris-interface = include support for a Shairport Sync D-Bus interface conforming to the MPRIS standard], [ +AC_ARG_WITH(mpris-interface, [ --with-mpris-interface = include support for a D-Bus interface conforming to the MPRIS standard], [ AC_MSG_RESULT(>>Including the MPRIS D-Bus Interface) + AC_DEFINE([CONFIG_MPRIS_INTERFACE], 1, [Include support for a D-Bus interface conforming to the MPRIS standard]) REQUESTED_MPRIS=1 - AC_DEFINE([HAVE_MPRIS], 1, [Needed by the compiler.]) PKG_CHECK_MODULES([GIO_UNIX], [gio-unix-2.0 >= 2.30.0],[CFLAGS="${GIO_UNIX_CFLAGS} ${CFLAGS}" LIBS="${GIO_UNIX_LIBS} ${LIBS}"],[AC_MSG_ERROR(dbus messaging support for mpris requires the glib 2.0 library -- libglib2.0-dev suggested!)]) ], ) AM_CONDITIONAL([USE_MPRIS], [test "x$REQUESTED_MPRIS" = "x1"]) @@ -328,15 +324,13 @@ AM_CONDITIONAL([USE_MPRIS], [test "x$REQUESTED_MPRIS" = "x1"]) AC_ARG_WITH(mpris-test-client, [ --with-mpris-test-client = compile mpris test client], [ AC_MSG_RESULT(>>Including mpris test client) REQUESTED_MPRIS_CLIENT=1 - AC_DEFINE([HAVE_MPRIS_CLIENT], 1, [Needed by the compiler.]) PKG_CHECK_MODULES([GIO_UNIX], [gio-unix-2.0 >= 2.30.0],[CFLAGS="${GIO_UNIX_CFLAGS} ${CFLAGS}" LIBS="${GIO_UNIX_LIBS} ${LIBS}"],[AC_MSG_ERROR(mpris client support requires the glib 2.0 library -- libglib2.0-dev suggested!)]) ], ) AM_CONDITIONAL([USE_MPRIS_CLIENT], [test "x$REQUESTED_MPRIS_CLIENT" = "x1"]) -#AM_CONDITIONAL([USE_DBUS_CORE_AND_DACP], [test "x$REQUESTED_MPRIS" = "x1" -o "x$REQUESTED_DBUS" = "x1"]) - # Look for mqtt flag -AC_ARG_WITH(mqtt-client, [ --with-mqtt-client = include support for the native Shairport Sync MQTT interface], [ +AC_ARG_WITH(mqtt-client, [ --with-mqtt-client = include a client for MQTT -- the Message Queuing Telemetry Transport protocol], [ + AC_DEFINE([CONFIG_MQTT], 1, [Include a client for MQTT, the Message Queuing Telemetry Transport protocol]) AC_MSG_RESULT(>>Including MQTT support) REQUESTED_MQTT=1 AC_CHECK_LIB([mosquitto], [mosquitto_lib_init], , AC_MSG_ERROR(MQTT support requires the mosquitto library -- libmosquitto-dev suggested!)) diff --git a/dbus-service.c b/dbus-service.c index 0a66aed7..c163ddb9 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -420,7 +420,7 @@ gboolean notify_drift_tolerance_callback(ShairportSync *skeleton, gboolean notify_alacdecoder_callback(ShairportSync *skeleton, __attribute__((unused)) gpointer user_data) { char *th = (char *)shairport_sync_get_alacdecoder(skeleton); -#ifdef HAVE_ALAC +#ifdef CONFIG_APPLE_ALAC if (strcasecmp(th, "hammerton") == 0) config.use_apple_decoder = 0; else if (strcasecmp(th, "apple") == 0) @@ -451,7 +451,7 @@ gboolean notify_alacdecoder_callback(ShairportSync *skeleton, gboolean notify_interpolation_callback(ShairportSync *skeleton, __attribute__((unused)) gpointer user_data) { char *th = (char *)shairport_sync_get_interpolation(skeleton); -#ifdef HAVE_LIBSOXR +#ifdef CONFIG_SOXR if (strcasecmp(th, "basic") == 0) config.packet_stuffing = ST_basic; else if (strcasecmp(th, "soxr") == 0) @@ -677,7 +677,7 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name config.loudness_reference_volume_db); shairport_sync_set_drift_tolerance(SHAIRPORT_SYNC(shairportSyncSkeleton), config.tolerance); -#ifdef HAVE_ALAC +#ifdef CONFIG_APPLE_ALAC if (config.use_apple_decoder == 0) { shairport_sync_set_alacdecoder(SHAIRPORT_SYNC(shairportSyncSkeleton), "hammerton"); debug(1, ">> ALACDecoder set to \"hammerton\""); @@ -691,7 +691,7 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name #endif -#ifdef HAVE_LIBSOXR +#ifdef CONFIG_SOXR if (config.packet_stuffing == ST_basic) { shairport_sync_set_interpolation(SHAIRPORT_SYNC(shairportSyncSkeleton), "basic"); debug(1, ">> interpolation set to \"basic\" (soxr support built in)"); diff --git a/mdns.c b/mdns.c index b8d870c6..d25335b1 100644 --- a/mdns.c +++ b/mdns.c @@ -36,7 +36,7 @@ extern mdns_backend mdns_avahi; extern mdns_backend mdns_external_avahi; #endif -#ifdef CONFIG_HAVE_DNS_SD_H +#ifdef CONFIG_DNS_SD extern mdns_backend mdns_dns_sd; extern mdns_backend mdns_external_dns_sd; #endif @@ -49,7 +49,7 @@ static mdns_backend *mdns_backends[] = { &mdns_avahi, &mdns_external_avahi, #endif -#ifdef CONFIG_HAVE_DNS_SD_H +#ifdef CONFIG_DNS_SD &mdns_dns_sd, &mdns_external_dns_sd, #endif diff --git a/metadata_hub.c b/metadata_hub.c index cf19aa85..2b04d388 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -45,15 +45,15 @@ #include "dacp.h" #include "metadata_hub.h" -#ifdef HAVE_LIBMBEDTLS +#ifdef CONFIG_MBEDTLS #include #endif -#ifdef HAVE_LIBPOLARSSL +#ifdef CONFIG_POLARSSL #include #endif -#ifdef HAVE_LIBSSL +#ifdef CONFIG_OPENSSL #include #endif @@ -219,21 +219,21 @@ char *metadata_write_image_file(const char *buf, int len) { uint8_t img_md5[16]; // uint8_t ap_md5[16]; -#ifdef HAVE_LIBSSL +#ifdef CONFIG_SSL MD5_CTX ctx; MD5_Init(&ctx); MD5_Update(&ctx, buf, len); MD5_Final(img_md5, &ctx); #endif -#ifdef HAVE_LIBMBEDTLS +#ifdef CONFIG_MBEDTLS mbedtls_md5_context tctx; mbedtls_md5_starts(&tctx); mbedtls_md5_update(&tctx, (const unsigned char *)buf, len); mbedtls_md5_finish(&tctx, img_md5); #endif -#ifdef HAVE_LIBPOLARSSL +#ifdef CONFIG_POLARSSL md5_context tctx; md5_starts(&tctx); md5_update(&tctx, (const unsigned char *)buf, len); diff --git a/player.c b/player.c index a90ced91..20f01855 100644 --- a/player.c +++ b/player.c @@ -45,21 +45,21 @@ #include "config.h" -#ifdef HAVE_LIBMBEDTLS +#ifdef CONFIG_MBEDTLS #include #include #endif -#ifdef HAVE_LIBPOLARSSL +#ifdef CONFIG_POLARSSL #include #include #endif -#ifdef HAVE_LIBSSL +#ifdef CONFIG_OPENSSL #include #endif -#ifdef HAVE_LIBSOXR +#ifdef CONFIG_SOXR #include #endif @@ -82,7 +82,7 @@ #include "alac.h" -#ifdef HAVE_ALAC +#ifdef CONFIG_APPLE_ALAC #include "apple_alac.h" #endif @@ -260,17 +260,17 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_co unsigned char iv[16]; int aeslen = len & ~0xf; memcpy(iv, conn->stream.aesiv, sizeof(iv)); -#ifdef HAVE_LIBMBEDTLS +#ifdef CONFIG_MBEDTLS mbedtls_aes_crypt_cbc(&conn->dctx, MBEDTLS_AES_DECRYPT, aeslen, iv, buf, packet); #endif -#ifdef HAVE_LIBPOLARSSL +#ifdef CONFIG_POLARSSL aes_crypt_cbc(&conn->dctx, AES_DECRYPT, aeslen, iv, buf, packet); #endif -#ifdef HAVE_LIBSSL +#ifdef CONFIG_OPENSSL AES_cbc_encrypt(buf, packet, aeslen, &conn->aes, iv, AES_DECRYPT); #endif memcpy(packet + aeslen, buf + aeslen, len - aeslen); -#ifdef HAVE_ALAC +#ifdef CONFIG_APPLE_ALAC if (config.use_apple_decoder) { if (conn->decoder_in_use != 1 << decoder_apple_alac) { debug(2, "Apple ALAC Decoder used on encrypted audio."); @@ -289,7 +289,7 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_co } } else { // not encrypted -#ifdef HAVE_ALAC +#ifdef CONFIG_APPLE_ALAC if (config.use_apple_decoder) { if (conn->decoder_in_use != 1 << decoder_apple_alac) { debug(2, "Apple ALAC Decoder used on unencrypted audio."); @@ -431,7 +431,7 @@ static int init_decoder(int32_t fmtp[12], rtsp_conn_info *conn) { alac->setinfo_8a_rate = fmtp[11]; alac_allocate_buffers(alac); // no pthread cancellation point in here -#ifdef HAVE_ALAC +#ifdef CONFIG_APPLE_ALAC apple_alac_init(fmtp); // no pthread cancellation point in here #endif @@ -440,7 +440,7 @@ static int init_decoder(int32_t fmtp[12], rtsp_conn_info *conn) { static void terminate_decoders(rtsp_conn_info *conn) { alac_free(conn->decoder_info); -#ifdef HAVE_ALAC +#ifdef CONFIG_APPLE_ALAC apple_alac_terminate(); #endif } @@ -1320,7 +1320,7 @@ static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l return length + tstuff; } -#ifdef HAVE_LIBSOXR +#ifdef CONFIG_SOXR // this takes an array of signed 32-bit integers and // (a) uses libsoxr to // resample the array to have one more or one less frame, as specified in @@ -1520,17 +1520,17 @@ void *player_thread_func(void *arg) { init_buffer(conn); // will need a corresponding deallocation if (conn->stream.encrypted) { -#ifdef HAVE_LIBMBEDTLS +#ifdef CONFIG_MBEDTLS memset(&conn->dctx, 0, sizeof(mbedtls_aes_context)); mbedtls_aes_setkey_dec(&conn->dctx, conn->stream.aeskey, 128); #endif -#ifdef HAVE_LIBPOLARSSL +#ifdef CONFIG_POLARSSL memset(&conn->dctx, 0, sizeof(aes_context)); aes_setkey_dec(&conn->dctx, conn->stream.aeskey, 128); #endif -#ifdef HAVE_LIBSSL +#ifdef CONFIG_OPENSSL AES_set_decrypt_key(conn->stream.aeskey, 128, &conn->aes); #endif } @@ -2204,7 +2204,7 @@ void *player_thread_func(void *arg) { stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format, conn->outbuf, amount_to_stuff, enable_dither, conn); } -#ifdef HAVE_LIBSOXR +#ifdef CONFIG_SOXR else if (config.packet_stuffing == ST_soxr) { // if (amount_to_stuff) debug(1,"Soxr stuff..."); play_samples = stuff_buffer_soxr_32((int32_t *)conn->tbuf, (int32_t *)conn->sbuf, diff --git a/player.h b/player.h index 3bc415e4..fe7077b1 100644 --- a/player.h +++ b/player.h @@ -7,17 +7,17 @@ #include "config.h" #include "definitions.h" -#ifdef HAVE_LIBMBEDTLS +#ifdef CONFIG_MBEDTLS #include #include #endif -#ifdef HAVE_LIBPOLARSSL +#ifdef CONFIG_POLARSSL #include #include #endif -#ifdef HAVE_LIBSSL +#ifdef CONFIG_OPENSSL #include #endif @@ -140,15 +140,15 @@ typedef struct { uint64_t time_of_last_audio_packet; seq_t ab_read, ab_write; -#ifdef HAVE_LIBMBEDTLS +#ifdef CONFIG_MBEDTLS mbedtls_aes_context dctx; #endif -#ifdef HAVE_LIBPOLARSSL +#ifdef CONFIG_POLARSSL aes_context dctx; #endif -#ifdef HAVE_LIBSSL +#ifdef CONFIG_OPENSSL AES_KEY aes; #endif diff --git a/rtsp.c b/rtsp.c index 7feaaa37..32759d43 100644 --- a/rtsp.c +++ b/rtsp.c @@ -46,15 +46,15 @@ #include "config.h" -#ifdef HAVE_LIBSSL +#ifdef CONFIG_OPENSSL #include #endif -#ifdef HAVE_LIBMBEDTLS +#ifdef CONFIG_MBEDTLS #include #endif -#ifdef HAVE_LIBPOLARSSL +#ifdef CONFIG_POLARSSL #include #endif @@ -67,7 +67,7 @@ #include "metadata_hub.h" #endif -#ifdef HAVE_LIBMOSQUITTO +#ifdef CONFIG_MQTT #include "mqtt.h" #endif @@ -1301,7 +1301,7 @@ void *metadata_thread_function(__attribute__((unused)) void *ignore) { #ifdef CONFIG_METADATA_HUB metadata_hub_process_metadata(pack.type, pack.code, pack.data, pack.length); #endif -#ifdef HAVE_LIBMOSQUITTO +#ifdef CONFIG_MQTT if (config.mqtt_enabled) { mqtt_process_metadata(pack.type, pack.code, pack.data, pack.length); } @@ -1836,7 +1836,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { uint8_t digest_urp[16], digest_mu[16], digest_total[16]; -#ifdef HAVE_LIBSSL +#ifdef CONFIG_OPENSSL MD5_CTX ctx; MD5_Init(&ctx); @@ -1853,7 +1853,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { MD5_Final(digest_mu, &ctx); #endif -#ifdef HAVE_LIBMBEDTLS +#ifdef CONFIG_MBEDTLS mbedtls_md5_context tctx; mbedtls_md5_starts(&tctx); mbedtls_md5_update(&tctx, (const unsigned char *)username, strlen(username)); @@ -1869,7 +1869,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { mbedtls_md5_finish(&tctx, digest_mu); #endif -#ifdef HAVE_LIBPOLARSSL +#ifdef CONFIG_POLARSSL md5_context tctx; md5_starts(&tctx); md5_update(&tctx, (const unsigned char *)username, strlen(username)); @@ -1890,7 +1890,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { for (i = 0; i < 16; i++) snprintf((char *)buf + 2 * i, 3, "%02x", digest_urp[i]); -#ifdef HAVE_LIBSSL +#ifdef CONFIG_OPENSSL MD5_Init(&ctx); MD5_Update(&ctx, buf, 32); MD5_Update(&ctx, ":", 1); @@ -1902,7 +1902,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { MD5_Final(digest_total, &ctx); #endif -#ifdef HAVE_LIBMBEDTLS +#ifdef CONFIG_MBEDTLS mbedtls_md5_starts(&tctx); mbedtls_md5_update(&tctx, buf, 32); mbedtls_md5_update(&tctx, (unsigned char *)":", 1); @@ -1914,7 +1914,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { mbedtls_md5_finish(&tctx, digest_total); #endif -#ifdef HAVE_LIBPOLARSSL +#ifdef CONFIG_POLARSSL md5_starts(&tctx); md5_update(&tctx, buf, 32); md5_update(&tctx, (unsigned char *)":", 1); diff --git a/shairport.c b/shairport.c index 457d1426..5e36e911 100644 --- a/shairport.c +++ b/shairport.c @@ -43,19 +43,19 @@ #include "config.h" -#ifdef HAVE_LIBMBEDTLS +#ifdef CONFIG_MBEDTLS #include #endif -#ifdef HAVE_LIBPOLARSSL +#ifdef CONFIG_POLARSSL #include #endif -#ifdef HAVE_LIBSSL +#ifdef CONFIG_OPENSSL #include #endif -#if defined(HAVE_DBUS) +#if defined(CONFIG_DBUS_INTERFACE) #include #endif @@ -72,15 +72,15 @@ #include "metadata_hub.h" #endif -#ifdef HAVE_DBUS +#ifdef CONFIG_DBUS_INTERFACE #include "dbus-service.h" #endif -#ifdef HAVE_LIBMOSQUITTO +#ifdef CONFIG_MQTT #include "mqtt.h" #endif -#ifdef HAVE_MPRIS +#ifdef CONFIG_MPRIS_INTERFACE #include "mpris-service.h" #endif @@ -434,7 +434,7 @@ int parse_options(int argc, char **argv) { if (strcasecmp(str, "basic") == 0) config.packet_stuffing = ST_basic; else if (strcasecmp(str, "soxr") == 0) -#ifdef HAVE_LIBSOXR +#ifdef CONFIG_SOXR config.packet_stuffing = ST_soxr; #else die("The soxr option not available because this version of shairport-sync was built " @@ -827,7 +827,7 @@ int parse_options(int argc, char **argv) { config_error_file(&config_file_stuff), config_error_text(&config_file_stuff)); } } -#if defined(HAVE_DBUS) +#if defined(CONFIG_DBUS_INTERFACE) /* Get the dbus service sbus setting. */ if (config_lookup_string(config.cfg, "general.dbus_service_bus", &str)) { if (strcasecmp(str, "system") == 0) @@ -840,7 +840,7 @@ int parse_options(int argc, char **argv) { } #endif -#if defined(HAVE_MPRIS) +#if defined(CONFIG_MPRIS_INTERFACE) /* Get the mpris service sbus setting. */ if (config_lookup_string(config.cfg, "general.mpris_service_bus", &str)) { if (strcasecmp(str, "system") == 0) @@ -853,7 +853,7 @@ int parse_options(int argc, char **argv) { } #endif -#ifdef HAVE_LIBMOSQUITTO +#ifdef CONFIG_MQTT int tmpval = 0; config_set_lookup_bool(config.cfg, "mqtt.enabled", &config.mqtt_enabled); if (config.mqtt_enabled && !config.metadata_enabled) { @@ -954,7 +954,7 @@ int parse_options(int argc, char **argv) { if (strcmp(stuffing, "basic") == 0) config.packet_stuffing = ST_basic; else if (strcmp(stuffing, "soxr") == 0) -#ifdef HAVE_LIBSOXR +#ifdef CONFIG_SOXR config.packet_stuffing = ST_soxr; #else die("The soxr option not available because this version of shairport-sync was built " @@ -1007,7 +1007,7 @@ int parse_options(int argc, char **argv) { free(i3); free(vs); -#ifdef HAVE_LIBMOSQUITTO +#ifdef CONFIG_MQTT // mqtt topic was not set. As we have the service name just now, set it if (config.mqtt_topic == NULL) { int topic_length = 1 + strlen(config.service_name) + 1; @@ -1032,7 +1032,7 @@ int parse_options(int argc, char **argv) { return optind + 1; } -#if defined(HAVE_DBUS) || defined(HAVE_MPRIS) +#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE) GMainLoop *g_main_loop; pthread_t dbus_thread; @@ -1124,17 +1124,17 @@ void exit_function() { void main_cleanup_handler(__attribute__((unused)) void *arg) { debug(1, "main cleanup handler called."); -#ifdef HAVE_LIBMOSQUITTO +#ifdef CONFIG_MQTT if (config.mqtt_enabled) { // terminate_mqtt(); } #endif -#if defined(HAVE_DBUS) || defined(HAVE_MPRIS) -#ifdef HAVE_MPRIS +#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE) +#ifdef CONFIG_MPRIS_INTERFACE // stop_mpris_service(); #endif -#ifdef HAVE_DBUS +#ifdef CONFIG_DBUS_INTERFACE stop_dbus_service(); #endif debug(1, "Stopping DBUS Loop Thread"); @@ -1239,7 +1239,7 @@ int main(int argc, char **argv) { config.output_rate = 44100; // default config.decoders_supported = 1 << decoder_hammerton; // David Hammerton's decoder supported by default -#ifdef HAVE_ALAC +#ifdef CONFIG_APPLE_ALAC config.decoders_supported += 1 << decoder_apple_alac; #endif @@ -1570,21 +1570,21 @@ int main(int argc, char **argv) { uint8_t ap_md5[16]; -#ifdef HAVE_LIBSSL +#ifdef CONFIG_OPENSSL MD5_CTX ctx; MD5_Init(&ctx); MD5_Update(&ctx, config.service_name, strlen(config.service_name)); MD5_Final(ap_md5, &ctx); #endif -#ifdef HAVE_LIBMBEDTLS +#ifdef CONFIG_MBEDTLS mbedtls_md5_context tctx; mbedtls_md5_starts(&tctx); mbedtls_md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); mbedtls_md5_finish(&tctx, ap_md5); #endif -#ifdef HAVE_LIBPOLARSSL +#ifdef CONFIG_POLARSSL md5_context tctx; md5_starts(&tctx); md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); @@ -1605,19 +1605,19 @@ int main(int argc, char **argv) { dacp_monitor_start(); #endif -#if defined(HAVE_DBUS) || defined(HAVE_MPRIS) +#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE) // Start up DBUS services after initial settings are all made // debug(1, "Starting up D-Bus services"); pthread_create(&dbus_thread, NULL, &dbus_thread_func, NULL); -#ifdef HAVE_DBUS +#ifdef CONFIG_DBUS_INTERFACE start_dbus_service(); #endif -#ifdef HAVE_MPRIS +#ifdef CONFIG_MPRIS_INTERFACE start_mpris_service(); #endif #endif -#ifdef HAVE_LIBMOSQUITTO +#ifdef CONFIG_MQTT if (config.mqtt_enabled) { initialise_mqtt(); } From 1b18f45facecde24639a4380e98439ee202bd4ec Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 9 Sep 2018 20:43:01 +0100 Subject: [PATCH 120/569] Clang Format Cleanup --- audio.h | 5 +++-- audio_alsa.c | 2 +- common.c | 2 -- common.h | 3 ++- player.c | 22 +++++++++++++--------- 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/audio.h b/audio.h index 171e366b..a4f0f178 100644 --- a/audio.h +++ b/audio.h @@ -24,9 +24,10 @@ typedef struct { // block of samples int (*play)(void *buf, int samples); void (*stop)(void); - + // may be null if no implemented - int (*is_running)(void); // if implemented, will return 0 if everything is okay, non-zero otherwise + int (*is_running)( + void); // if implemented, will return 0 if everything is okay, non-zero otherwise // may be null if not implemented void (*flush)(void); diff --git a/audio_alsa.c b/audio_alsa.c index d0b62532..17b2df4c 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1005,7 +1005,7 @@ static int play(void *buf, int samples) { measurement_time = get_absolute_time_in_fp(); frames_played_at_measurement_time = frames_sent_for_playing - fl; if (frame_index == start_measurement_from_this_frame) { - debug(1,"Start frame counting"); + debug(1, "Start frame counting"); frames_played_at_measurement_start_time = frames_played_at_measurement_time; measurement_start_time = measurement_time; measurement_data_is_valid = 1; diff --git a/common.c b/common.c index f895866c..4a60cfb9 100644 --- a/common.c +++ b/common.c @@ -587,8 +587,6 @@ int config_set_lookup_bool(config_t *cfg, char *where, int *dst) { } } - - void command_set_volume(double volume) { if (config.cmd_set_volume) { /*Spawn a child to run the program.*/ diff --git a/common.h b/common.h index 41844b77..c25b166a 100644 --- a/common.h +++ b/common.h @@ -206,7 +206,8 @@ typedef struct { #ifdef CONFIG_JACK char *jack_client_name, *jack_left_channel_name, *jack_right_channel_name; int jack_auto_client_open_interval; // will try to open a client automatically every second - int jack_auto_client_disconnect; // will disconnect from the server on end of session if set, normally clear. + int jack_auto_client_disconnect; // will disconnect from the server on end of session if set, + // normally clear. #endif } shairport_cfg; diff --git a/player.c b/player.c index 20f01855..9bfc99a7 100644 --- a/player.c +++ b/player.c @@ -835,14 +835,14 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { debug_mutex_unlock(&conn->flush_mutex, 3); } } - + if (config.output->is_running) - if (config.output->is_running() !=0 ) { // if the back end isn't running for any reason - debug(3,"not running"); + if (config.output->is_running() != 0) { // if the back end isn't running for any reason + debug(3, "not running"); debug_mutex_lock(&conn->flush_mutex, 1000, 1); conn->flush_requested = 1; debug_mutex_unlock(&conn->flush_mutex, 3); - } + } debug_mutex_lock(&conn->flush_mutex, 1000, 1); if (conn->flush_requested == 1) { @@ -907,7 +907,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // player int have_sent_prefiller_silence = 1; // set true when we have sent some silent frames to // the DAC - /* + /* int64_t reference_timestamp; uint64_t reference_timestamp_time, remote_reference_timestamp_time; get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, @@ -991,7 +991,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { &should_be_time, conn); conn->first_packet_time_to_play = should_be_time; - + // now, the size of the initial silence must be affected by the lead-in time. // it must be somewhat less than the lead-in time so that dynamic adjustments can be // made @@ -1175,11 +1175,15 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // here, get the time to play the current frame. - if (have_timestamp_timing_information(conn)) { // if we have a reference time + if (have_timestamp_timing_information(conn)) { // if we have a reference time uint64_t time_to_play; - frame_to_local_time(curframe->timestamp + conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate) - config.audio_backend_buffer_desired_length * config.output_rate, &time_to_play, conn); - + frame_to_local_time( + curframe->timestamp + conn->latency * conn->output_sample_ratio + + (int64_t)(config.audio_backend_latency_offset * config.output_rate) - + config.audio_backend_buffer_desired_length * config.output_rate, + &time_to_play, conn); + if (local_time_now >= time_to_play) { do_wait = 0; } From cda70644eece5db19597f8af7dc7af56d29894ac Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 9 Sep 2018 20:43:19 +0100 Subject: [PATCH 121/569] Many changes to compilation and linking flags. Stop using HAVE_* flags except where necessary, use CONFIG_* for optional stuff, use HAS_* for immediate definitions, use USE_* for AM definitions. Probably introduced bgs, sigh. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 8164384f..47861a2b 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.3d8], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d9], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From fedd3ac9a068d9d11d308df1178d7605c72c051f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 9 Sep 2018 20:50:58 +0100 Subject: [PATCH 122/569] Update RELEASENOTES.md --- RELEASENOTES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 72de5bdd..23c65d14 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,7 @@ +Version 3.3d9 +==== +Many changes to compilation and linking flags. Stop using `HAVE_*` flags except where necessary, use `CONFIG_*` for optional stuff, use `HAS_*` for immediate definitions to be used during configuration, use `USE_*` for Automake definitions. Probably introduced bugs, sigh. + Version 3.3d8 ==== **Bug Fix** From dc56b1f19b8e8fe2e07aa85fc0bf34466716aced Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 9 Sep 2018 21:25:09 +0100 Subject: [PATCH 123/569] Remove the --c-generate-autocleanup all -- not present on all systems, it seems --- Makefile.am | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index 1feec23d..e8e4a341 100644 --- a/Makefile.am +++ b/Makefile.am @@ -104,7 +104,7 @@ noinst_HEADERS += $(BUILT_SOURCES) CLEANFILES += $(BUILT_SOURCES) dbus-interface.h dbus-interface.c: org.gnome.ShairportSync.xml - gdbus-codegen --interface-prefix org.gnome --c-generate-autocleanup all --generate-c-code dbus-interface org.gnome.ShairportSync.xml + gdbus-codegen --interface-prefix org.gnome --generate-c-code dbus-interface org.gnome.ShairportSync.xml endif if USE_MPRIS @@ -116,7 +116,7 @@ noinst_HEADERS += $(BUILT_SOURCES) CLEANFILES += $(BUILT_SOURCES) mpris-interface.h mpris-interface.c: org.mpris.MediaPlayer2.xml - gdbus-codegen --interface-prefix org.mpris --c-generate-autocleanup all --generate-c-code mpris-interface org.mpris.MediaPlayer2.xml + gdbus-codegen --interface-prefix org.mpris --generate-c-code mpris-interface org.mpris.MediaPlayer2.xml endif noinst_PROGRAMS = From 947a0ca291d307272b5bc3fe74f3065e035a4fde Mon Sep 17 00:00:00 2001 From: Maciej Wilczynski Date: Sat, 15 Sep 2018 20:05:31 +0200 Subject: [PATCH 124/569] Add PA sink configuration option --- audio_pa.c | 18 ++++++++++++++++-- common.h | 4 +++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/audio_pa.c b/audio_pa.c index d8f8a9d3..aec4d992 100644 --- a/audio_pa.c +++ b/audio_pa.c @@ -85,6 +85,11 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char * if (config_lookup_string(config.cfg, "pa.application_name", &str)) { config.pa_application_name = (char *)str; } + + /* Get the PulseAudio sink name. */ + if (config_lookup_string(config.cfg, "pa.sink", &str)) { + config.pa_sink = (char *)str; + } } // finish collecting settings @@ -178,8 +183,17 @@ static void start(__attribute__((unused)) int sample_rate, // PA_STREAM_AUTO_TIMING_UPDATE; PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY; - // Connect stream to the default audio output sink - if (pa_stream_connect_playback(stream, NULL, &buffer_attr, stream_flags, NULL, NULL) != 0) + int connect_result; + + if (config.pa_sink) { + // Connect stream to the sink specified in the config + connect_result = pa_stream_connect_playback(stream, config.pa_sink, &buffer_attr, stream_flags, NULL, NULL); + } else { + // Connect stream to the default audio output sink + connect_result = pa_stream_connect_playback(stream, NULL, &buffer_attr, stream_flags, NULL, NULL); + } + + if (connect_result != 0) die("could not connect to the pulseaudio playback stream -- the error message is \"%s\".", pa_strerror(pa_context_errno(context))); diff --git a/common.h b/common.h index c25b166a..c5d03aa3 100644 --- a/common.h +++ b/common.h @@ -84,7 +84,9 @@ typedef struct { #ifdef CONFIG_PA char *pa_application_name; // the name under which Shairport Sync shows up as an "Application" in // the Sound Preferences in most desktop Linuxes. -// Defaults to "Shairport Sync". Shairport Sync must be playing to see it. + // Defaults to "Shairport Sync". Shairport Sync must be playing to see it. + + char *pa_sink; // the name (or id) of the sink that Shairport Sync will play on. #endif #ifdef CONFIG_METADATA int metadata_enabled; From 231b77d5439946bfa10b00ec0c075b8ac1043f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wilczy=C5=84ski?= Date: Sat, 15 Sep 2018 20:40:35 +0200 Subject: [PATCH 125/569] Update README.md --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bfd61851..45309fd3 100644 --- a/README.md +++ b/README.md @@ -326,7 +326,15 @@ alsa = }; ``` -The `pa` group is used to specify settings relevant to the PulseAudio backend. You can set the "Application Name" that will appear in the "Sound" control panel. +The `pa` group is used to specify settings relevant to the PulseAudio backend. You can set the "Application Name" that will appear in the "Sound" control panel. You can also set the sink name that audio will be pushed to. Here's an example `pa` group configuration: + +``` +pa = +{ + application_name = "Shairport Sync - Bedroom"; + sink = "server_bedroom"; // Sink name or ID. If not specified, the default sink will be used. +}; +``` Shairport Sync can run programs just before it starts to play an audio stream and just after it finishes. You specify them using the `sessioncontrol` group settings `run_this_before_play_begins` and `run_this_after_play_ends`. This is to facilitate situations where something has to be done before and after playing, e.g. switching on an amplifier beforehand and switching it off afterwards. Set the `wait_for_completion` value to `"yes"` for Shairport Sync to wait until the respective commands have been completed before continuing. From c1d5815c49fde127845d2bc7420d9d7f72a22ce2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 17 Sep 2018 19:14:38 +0100 Subject: [PATCH 126/569] Ignore the latency of whatever is connected to Shairport Sync. It's inappropriate to try to compensate for the latency in the full jack chain. Don't mute when the latency is zero. --- audio_jack.c | 48 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index e27c5884..2f79bee1 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -49,7 +49,7 @@ size_t audio_occupancy; // this is in frames, not bytes. A frame is a left and // right sample, each 16 bits, hence 4 bytes pthread_t *open_client_if_necessary_thread = NULL; -int init(int, char **); +int jack_init(int, char **); void jack_deinit(void); void jack_start(int, int); int play(void *, int); @@ -60,7 +60,7 @@ void jack_flush(void); audio_output audio_jack = {.name = "jack", .help = NULL, - .init = &init, + .init = &jack_init, .deinit = &jack_deinit, .start = &jack_start, .stop = &jack_stop, @@ -80,6 +80,7 @@ long offset = 0; int client_is_open; jack_client_t *client; jack_nframes_t sample_rate; +jack_nframes_t jack_latency; jack_latency_range_t latest_left_latency_range, latest_right_latency_range; int64_t time_of_latest_transfer; @@ -192,6 +193,23 @@ void default_jack_error_callback(const char *desc) { debug(2, "jackd error: \"%s void default_jack_info_callback(const char *desc) { inform("jackd information: \"%s\"", desc); } +void default_jack_set_latency_callback(jack_latency_callback_mode_t mode, + __attribute__((unused)) void *arg) { + if (mode == JackPlaybackLatency) { + jack_latency_range_t left_latency_range, right_latency_range; + jack_port_get_latency_range(left_port, JackPlaybackLatency, &left_latency_range); + jack_port_get_latency_range(right_port, JackPlaybackLatency, &right_latency_range); + + jack_nframes_t b_latency = (left_latency_range.min + left_latency_range.max) / 2; + if (b_latency == 0) + b_latency = (right_latency_range.min + right_latency_range.max) / 2; + // jack_latency = b_latency; // actually, we are not interested in the latency of the jack + // devices connected... + jack_latency = 0; + debug(1, "playback latency callback: %" PRIu32 ".", jack_latency); + } +} + int jack_is_running() { int reply = -1; // meaning jack is not running // if the client is open and initialised, see if the status is "rolling" @@ -203,12 +221,12 @@ int jack_is_running() { jack_port_get_latency_range(left_port, JackPlaybackLatency, &left_latency_range); jack_port_get_latency_range(right_port, JackPlaybackLatency, &right_latency_range); - if ((left_latency_range.min == 0) && (left_latency_range.max == 0) && - (right_latency_range.min == 0) && (right_latency_range.max == 0)) { - reply = -2; // meaning Shairport Sync is not connected - } else { - reply = 0; // meaning jack is open and Shairport Sync is connected to it - } + // if ((left_latency_range.min == 0) && (left_latency_range.max == 0) && + // (right_latency_range.min == 0) && (right_latency_range.max == 0)) { + // reply = -2; // meaning Shairport Sync is not connected + // } else { + reply = 0; // meaning jack is open and Shairport Sync is connected to it + // } } return reply; } @@ -227,11 +245,15 @@ int jack_client_open_if_needed(void) { sample_rate = jack_get_sample_rate(client); // debug(1, "jackaudio sample rate = %" PRId32 ".", sample_rate); if (sample_rate == 44100) { - if (jack_activate(client)) { - debug(1, "jackaudio cannot activate client"); + if (jack_set_latency_callback(client, default_jack_set_latency_callback, NULL) == 0) { + if (jack_activate(client)) { + debug(1, "jackaudio cannot activate client"); + } else { + debug(2, "jackaudio client opened."); + client_is_open = 1; + } } else { - client_is_open = 1; - debug(2, "jackaudio client opened."); + debug(1, "jackaudio cannot set latency callback"); } } else { inform( @@ -276,7 +298,7 @@ void *open_client_if_necessary_thread_function(void *arg) { pthread_exit(NULL); } -int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) { +int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) { config.audio_backend_latency_offset = 0; config.audio_backend_buffer_desired_length = 0.500; config.jack_auto_client_open_interval = 1; // check every second From efe77a886d88a4be3a3dce9e61c2d2ae5ac62bd1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 17 Sep 2018 19:15:14 +0100 Subject: [PATCH 127/569] Ignore the latency of whatever is connected to Shairport Sync. It's inappropriate to try to compensate for the latency in the full jack chain. Don't mute when the latency is zero. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 47861a2b..3b3093ad 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.3d9], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d10], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 682943d513de78d9ed8aeba2d0b0b3f95950cdbe Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 17 Sep 2018 19:34:20 +0100 Subject: [PATCH 128/569] Update RELEASENOTES.md --- RELEASENOTES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 23c65d14..d5d215e8 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,7 @@ +Version 3.3d10 +==== +Modify the `jack` interface to ignore the latencies of any ports connected to the `jack` end. This is because it does not seem appropriate to try to compensate for the delays in the `jack` system. Not sure if this is the correct approach... Maybe it's better to allow the user to select no compensation, compensate for the lowest latency or compensate for the highest latency; it seems unnecessarily complex, not really the business of Shairport Sync. + Version 3.3d9 ==== Many changes to compilation and linking flags. Stop using `HAVE_*` flags except where necessary, use `CONFIG_*` for optional stuff, use `HAS_*` for immediate definitions to be used during configuration, use `USE_*` for Automake definitions. Probably introduced bugs, sigh. From 07c45454cd1bb5686114b880fc1aff349e75f47f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 20 Sep 2018 13:21:51 +0100 Subject: [PATCH 129/569] Improve Makefile.am for FreeBSD -- remove irreleveant compiler flags --- Makefile.am | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index e8e4a341..feaf800b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -6,14 +6,16 @@ bin_PROGRAMS = shairport-sync shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c loudness.c -AM_CFLAGS = -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\" if BUILD_FOR_FREEBSD AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\" -O2 + AM_CFLAGS = -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\" -O2 else if BUILD_FOR_OPENBSD AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\" + AM_CFLAGS = -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\" else AM_CXXFLAGS = -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\" + AM_CFLAGS = -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\" endif endif From fbf8510fb502baf887f1584b5fe375a6daf53eb2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 21 Sep 2018 16:58:38 +0100 Subject: [PATCH 130/569] Update RELEASENOTES.md --- RELEASENOTES.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d5d215e8..e255529b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,12 @@ + +Version 3.3d11 +==== +**Enhancement** +* Add a configuration option to specify the PulseAudio output sink. An extra option for the `pa` backend to allow the specification of the audio sink. Thanks to [Maciej Wilczyński](https://github.com/mLupine) for his work on this. + +**Bug Fix** +* Remove unrecognised options from FreeBSD and OpenBSD complation instructions. + Version 3.3d10 ==== Modify the `jack` interface to ignore the latencies of any ports connected to the `jack` end. This is because it does not seem appropriate to try to compensate for the delays in the `jack` system. Not sure if this is the correct approach... Maybe it's better to allow the user to select no compensation, compensate for the lowest latency or compensate for the highest latency; it seems unnecessarily complex, not really the business of Shairport Sync. From b890fc570ba69f115cd5f8c627ba3c4c2052001f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 21 Sep 2018 16:59:40 +0100 Subject: [PATCH 131/569] Update configure.ac Add configuration option to specify PulseAudio output sink. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 3b3093ad..4390fa7b 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.3d10], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d11], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From c57d25652e6474758594544e8c8b5b6e05a67084 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 21 Sep 2018 17:41:50 +0100 Subject: [PATCH 132/569] Unreference the pa stream when finished with it, so that it the sink can go idle. --- audio_pa.c | 1 + 1 file changed, 1 insertion(+) diff --git a/audio_pa.c b/audio_pa.c index aec4d992..06144c90 100644 --- a/audio_pa.c +++ b/audio_pa.c @@ -291,6 +291,7 @@ static void stop(void) { audio_occupancy = 0; // debug(1, "finish with stream"); + pa_stream_unref(stream); pa_stream_disconnect(stream); } From 6eca829b751f76013b7bb9925b540bcabfe742ce Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 21 Sep 2018 17:48:40 +0100 Subject: [PATCH 133/569] Unreference the pa stream when finished with it, so that it the sink can go idle. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 4390fa7b..fdafef79 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.3d11], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d12], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From ef1fe0a5c0dc153904043ef5efd15d01b39c19e2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 21 Sep 2018 17:51:48 +0100 Subject: [PATCH 134/569] Update RELEASENOTES.md --- RELEASENOTES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e255529b..3a944b8b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,4 +1,9 @@ +Version 3.3d12 +==== +**Bug Fix** +* Unref-erence the `pa` stream when finished with it, so that the sink can go idle -- see [#745](https://github.com/mikebrady/shairport-sync/issues/745). + Version 3.3d11 ==== **Enhancement** From ae97af4ad16b75565d458f702aedf19d3d166695 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 21 Sep 2018 18:18:32 +0100 Subject: [PATCH 135/569] Revert previous change which was to unreference a stream prior to disconnection -- not needed. --- audio_pa.c | 1 - 1 file changed, 1 deletion(-) diff --git a/audio_pa.c b/audio_pa.c index 06144c90..aec4d992 100644 --- a/audio_pa.c +++ b/audio_pa.c @@ -291,7 +291,6 @@ static void stop(void) { audio_occupancy = 0; // debug(1, "finish with stream"); - pa_stream_unref(stream); pa_stream_disconnect(stream); } From aeb1e42c6b309d7dc98ff0f852d792f7a38d9582 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 21 Sep 2018 18:20:23 +0100 Subject: [PATCH 136/569] Revert previous change which was to unreference a stream prior to disconnection -- not needed. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index fdafef79..3b3401d3 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.3d12], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d13], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 3a80f9d495a9e467f9c10aa683b4271cce78f521 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 21 Sep 2018 18:24:45 +0100 Subject: [PATCH 137/569] Update RELEASENOTES.md --- RELEASENOTES.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 3a944b8b..d56b5384 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,8 +1,7 @@ -Version 3.3d12 +Version 3.3d13 ==== -**Bug Fix** -* Unref-erence the `pa` stream when finished with it, so that the sink can go idle -- see [#745](https://github.com/mikebrady/shairport-sync/issues/745). +Reverted to 3.3d11 -- the modification in 3.3d12 was not needed and has been removed. Version 3.3d11 ==== From 6b898b6830045f7510d9a4a399f81fbfe62a3803 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 21 Sep 2018 18:25:04 +0100 Subject: [PATCH 138/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d56b5384..8473d164 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -9,7 +9,7 @@ Version 3.3d11 * Add a configuration option to specify the PulseAudio output sink. An extra option for the `pa` backend to allow the specification of the audio sink. Thanks to [Maciej Wilczyński](https://github.com/mLupine) for his work on this. **Bug Fix** -* Remove unrecognised options from FreeBSD and OpenBSD complation instructions. +* Remove unrecognised options from FreeBSD and OpenBSD compilation instructions. Version 3.3d10 ==== From 539c5011744511c0d33d1615cc088d6481300b53 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 28 Sep 2018 09:33:10 +0100 Subject: [PATCH 139/569] Fix the volume-changed command caller. A space went missing. --- common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.c b/common.c index 4a60cfb9..3ba442d4 100644 --- a/common.c +++ b/common.c @@ -598,7 +598,7 @@ void command_set_volume(double volume) { inform("Couldn't allocate memory for set_volume argument string"); } else { memset(command_buffer, 0, command_buffer_size); - snprintf(command_buffer, command_buffer_size, "%s%f", config.cmd_set_volume, volume); + snprintf(command_buffer, command_buffer_size, "%s %f", config.cmd_set_volume, volume); // debug(1,"command_buffer is \"%s\".",command_buffer); int argC; char **argV; From 98e05be7035b01fb64abb78a707c2291311be737 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 28 Sep 2018 09:33:40 +0100 Subject: [PATCH 140/569] Fix the volume-changed command caller. A space went missing. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 3b3401d3..dcc2cf20 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.3d13], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d14], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From c8dc7c06f6ec3b8c4210b1a3c79177ad5855f1b1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 28 Sep 2018 09:38:37 +0100 Subject: [PATCH 141/569] Update RELEASENOTES.md --- RELEASENOTES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 8473d164..0fd82253 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,4 +1,10 @@ +Version 3.3d14 +==== +**Bug Fix** +* Fix a problem when calling the program to be run when the volume control is changed. Thanks to [shaven](https://github.com/shaven) for the report. + + Version 3.3d13 ==== Reverted to 3.3d11 -- the modification in 3.3d12 was not needed and has been removed. From 31499dcea8d2c82601271fad0bdbfb7470c00eba Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 11 Oct 2018 15:35:17 +0100 Subject: [PATCH 142/569] Allow for a change in the sync time of AirPlay on Mojave and iOS 12. --- rtp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rtp.c b/rtp.c index 253318f2..b8c8ee52 100644 --- a/rtp.c +++ b/rtp.c @@ -332,7 +332,8 @@ void *rtp_control_receiver(void *arg) { uint16_t flags = nctohs(&packet[2]); int64_t la = sync_rtp_timestamp - rtp_timestamp_less_latency; // debug(3, "Latency derived just from the sync packet is %" PRId64 " frames.", la); - if ((flags == 7) || ((conn->AirPlayVersion > 0) && (conn->AirPlayVersion <= 353))) { + + if ((flags == 7) || ((conn->AirPlayVersion > 0) && (conn->AirPlayVersion <= 353)) || ((conn->AirPlayVersion > 0) && (conn->AirPlayVersion >= 371))) { la += config.fixedLatencyOffset; // debug(3, "A fixed latency offset of %d frames has been added, giving a latency of // " From 3d4b1344e8ae56aca2aff30e925a8f99fc7c587f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 11 Oct 2018 15:35:43 +0100 Subject: [PATCH 143/569] Allow for a change in the sync time of AirPlay on Mojave and iOS 12. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index dcc2cf20..79470fd6 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.3d14], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d15], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From f1e19f4d2513c0746f9b405ef4caea19ca688b05 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 11 Oct 2018 15:38:23 +0100 Subject: [PATCH 144/569] Update RELEASENOTES.md --- RELEASENOTES.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0fd82253..fff9c354 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,10 +1,14 @@ +Version 3.3d15 +==== +**Bug Fix** +* Adjust the latency calculation to accommodate changes in iOS 12 and AirPlay connections from macOS Mojave. + Version 3.3d14 ==== **Bug Fix** * Fix a problem when calling the program to be run when the volume control is changed. Thanks to [shaven](https://github.com/shaven) for the report. - Version 3.3d13 ==== Reverted to 3.3d11 -- the modification in 3.3d12 was not needed and has been removed. From 29f7f2c962a179fa4d24c3b8d4fbc5b96581b6c1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 11 Oct 2018 15:39:39 +0100 Subject: [PATCH 145/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index fff9c354..90af5e03 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,7 +2,7 @@ Version 3.3d15 ==== **Bug Fix** -* Adjust the latency calculation to accommodate changes in iOS 12 and AirPlay connections from macOS Mojave. +* Adjust the latency calculation to accommodate changes in iOS 12 and AirPlay connections from macOS Mojave. Thanks to [artenverho](https://github.com/artenverho) who first reported the issue. Version 3.3d14 ==== From 355365691437e760e7b87aef60d68c6aa65419a4 Mon Sep 17 00:00:00 2001 From: Robert Lange Date: Sat, 13 Oct 2018 16:21:39 +0200 Subject: [PATCH 146/569] normalize whitespaces in scripts/shairport-sync.conf --- scripts/shairport-sync.conf | 100 ++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index b63eb229..4697a6a3 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -29,9 +29,9 @@ general = // volume_control_profile = "standard" ; // use this advanced setting to specify how the airplay volume is transferred to the mixer volume. // "standard" makes the volume change more quickly at lower volumes and slower at higher volumes. // "flat" makes the volume change at the same rate at all volumes. -// run_this_when_volume_is_set = "/full/path/to/application/and/args"; // Run the specified application whenever the volume control is set or changed. -// The desired AirPlay volume is appended to the end of the command line – leave a space if you want it treated as an extra argument. -// AirPlay volume goes from 0 to -30 and -144 means "mute". +// run_this_when_volume_is_set = "/full/path/to/application/and/args"; // Run the specified application whenever the volume control is set or changed. +// The desired AirPlay volume is appended to the end of the command line – leave a space if you want it treated as an extra argument. +// AirPlay volume goes from 0 to -30 and -144 means "mute". // regtype = "_raop._tcp"; // Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp". // playback_mode = "stereo"; // This can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo". @@ -39,17 +39,17 @@ general = // the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple. // interface = "name"; // Use this advanced setting to specify the interface on which Shairport Sync should provide its service. Leave it commented out to get the default, which is to select the interface(s) automatically. -// audio_backend_latency_offset_in_seconds = 0.0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1. -// audio_backend_buffer_desired_length_in_seconds = 0.15; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response time to volume changes becomes annoying. Default is 0.15 seconds in the alsa backend, 0.35 seconds in the pa backend and 1.0 seconds otherwise. -// audio_backend_silent_lead_in_time = 2.0; // This optional advanced setting, from 0.0 and 4.0 seconds, sets the length of the period of silence that precedes the start of the audio. The default is the latency, usually 2.0 seconds. Values greater than the latency are ignored. Values that are too low will affect initial synchronisation. -// dbus_service_bus = "system"; // The Shairport Sync dbus interface, if selected at compilation, will appear -// as "org.gnome.ShairportSync" on the whichever bus you specify here: "system" (default) or "session". -// mpris_service_bus = "system"; // The Shairport Sync mpris interface, if selected at compilation, will appear -// as "org.gnome.ShairportSync" on the whichever bus you specify here: "system" (default) or "session". +// audio_backend_latency_offset_in_seconds = 0.0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1. +// audio_backend_buffer_desired_length_in_seconds = 0.15; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response time to volume changes becomes annoying. Default is 0.15 seconds in the alsa backend, 0.35 seconds in the pa backend and 1.0 seconds otherwise. +// audio_backend_silent_lead_in_time = 2.0; // This optional advanced setting, from 0.0 and 4.0 seconds, sets the length of the period of silence that precedes the start of the audio. The default is the latency, usually 2.0 seconds. Values greater than the latency are ignored. Values that are too low will affect initial synchronisation. +// dbus_service_bus = "system"; // The Shairport Sync dbus interface, if selected at compilation, will appear +// as "org.gnome.ShairportSync" on the whichever bus you specify here: "system" (default) or "session". +// mpris_service_bus = "system"; // The Shairport Sync mpris interface, if selected at compilation, will appear +// as "org.gnome.ShairportSync" on the whichever bus you specify here: "system" (default) or "session". }; // Advanced parameters for controlling how Shairport Sync runs a play session -sessioncontrol = +sessioncontrol = { // run_this_before_play_begins = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line // run_this_after_play_ends = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line @@ -63,82 +63,82 @@ sessioncontrol = // These are parameters for the "alsa" audio back end. alsa = { -// output_device = "default"; // the name of the alsa output device. Use "alsamixer" or "aplay" to find out the names of devices, mixers, etc. -// mixer_control_name = "PCM"; // the name of the mixer to use to adjust output volume. If not specified, volume in adjusted in software. -// mixer_device = "default"; // the mixer_device default is whatever the output_device is. Normally you wouldn't have to use this. -// output_rate = 44100; // can be 44100, 88200, 176400 or 352800, but the device must have the capability. -// output_format = "S16"; // can be "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32", but the device must have the capability. Except where stated using (*LE or *BE), endianness matches that of the processor. -// disable_synchronization = "no"; // Set to "yes" to disable synchronization. Default is "no". -// period_size = ; // Use this optional advanced setting to set the alsa period size near to this value -// buffer_size = ; // Use this optional advanced setting to set the alsa buffer size near to this value -// use_mmap_if_available = "yes"; // Use this optional advanced setting to control whether MMAP-based output is used to communicate with the DAC. Default is "yes" -// use_hardware_mute_if_available = "no"; // Use this optional advanced setting to control whether the hardware in the DAC is used for muting. Default is "no", for compatibility with other audio players. +// output_device = "default"; // the name of the alsa output device. Use "alsamixer" or "aplay" to find out the names of devices, mixers, etc. +// mixer_control_name = "PCM"; // the name of the mixer to use to adjust output volume. If not specified, volume in adjusted in software. +// mixer_device = "default"; // the mixer_device default is whatever the output_device is. Normally you wouldn't have to use this. +// output_rate = 44100; // can be 44100, 88200, 176400 or 352800, but the device must have the capability. +// output_format = "S16"; // can be "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32", but the device must have the capability. Except where stated using (*LE or *BE), endianness matches that of the processor. +// disable_synchronization = "no"; // Set to "yes" to disable synchronization. Default is "no". +// period_size = ; // Use this optional advanced setting to set the alsa period size near to this value +// buffer_size = ; // Use this optional advanced setting to set the alsa buffer size near to this value +// use_mmap_if_available = "yes"; // Use this optional advanced setting to control whether MMAP-based output is used to communicate with the DAC. Default is "yes" +// use_hardware_mute_if_available = "no"; // Use this optional advanced setting to control whether the hardware in the DAC is used for muting. Default is "no", for compatibility with other audio players. }; // Parameters for the "sndio" audio back end. All are optional. sndio = { -// device = "snd/0"; // optional setting to set the name of the output device. Default is the sndio system default. -// rate = 44100; // optional setting which can be 44100, 88200, 176400 or 352800, but the device must have the capability. Default is 44100. -// format = "S16"; // optional setting which can be "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32", but the device must have the capability. Except where stated using (*LE or *BE), endianness matches that of the processor. -// round = ; // advanced optional setting to set the period size near to this value -// bufsz = ; // advanced optional setting to set the buffer size near to this value +// device = "snd/0"; // optional setting to set the name of the output device. Default is the sndio system default. +// rate = 44100; // optional setting which can be 44100, 88200, 176400 or 352800, but the device must have the capability. Default is 44100. +// format = "S16"; // optional setting which can be "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32", but the device must have the capability. Except where stated using (*LE or *BE), endianness matches that of the processor. +// round = ; // advanced optional setting to set the period size near to this value +// bufsz = ; // advanced optional setting to set the buffer size near to this value }; // Parameters for the "pa" PulseAudio backend. pa = { -// application_name = "Shairport Sync"; //Set this to the name that should appear in the Sounds "Applications" tab when Shairport Sync is active. +// application_name = "Shairport Sync"; //Set this to the name that should appear in the Sounds "Applications" tab when Shairport Sync is active. }; // Parameters for the "jack" JACK Audio Connection Kit backend. jack = { -// client_name = "Shairport Sync"; //Set this to the name of the client that should appear in "Connections" when Shairport Sync is active. -// left_channel_name = "left"; //Set this to the name of the left output port that should appear in "Connections" when Shairport Sync is active. -// right_channel_name = "right"; //Set this to the name of the right output port that should appear in "Connections" when Shairport Sync is active. -// auto_client_open_interval = 1; // Set this to the interval in seconds between automatic attempts to open a client on the jack server. Set to zero to disable this behaviour. -// auto_client_disconnect = "no"; // Set this to "yes" to close the jack client automatically at then end of a play session. Default is no -- the client stays connected. +// client_name = "Shairport Sync"; //Set this to the name of the client that should appear in "Connections" when Shairport Sync is active. +// left_channel_name = "left"; //Set this to the name of the left output port that should appear in "Connections" when Shairport Sync is active. +// right_channel_name = "right"; //Set this to the name of the right output port that should appear in "Connections" when Shairport Sync is active. +// auto_client_open_interval = 1; // Set this to the interval in seconds between automatic attempts to open a client on the jack server. Set to zero to disable this behaviour. +// auto_client_disconnect = "no"; // Set this to "yes" to close the jack client automatically at then end of a play session. Default is no -- the client stays connected. }; // Parameters for the "pipe" audio back end, a back end that directs raw CD-style audio output to a pipe. No interpolation is done. pipe = { -// name = "/path/to/pipe"; // there is no default pipe name for the output +// name = "/path/to/pipe"; // there is no default pipe name for the output }; // These are no configuration file parameters for the "stdout" audio back end. No interpolation is done. // These are no configuration file parameters for the "ao" audio back end. No interpolation is done. -// Static latency settings are deprecated and the settings have been removed. +// Static latency settings are deprecated and the settings have been removed. dsp = { ////////////////////////////////////////// -// This convolution filter can be used to apply almost any correction to the audio signal, like frequency and phase correction. -// For example you could measure (with a good microphone and a sweep-sine) the frequency response of your speakers + room, -// and apply a correction to get a flat response curve. +// This convolution filter can be used to apply almost any correction to the audio signal, like frequency and phase correction. +// For example you could measure (with a good microphone and a sweep-sine) the frequency response of your speakers + room, +// and apply a correction to get a flat response curve. ////////////////////////////////////////// // -// convolution = "yes"; // Activate the convolution filter. -// convolution_ir_file = "impulse.wav"; // Impulse Response file to be convolved to the audio stream -// convolution_gain = -4.0; // Static gain applied to prevent clipping during the convolution process -// convolution_max_length = 44100; // Truncate the input file to this length in order to save CPU. +// convolution = "yes"; // Activate the convolution filter. +// convolution_ir_file = "impulse.wav"; // Impulse Response file to be convolved to the audio stream +// convolution_gain = -4.0; // Static gain applied to prevent clipping during the convolution process +// convolution_max_length = 44100; // Truncate the input file to this length in order to save CPU. ////////////////////////////////////////// -// This loudness filter is used to compensate for human ear non linearity. -// When the volume decreases, our ears loose more sentisitivity in the low range frequencies than in the mid range ones. -// This filter aims at compensating for this loss, applying a variable gain to low frequencies depending on the volume. -// More info can be found here: https://en.wikipedia.org/wiki/Equal-loudness_contour -// For this filter to work properly, you should disable (or set to a fix value) all other volume control and only let shairport-sync control your volume. -// The setting "loudness_reference_volume_db" should be set at the volume reported by shairport-sync when listening to music at a normal listening volume. +// This loudness filter is used to compensate for human ear non linearity. +// When the volume decreases, our ears loose more sentisitivity in the low range frequencies than in the mid range ones. +// This filter aims at compensating for this loss, applying a variable gain to low frequencies depending on the volume. +// More info can be found here: https://en.wikipedia.org/wiki/Equal-loudness_contour +// For this filter to work properly, you should disable (or set to a fix value) all other volume control and only let shairport-sync control your volume. +// The setting "loudness_reference_volume_db" should be set at the volume reported by shairport-sync when listening to music at a normal listening volume. ////////////////////////////////////////// // -// loudness = "yes"; // Activate the filter -// loudness_reference_volume_db = -20.0; // Above this level the filter will have no effect anymore. Below this level it will gradually boost the low frequencies. +// loudness = "yes"; // Activate the filter +// loudness_reference_volume_db = -20.0; // Above this level the filter will have no effect anymore. Below this level it will gradually boost the low frequencies. }; @@ -155,7 +155,7 @@ metadata = }; // How to enable the MQTT-metadata/remote-service -mqtt = +mqtt = { // enabled = "no"; // set this to yes to enable the mqtt-metadata-service // hostname = "iot.eclipse.org"; // Hostname of the MQTT Broker From ed4f439910f573e81ca71d4052cda751e5093639 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 13 Oct 2018 15:23:14 +0000 Subject: [PATCH 147/569] Add a debug message... --- player.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/player.c b/player.c index 9bfc99a7..976943c8 100644 --- a/player.c +++ b/player.c @@ -1098,6 +1098,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // debug(1,"Frames to start: %llu, DAC delay %d, buffer: %d // packets.",exact_frame_gap,dac_delay,seq_diff(conn->ab_read, // conn->ab_write, conn->ab_read)); + + //debug (1,"Play %" PRId64 " frames of silence.") config.output->play(silence, fs); free(silence); } From 905e4085c4a0ad93b2dca811df581e2de7f9a9ef Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 14 Oct 2018 14:33:24 +0100 Subject: [PATCH 148/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 90af5e03..7dd74ca8 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,7 +1,7 @@ Version 3.3d15 ==== -**Bug Fix** +**Enhancement** * Adjust the latency calculation to accommodate changes in iOS 12 and AirPlay connections from macOS Mojave. Thanks to [artenverho](https://github.com/artenverho) who first reported the issue. Version 3.3d14 From c2d1da2e5c24221025bcfa3c91e10c34e0a19a87 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 14 Oct 2018 15:15:15 +0100 Subject: [PATCH 149/569] Generate error messages if there is a problem with getting the delay, and pay attention to it in the player thread. --- audio_alsa.c | 22 ++++---- player.c | 153 ++++++++++++++++++++++++++------------------------- 2 files changed, 89 insertions(+), 86 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 17b2df4c..5dc4d32f 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -876,7 +876,8 @@ int delay(long *the_delay) { } else { pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); int derr; - if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING) { + snd_pcm_state_t dac_state = snd_pcm_state(alsa_handle); + if (dac_state == SND_PCM_STATE_RUNNING) { *the_delay = 0; // just to see what happens reply = snd_pcm_delay(alsa_handle, the_delay); if (reply != 0) { @@ -893,19 +894,18 @@ int delay(long *the_delay) { } } } else { + reply = -EIO; // shomething is wrong frame_index = 0; // we'll be starting over... measurement_data_is_valid = 0; - if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) { - *the_delay = 0; - reply = 0; // no error + if (dac_state == SND_PCM_STATE_PREPARED) { + debug(1,"delay not available -- state is SND_PCM_STATE_PREPARED"); } else { - if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) { - *the_delay = 0; - reply = 0; // no error + if (dac_state == SND_PCM_STATE_XRUN) { + debug(1,"delay not available -- state is SND_PCM_STATE_XRUN"); } else { - reply = -EIO; - debug(1, "Error -- ALSA delay(): bad state: %d.", snd_pcm_state(alsa_handle)); + + debug(1, "Error -- ALSA delay(): bad state: %d.", dac_state); } if ((derr = snd_pcm_prepare(alsa_handle))) { snd_pcm_recover(alsa_handle, derr, 1); @@ -917,7 +917,7 @@ int delay(long *the_delay) { pthread_cleanup_pop(0); // here, occasionally pretend there's a problem with pcm_get_delay() // if ((random() % 100000) < 3) // keep it pretty rare - // reply = -EPERM; // pretend something bad has happened + // reply = -EIO; // pretend something bad has happened return reply; } } @@ -1005,7 +1005,7 @@ static int play(void *buf, int samples) { measurement_time = get_absolute_time_in_fp(); frames_played_at_measurement_time = frames_sent_for_playing - fl; if (frame_index == start_measurement_from_this_frame) { - debug(1, "Start frame counting"); + // debug(1, "Start frame counting"); frames_played_at_measurement_start_time = frames_played_at_measurement_time; measurement_start_time = measurement_time; measurement_data_is_valid = 1; diff --git a/player.c b/player.c index 976943c8..8af27c30 100644 --- a/player.c +++ b/player.c @@ -1000,9 +1000,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { int64_t max_dac_delay = config.output_rate / 10; // so the lead-in time must be greater // than this, say 0.2 sec, to allow for - // dynamic adjustment - int64_t filler_size = max_dac_delay; // 0.1 second -- the maximum we'll add to the DAC - + // dynamic adjustment + int64_t filler_size = max_dac_delay; + if (local_time_now >= conn->first_packet_time_to_play) { // debug(1,"Gone past starting time"); have_sent_prefiller_silence = 1; @@ -1025,87 +1025,90 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { } else { // do some calculations int64_t lead_time = conn->first_packet_time_to_play - local_time_now; - int64_t lead_in_time = + // an audio_backend_silent_lead_in_time of less than zero means start filling ASAP + int64_t lead_in_time = -1; + if (config.audio_backend_silent_lead_in_time>=0) + lead_in_time = (int64_t)(config.audio_backend_silent_lead_in_time * (int64_t)0x100000000); // debug(1,"Lead time is %llx at fpttp // %llx.",lead_time,conn->first_packet_time_to_play); - // an audio_backend_silent_lead_in_time of less than zero means start filling ASAP if ((lead_in_time < 0) || (lead_time <= lead_in_time)) { + // debug(1,"Lead time is %" PRIx64 ", lead-in time is %" PRIx64 " at fpttp %llx.",lead_time,conn->first_packet_time_to_play); + // debug(1,"Checking"); if (config.output->delay) { // conn->first_packet_time_to_play is definitely later than local_time_now - if (have_sent_prefiller_silence != 0) { - int resp = config.output->delay(&dac_delay); - - if (resp != 0) { - debug(1, "Error %d getting dac_delay in buffer_get_frame.", resp); - dac_delay = 0; - } - } else { - dac_delay = 0; - } - int64_t gross_frame_gap = - ((conn->first_packet_time_to_play - local_time_now) * config.output_rate) >> - 32; - int64_t exact_frame_gap = gross_frame_gap - dac_delay; - if (exact_frame_gap < 0) { - // we've gone past the time... - // debug(1,"Run past time."); - // debug(1,"Run a bit past the exact start time by %lld frames, with time now of - // %llx, fpttp of %llx and dac_delay of %d and %d packets; - // flush.",-exact_frame_gap,tn,conn->first_packet_time_to_play,dac_delay,seq_diff(ab_read, - // ab_write)); - if (config.output->flush) - config.output->flush(); - ab_resync(conn); - conn->first_packet_timestamp = 0; - conn->first_packet_time_to_play = 0; - } else { - int64_t fs = filler_size; - if (fs > (max_dac_delay - dac_delay)) - fs = max_dac_delay - dac_delay; - if (fs < 0) { - debug(2, - "frame size (fs) < 0 with max_dac_delay of %lld and dac_delay of %ld", - max_dac_delay, dac_delay); - fs = 0; - } - if ((exact_frame_gap <= fs) || - (exact_frame_gap <= conn->max_frames_per_packet * 2)) { - fs = exact_frame_gap; - // debug(1,"Exact frame gap is %llu; play %d frames of silence. Dac_delay is - // %d, - // with %d packets, ab_read is %04x, ab_write is - // %04x.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read, - // ab_write),ab_read,ab_write); - conn->ab_buffering = 0; - } - void *silence; - // if (fs==0) - // debug(2,"Zero length silence buffer needed with gross_frame_gap of %lld and - // dac_delay of %lld.",gross_frame_gap,dac_delay); - // the fs (number of frames of silence to play) can be zero in the DAC doesn't - // start - // outputting frames for a while -- it could get loaded up but not start - // responding - // for many milliseconds. - 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 { - memset(silence, 0, conn->output_bytes_per_frame * fs); - // debug(1,"Frames to start: %llu, DAC delay %d, buffer: %d - // packets.",exact_frame_gap,dac_delay,seq_diff(conn->ab_read, - // conn->ab_write, conn->ab_read)); - - //debug (1,"Play %" PRId64 " frames of silence.") - config.output->play(silence, fs); - free(silence); + int resp = 0; + dac_delay = 0; + if (have_sent_prefiller_silence != 0) + resp = config.output->delay(&dac_delay); + if (resp == 0) { + int64_t gross_frame_gap = + ((conn->first_packet_time_to_play - local_time_now) * config.output_rate) >> + 32; + int64_t exact_frame_gap = gross_frame_gap - dac_delay; + // debug(1,"Exact and gross frame gaps are %" PRId64 " and %" PRId64 " frames, and the dac delay is %ld.", exact_frame_gap, gross_frame_gap, dac_delay); + if (exact_frame_gap < 0) { + // we've gone past the time... + // debug(1,"Run past time."); + + // this might happen if a big clock adjustment was made at just the wrong time. + + debug(1,"Run a bit past the exact start time by %" PRId64 " frames.",-exact_frame_gap); + if (config.output->flush) + config.output->flush(); + ab_resync(conn); + conn->first_packet_timestamp = 0; + conn->first_packet_time_to_play = 0; + } else { + int64_t fs = filler_size; + if (fs > (max_dac_delay - dac_delay)) + fs = max_dac_delay - dac_delay; + if (fs < 0) { + // this could happen if the dac delay mysteriously grows between samples, which could happen in a transition between having no interpolation and having interpolated buffer numbers. + debug(2, + "frame size (fs) < 0 with max_dac_delay of %lld and dac_delay of %ld", + max_dac_delay, dac_delay); + fs = 0; } + if ((exact_frame_gap <= fs) || + (exact_frame_gap <= conn->max_frames_per_packet * 2)) { + fs = exact_frame_gap; + // debug(1,"Exact frame gap is %llu; play %d frames of silence. Dac_delay is + // %d, + // with %d packets, ab_read is %04x, ab_write is + // %04x.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read, + // ab_write),ab_read,ab_write); + conn->ab_buffering = 0; + } + void *silence; + // if (fs==0) + // debug(2,"Zero length silence buffer needed with gross_frame_gap of %lld and + // dac_delay of %lld.",gross_frame_gap,dac_delay); + // the fs (number of frames of silence to play) can be zero in the DAC doesn't + // start + // outputting frames for a while -- it could get loaded up but not start + // responding + // for many milliseconds. + 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 { + memset(silence, 0, conn->output_bytes_per_frame * fs); + // debug(1,"Frames to start: %llu, DAC delay %d, buffer: %d + // packets.",exact_frame_gap,dac_delay,seq_diff(conn->ab_read, + // conn->ab_write, conn->ab_read)); + config.output->play(silence, fs); + debug(1,"Sent %" PRId64 " frames of silence",fs); + free(silence); + } + } + have_sent_prefiller_silence = + 1; // even if we haven't sent silence because it's zero frames long... } - have_sent_prefiller_silence = - 1; // even if we haven't sent silence because it's zero frames long... + } else { + // debug(1, "Unable to get dac delay."); } } else { // no delay function on back end -- just send the prefiller silence From 72fa29b6459c178f7d49317c01aa1c714313f9eb Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 14 Oct 2018 15:15:45 +0100 Subject: [PATCH 150/569] Allow a latency offset of +/- 1.75 seconds. --- audio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/audio.c b/audio.c index 382d46d2..a0b5f74d 100644 --- a/audio.c +++ b/audio.c @@ -171,9 +171,9 @@ void parse_general_audio_options(void) { /* Get the latency offset in seconds. */ if (config_lookup_float(config.cfg, "general.audio_backend_latency_offset_in_seconds", &dvalue)) { - if ((dvalue < -1.0) || (dvalue > 1.5)) { + if ((dvalue < -1.75) || (dvalue > 1.75)) { die("Invalid audio_backend_latency_offset_in_seconds \"%f\". It " - "should be between -1.0 and +1.5, default is 0 seconds", + "should be between -1.75 and +1.75, default is 0 seconds", dvalue); } else { config.audio_backend_latency_offset = dvalue; From b122763a9be3c193ccbce2ffd51f8a7f593eaeff Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 14 Oct 2018 15:28:51 +0100 Subject: [PATCH 151/569] quieten a debug message --- player.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player.c b/player.c index 8af27c30..457cd702 100644 --- a/player.c +++ b/player.c @@ -1100,7 +1100,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // packets.",exact_frame_gap,dac_delay,seq_diff(conn->ab_read, // conn->ab_write, conn->ab_read)); config.output->play(silence, fs); - debug(1,"Sent %" PRId64 " frames of silence",fs); + // debug(1,"Sent %" PRId64 " frames of silence",fs); free(silence); } } From 7462e141445369596312da9aa1ef81799681da86 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 14 Oct 2018 15:30:26 +0100 Subject: [PATCH 152/569] Take more notice of errors requesing delay times. Extend the latency offset range to +/- 1.75, but at the programmer's risk. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 79470fd6..cfda987c 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.3d15], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d16], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 5e16fdbbf9e6a73407cf750f6791c43c0d9cd971 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 14 Oct 2018 15:37:17 +0100 Subject: [PATCH 153/569] Update RELEASENOTES.md --- RELEASENOTES.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7dd74ca8..316282d9 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,4 +1,13 @@ +Version 3.3d16 +==== +**Enhancement** +* Extend the range of `audio_backend_latency_offset_in_seconds` to ± 1.75 seconds. Note that no sanity checking of any kind is done on this – if it is too large the program may simply crash. + +* Pay more attention to errors that may occur when asking for the DAC delay just before play starts. For diagnostic use. + +* Formatting of the settings file `shairport-sync.conf` has been fixed, thanks to the work of [roblan](https://github.com/roblan). + Version 3.3d15 ==== **Enhancement** From de071aeffc16d0d94b5c70741d55dfd9ff54edfb Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 18 Oct 2018 17:44:50 +0100 Subject: [PATCH 154/569] Try to fix modulo calculations --- common.h | 4 +- player.c | 192 +++++++++++++++++++++++++------------------------------ player.h | 22 +++---- rtp.c | 40 ++++++------ rtp.h | 2 +- 5 files changed, 119 insertions(+), 141 deletions(-) diff --git a/common.h b/common.h index c5d03aa3..0042d2c4 100644 --- a/common.h +++ b/common.h @@ -134,8 +134,8 @@ typedef struct { char *mdns_name; mdns_backend *mdns; int buffer_start_fill; - int64_t userSuppliedLatency; // overrides all other latencies -- use with caution - int64_t fixedLatencyOffset; // add this to all automatic latencies supplied to get the actual + uint32_t userSuppliedLatency; // overrides all other latencies -- use with caution + uint32_t fixedLatencyOffset; // add this to all automatic latencies supplied to get the actual // total latency // the total latency will be limited to the min and max-latency values, if supplied int daemonise; diff --git a/player.c b/player.c index 457cd702..235b03ab 100644 --- a/player.c +++ b/player.c @@ -99,60 +99,15 @@ // static abuf_t audio_buffer[BUFFER_FRAMES]; #define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES) -void do_flush(int64_t timestamp, rtsp_conn_info *conn); - -// make timestamps and seqnos definitely monotonic - -// add an epoch to the timestamp. The monotonic timestamp guaranteed to start between 2^32 and 2^33 -// frames and continue up to 2^63-1 frames -// if should never get into the negative range -// which is about 2*10^8 * 1,000 seconds at 384,000 frames per second -- about 2 trillion seconds or -// over 50,000 years. -// also, it won't reach zero until then, if ever, so we can safely say that a null monotonic -// timestamp can mean something special -int64_t monotonic_timestamp(uint32_t timestamp, rtsp_conn_info *conn) { - int64_t previous_value; - int64_t return_value; - if (conn->timestamp_epoch == 0) { - if (timestamp > conn->maximum_timestamp_interval) - conn->timestamp_epoch = 1; - else - conn->timestamp_epoch = 2; - previous_value = conn->timestamp_epoch; - previous_value <<= 32; - previous_value += timestamp; - } else { - previous_value = conn->timestamp_epoch; - previous_value <<= 32; - previous_value += conn->last_timestamp; - if (timestamp < conn->last_timestamp) { - // the incoming timestamp is less than the last one. - // if the difference is more than a minute, assume it's really from the next epoch - if ((conn->last_timestamp - timestamp) > conn->maximum_timestamp_interval) - conn->timestamp_epoch++; - } else { - // the incoming timestamp is greater than the last one. - // if the difference is more than a minute, assume it's really from the previous epoch - if ((timestamp - conn->last_timestamp) > conn->maximum_timestamp_interval) - conn->timestamp_epoch--; - } - } - return_value = conn->timestamp_epoch; - return_value <<= 32; - return_value += timestamp; - if (previous_value > return_value) { - if ((previous_value - return_value) > conn->maximum_timestamp_interval) - debug(2, "interval between successive rtptimes greater than allowed!"); - } else { - if ((return_value - previous_value) > conn->maximum_timestamp_interval) - debug(2, "interval between successive rtptimes greater than allowed!"); - } - if (return_value < 0) - debug(1, "monotonic rtptime is negative!"); - conn->last_timestamp = timestamp; - return return_value; +uint32_t rtp_frame_offset(uint32_t base, uint32_t frame_in_question) { + if (base <= frame_in_question) + return frame_in_question - base; + else + return UINT32_MAX - base + frame_in_question + 1; } +void do_flush(uint32_t timestamp, rtsp_conn_info *conn); + static void ab_resync(rtsp_conn_info *conn) { int i; for (i = 0; i < BUFFER_FRAMES; i++) { @@ -165,11 +120,36 @@ static void ab_resync(rtsp_conn_info *conn) { conn->ab_buffering = 1; } -// the sequence number is a 16-bit unsigned number which wraps pretty often -// to work out if one seqno is 'after' another therefore depends whether wrap has occurred -// this function works out the actual ordinate of the seqno, i.e. the distance up from -// the zeroth element, at ab_read, taking due account of wrap. +// given starting and ending points as unsigned 32-bit integers running modulo 2^32, returns the position of x in the interval in *pos +// returns true if x is actually within the buffer +int position_in_modulo_uint32_t_buffer(uint32_t x, uint32_t start, uint32_t end, uint32_t *pos ) { + int response = 0; // not in the buffer + if (start <= end ) { + if (x < start) { + if (pos) + *pos = UINT32_MAX - start + 1 + x; + } else { + if (pos) + *pos = x - start; + if (x < end) + response = 1; + } + } else if ((x >= start) && (x <= UINT32_MAX)) { + response = 1; + if (pos) + *pos = x - start; + } else { + if (pos) + *pos = UINT32_MAX - start + 1 + x; + if (x < end) { + response = 1; + } + } + return response; +} + +// this is used. static inline seq_t SUCCESSOR(seq_t x) { uint32_t p = x & 0xffff; p += 1; @@ -177,6 +157,7 @@ static inline seq_t SUCCESSOR(seq_t x) { return p; } +// this is not used static inline seq_t PREDECESSOR(seq_t x) { uint32_t p = (x & 0xffff) + 0x10000; p -= 1; @@ -184,6 +165,8 @@ static inline seq_t PREDECESSOR(seq_t x) { return p; } +// used in seq_diff and seq_order + // anything with ORDINATE in it must be proctected by the ab_mutex static inline int32_t ORDINATE(seq_t x, seq_t base) { int32_t p = x; // int32_t from seq_t, i.e. uint16_t, so okay @@ -458,14 +441,8 @@ static void free_audio_buffers(rtsp_conn_info *conn) { free(conn->audio_buffer[i].data); } -void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp, uint8_t *data, +void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, int len, rtsp_conn_info *conn) { - // all timestamps are done at the output rate - // the "actual_timestamp" is the one that comes in the packet, and is carried over for - // debugging - // and checking only. - - int64_t ltimestamp = timestamp * conn->output_sample_ratio; // ignore a request to flush that has been made before the first packet... if (conn->packet_count == 0) { @@ -483,22 +460,26 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp // if (flush_rtp_timestamp != 0) // debug(1,"Flush_rtp_timestamp is %u",flush_rtp_timestamp); - - if ((conn->flush_rtp_timestamp != 0) && (ltimestamp <= conn->flush_rtp_timestamp)) { - debug(3, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %" PRId64 - ", flushing to " - "timestamp: %" PRId64 ".", - seqno, ltimestamp, conn->flush_rtp_timestamp); - conn->initial_reference_time = 0; - conn->initial_reference_timestamp = 0; + + // now, if a flush_rtp_timestamp has been defined and the incoming timestamp is "before" it, drop it… + + if ((conn->flush_rtp_timestamp != 0) && (rtp_frame_offset(actual_timestamp,conn->flush_rtp_timestamp) < 8820)) {// if it's less than 0.2 seconds + debug(2, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %" PRIu32 + ", flushing to " + "timestamp: %" PRIu32 ".", + seqno, actual_timestamp, conn->flush_rtp_timestamp); + conn->initial_reference_time = 0; + conn->initial_reference_timestamp = 0; } else { - if ((conn->flush_rtp_timestamp != 0x0) && - (ltimestamp > conn->flush_rtp_timestamp)) // if we have gone past the flush boundary time - conn->flush_rtp_timestamp = 0x0; + if ((conn->flush_rtp_timestamp != 0) && (rtp_frame_offset(conn->flush_rtp_timestamp,actual_timestamp) > 8820) && (rtp_frame_offset(conn->flush_rtp_timestamp,actual_timestamp) < 44100)) { + debug(2,"Dropping flush request"); + conn->flush_rtp_timestamp = 0; + } 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; @@ -519,21 +500,21 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp conn->resend_interval = resend_interval; } - if (conn->ab_write == seqno) { // expected packet + if (conn->ab_write == seqno) { // if this is the expected packet (which could be the first packet…) uint64_t reception_time = get_absolute_time_in_fp(); 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 = reception_time; - conn->frames_inward_frames_received_at_measurement_start_time = timestamp; + 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 = reception_time; - conn->frames_inward_frames_received_at_measurement_time = timestamp; + conn->frames_inward_frames_received_at_measurement_time = actual_timestamp; abuf = conn->audio_buffer + BUFIDX(seqno); - conn->ab_write = SUCCESSOR(seqno); + conn->ab_write = SUCCESSOR(seqno); // move the write pointer to the next free space } else if (seq_order(conn->ab_write, seqno, conn->ab_read)) { // newer than expected // if (ORDINATE(seqno)>(BUFFER_FRAMES*7)/8) // debug(1,"An interval of %u frames has opened, with ab_read: %u, ab_write: %u and @@ -547,7 +528,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp abuf = conn->audio_buffer + BUFIDX(seq_sum(conn->ab_write, i)); abuf->ready = 0; // to be sure, to be sure abuf->resend_level = 0; - abuf->timestamp = 0; + // abuf->timestamp = 0; abuf->given_timestamp = 0; abuf->sequence_number = 0; } @@ -580,14 +561,14 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp if (alac_decode(abuf->data, &datalen, data, len, conn) == 0) { abuf->ready = 1; abuf->length = datalen; - abuf->timestamp = ltimestamp; + // abuf->timestamp = ltimestamp; abuf->given_timestamp = actual_timestamp; abuf->sequence_number = seqno; } else { debug(1, "Bad audio packet detected and discarded."); abuf->ready = 0; abuf->resend_level = 0; - abuf->timestamp = 0; + // abuf->timestamp = 0; abuf->given_timestamp = 0; abuf->sequence_number = 0; } @@ -621,12 +602,6 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp j)) { // prevent multiple requests from the same level of lookback check_buf->resend_level = j; if (config.disable_resend_requests == 0) { - if (((int)(resend_interval * pow(j + 1, step_exponent)) + k) >= - seq_diff(conn->ab_read, conn->ab_write, conn->ab_read)) - debug(3, "Last-ditch (#%d) resend request for packet %u in range %u to %u. " - "Looking back %d packets.", - j, next, conn->ab_read, conn->ab_write, back_step + k); - debug_mutex_unlock(&conn->ab_mutex, 3); rtp_request_resend(next, 1, conn); conn->resend_requests++; debug_mutex_lock(&conn->ab_mutex, 20000, 1); @@ -877,20 +852,23 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { debug(1, "Inconsistent sequence numbers detected"); } } - - if ((conn->flush_rtp_timestamp != 0) && - (curframe->timestamp <= conn->flush_rtp_timestamp)) { - debug(2, "Dropping flushed packet in buffer_get_frame seqno %u, timestamp %" PRId64 ".", - curframe->sequence_number, curframe->timestamp); + + if ((conn->flush_rtp_timestamp != 0) && (rtp_frame_offset(curframe->given_timestamp,conn->flush_rtp_timestamp) < 8820)) {// if it's less than 0.2 seconds + debug(2, "Dropping flushed packet in buffer_get_frame, seqno %u, timestamp %" PRIu32 + ", flushing to " + "timestamp: %" PRIu32 ".", + curframe->sequence_number, curframe->given_timestamp, conn->flush_rtp_timestamp); curframe->ready = 0; curframe->resend_level = 0; - flush_limit++; + flush_limit += curframe->length; conn->ab_read = SUCCESSOR(conn->ab_read); conn->initial_reference_time = 0; conn->initial_reference_timestamp = 0; } - if (curframe->timestamp > conn->flush_rtp_timestamp) - conn->flush_rtp_timestamp = 0; + if ((conn->flush_rtp_timestamp != 0) && (rtp_frame_offset(conn->flush_rtp_timestamp,curframe->given_timestamp) > 8820) && (rtp_frame_offset(conn->flush_rtp_timestamp,curframe->given_timestamp) < 44100)) { + debug(2,"Dropping flush request"); + conn->flush_rtp_timestamp = 0; + } } } while ((conn->flush_rtp_timestamp != 0) && (flush_limit <= 8820) && (curframe->ready == 0)); @@ -921,7 +899,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (have_timestamp_timing_information(conn)) { // if we have a reference time // debug(1,"First frame seen with timestamp..."); conn->first_packet_timestamp = - curframe->timestamp; // we will keep buffering until we are + curframe->given_timestamp; // we will keep buffering until we are // supposed to start playing this have_sent_prefiller_silence = 0; conn->packet_stream_established = 1; @@ -1174,7 +1152,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // Note: the last three items are expressed in frames and must be converted to time. int do_wait = 0; // don't wait unless we can really prove we must - if ((conn->ab_synced) && (curframe) && (curframe->ready) && (curframe->timestamp)) { + if ((conn->ab_synced) && (curframe) && (curframe->ready) && (curframe->given_timestamp)) { do_wait = 1; // if the current frame exists and is ready, then wait unless it's time to let it go... @@ -1184,7 +1162,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { uint64_t time_to_play; frame_to_local_time( - curframe->timestamp + conn->latency * conn->output_sample_ratio + + (curframe->given_timestamp + conn->latency) * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate) - config.audio_backend_buffer_desired_length * config.output_rate, &time_to_play, conn); @@ -1242,7 +1220,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (!curframe->ready) { // debug(1, "Supplying a silent frame for frame %u", read); conn->missing_packets++; - curframe->timestamp = 0; // indicate a silent frame should be substituted + curframe->given_timestamp = 0; // indicate a silent frame should be substituted } curframe->ready = 0; curframe->resend_level = 0; @@ -1791,7 +1769,7 @@ void *player_thread_func(void *arg) { // if (play_number % 100 == 0) // debug(3, "Play frame %d.", play_number); conn->play_number_after_flush++; - if (inframe->timestamp == 0) { + if (inframe->given_timestamp == 0) { debug(3, "Player has supplied a silent frame, (possibly frame %u) for play number %d.", SUCCESSOR(conn->last_seqno_read), play_number); conn->last_seqno_read = (SUCCESSOR(conn->last_seqno_read) & @@ -1925,14 +1903,16 @@ void *player_thread_func(void *arg) { // now, go back as far as the total latency less, say, 100 ms, and check the presence of // frames from then onwards - int64_t reference_timestamp; + uint32_t reference_timestamp; uint64_t reference_timestamp_time, remote_reference_timestamp_time; get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time, conn); // types okay reference_timestamp *= conn->output_sample_ratio; int64_t rt, nt; rt = reference_timestamp; // uint32_t to int64_t - nt = inframe->timestamp; // uint32_t to int64_t + nt = inframe->given_timestamp; // uint32_t to int64_t + rt *= conn->output_sample_ratio; + nt *= conn->output_sample_ratio; uint64_t local_time_now = get_absolute_time_in_fp(); // types okay // struct timespec tn; @@ -2056,7 +2036,7 @@ void *player_thread_func(void *arg) { if (abs_sync_error < 0) abs_sync_error = -abs_sync_error; - if ((config.no_sync == 0) && (inframe->timestamp != 0) && + if ((config.no_sync == 0) && (inframe->given_timestamp != 0) && (config.resyncthreshold > 0.0) && (abs_sync_error > config.resyncthreshold * config.output_rate)) { if (abs_sync_error > 3 * config.output_rate) { @@ -2282,7 +2262,7 @@ void *player_thread_func(void *arg) { } // mark the frame as finished - inframe->timestamp = 0; + inframe->given_timestamp = 0; inframe->sequence_number = 0; // debug(1,"Sync error %lld frames. Amount to stuff %d." ,sync_error,amount_to_stuff); @@ -2726,7 +2706,7 @@ void player_volume(double airplay_volume, rtsp_conn_info *conn) { player_volume_without_notification(airplay_volume, conn); } -void do_flush(int64_t timestamp, rtsp_conn_info *conn) { +void do_flush(uint32_t timestamp, rtsp_conn_info *conn) { debug(3, "Flush requested up to %u. It seems as if 0 is special.", timestamp); debug_mutex_lock(&conn->flush_mutex, 1000, 1); @@ -2755,7 +2735,7 @@ void do_flush(int64_t timestamp, rtsp_conn_info *conn) { debug(3, "Flush request made."); } -void player_flush(int64_t timestamp, rtsp_conn_info *conn) { +void player_flush(uint32_t timestamp, rtsp_conn_info *conn) { debug(3, "player_flush"); do_flush(timestamp, conn); } diff --git a/player.h b/player.h index fe7077b1..6c10d34d 100644 --- a/player.h +++ b/player.h @@ -40,7 +40,7 @@ typedef uint16_t seq_t; typedef struct audio_buffer_entry { // decoded audio packets int ready; int resend_level; - int64_t timestamp; + // int64_t timestamp; seq_t sequence_number; uint32_t given_timestamp; // for debugging and checking signed short *data; @@ -71,10 +71,10 @@ typedef struct { int connection_number; // for debug ID purposes, nothing else... int resend_interval; // this is really just for debugging int AirPlayVersion; // zero if not an AirPlay session. Used to help calculate latency - int64_t latency; // the actual latency used for this play session - int64_t minimum_latency; // set if an a=min-latency: line appears in the ANNOUNCE message; zero + uint32_t latency; // the actual latency used for this play session + uint32_t minimum_latency; // set if an a=min-latency: line appears in the ANNOUNCE message; zero // otherwise - int64_t maximum_latency; // set if an a=max-latency: line appears in the ANNOUNCE message; zero + uint32_t maximum_latency; // set if an a=max-latency: line appears in the ANNOUNCE message; zero // otherwise int fd; @@ -103,10 +103,10 @@ typedef struct { int input_frame_rate_starting_point_is_valid; uint64_t frames_inward_measurement_start_time; - uint64_t frames_inward_frames_received_at_measurement_start_time; + uint32_t frames_inward_frames_received_at_measurement_start_time; uint64_t frames_inward_measurement_time; - uint64_t frames_inward_frames_received_at_measurement_time; + uint32_t frames_inward_frames_received_at_measurement_time; // other stuff... pthread_t *player_thread; @@ -136,7 +136,7 @@ typedef struct { int ab_buffering, ab_synced; int64_t first_packet_timestamp; int flush_requested; - int64_t flush_rtp_timestamp; + uint32_t flush_rtp_timestamp; uint64_t time_of_last_audio_packet; seq_t ab_read, ab_write; @@ -187,7 +187,7 @@ typedef struct { // this is what connects an rtp timestamp to the remote time - int64_t reference_timestamp; + uint32_t reference_timestamp; uint64_t remote_reference_timestamp_time; int packet_stream_established; // true if a stream of packets is flowing, made true by a first @@ -195,7 +195,7 @@ typedef struct { // used as the initials values for calculating the rate at which the source thinks it's sending // frames - int64_t initial_reference_timestamp; + uint32_t initial_reference_timestamp; uint64_t initial_reference_time; double remote_frame_rate; @@ -247,8 +247,8 @@ int player_stop(rtsp_conn_info *conn); void player_volume(double f, rtsp_conn_info *conn); void player_volume_without_notification(double f, rtsp_conn_info *conn); -void player_flush(int64_t timestamp, rtsp_conn_info *conn); -void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp, uint8_t *data, +void player_flush(uint32_t timestamp, rtsp_conn_info *conn); +void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, int len, rtsp_conn_info *conn); int64_t monotonic_timestamp(uint32_t timestamp, rtsp_conn_info *conn); // add an epoch to the timestamp. The monotonic diff --git a/rtp.c b/rtp.c index b8c8ee52..4cd96aba 100644 --- a/rtp.c +++ b/rtp.c @@ -179,7 +179,6 @@ void *rtp_audio_receiver(void *arg) { } uint32_t actual_timestamp = ntohl(*(uint32_t *)(pktp + 4)); - int64_t timestamp = monotonic_timestamp(actual_timestamp, conn); // if (packet[1]&0x10) // debug(1,"Audio packet Extension bit set."); @@ -191,7 +190,7 @@ void *rtp_audio_receiver(void *arg) { if (plen >= 16) { if ((config.diagnostic_drop_packet_fraction == 0.0) || (drand48() > config.diagnostic_drop_packet_fraction)) - player_put_packet(seqno, actual_timestamp, timestamp, pktp, plen, conn); + player_put_packet(seqno, actual_timestamp, pktp, plen, conn); else debug(3, "Dropping audio packet %u to simulate a bad connection.", seqno); continue; @@ -238,7 +237,7 @@ void *rtp_control_receiver(void *arg) { uint8_t packet[2048], *pktp; // struct timespec tn; uint64_t remote_time_of_sync; - int64_t sync_rtp_timestamp; + uint32_t sync_rtp_timestamp; ssize_t nread; while (1) { nread = recv(conn->control_socket, packet, sizeof(packet), 0); @@ -304,14 +303,14 @@ void *rtp_control_receiver(void *arg) { // debug(1,"Remote Sync Time: %0llx.",remote_time_of_sync); - sync_rtp_timestamp = monotonic_timestamp(nctohl(&packet[16]), conn); - int64_t rtp_timestamp_less_latency = monotonic_timestamp(nctohl(&packet[4]), conn); + sync_rtp_timestamp = nctohl(&packet[16]); + uint32_t rtp_timestamp_less_latency = nctohl(&packet[4]); // debug(1,"Sync timestamp is %u.",ntohl(*((uint32_t *)&packet[16]))); if (config.userSuppliedLatency) { if (config.userSuppliedLatency != conn->latency) { - debug(1, "Using the user-supplied latency: %" PRId64 ".", + debug(1, "Using the user-supplied latency: %" PRIu32 ".", config.userSuppliedLatency); } conn->latency = config.userSuppliedLatency; @@ -330,8 +329,8 @@ void *rtp_control_receiver(void *arg) { // Sigh, it would be nice to have a published protocol... uint16_t flags = nctohs(&packet[2]); - int64_t la = sync_rtp_timestamp - rtp_timestamp_less_latency; - // debug(3, "Latency derived just from the sync packet is %" PRId64 " frames.", la); + uint32_t la = sync_rtp_timestamp - rtp_timestamp_less_latency; // note, this might loop around in modulo. Not sure if you'll get an error! + // debug(3, "Latency derived just from the sync packet is %" PRIu32 " frames.", la); if ((flags == 7) || ((conn->AirPlayVersion > 0) && (conn->AirPlayVersion <= 353)) || ((conn->AirPlayVersion > 0) && (conn->AirPlayVersion >= 371))) { la += config.fixedLatencyOffset; @@ -347,20 +346,20 @@ void *rtp_control_receiver(void *arg) { if ((conn->minimum_latency) && (conn->minimum_latency > la)) la = conn->minimum_latency; - const int max_frames = ((3 * BUFFER_FRAMES * 352) / 4) - 11025; + const uint32_t max_frames = ((3 * BUFFER_FRAMES * 352) / 4) - 11025; - if ((la < 0) || (la > max_frames)) { - warn("An out-of-range latency request of %" PRId64 - " frames was ignored. Must be %d frames or less (44,100 frames per second). " - "Latency remains at %" PRId64 " frames.", + if (la > max_frames) { + warn("An out-of-range latency request of %" PRIu32 + " frames was ignored. Must be %" PRIu32 " frames or less (44,100 frames per second). " + "Latency remains at %" PRIu32 " frames.", la, max_frames, conn->latency); } else { if (la != conn->latency) { conn->latency = la; - debug(3, "New latency detected: %" PRId64 ", sync latency: %" PRId64 - ", minimum latency: %" PRId64 ", maximum " - "latency: %" PRId64 ", fixed offset: %" PRId64 ".", + debug(3, "New latency detected: %" PRIu32 ", sync latency: %" PRIu32 + ", minimum latency: %" PRIu32 ", maximum " + "latency: %" PRIu32 ", fixed offset: %" PRIu32 ".", la, sync_rtp_timestamp - rtp_timestamp_less_latency, conn->minimum_latency, conn->maximum_latency, config.fixedLatencyOffset); } @@ -380,7 +379,7 @@ void *rtp_control_receiver(void *arg) { if (remote_frame_time_interval) { conn->remote_frame_rate = (1.0 * (conn->reference_timestamp - conn->initial_reference_timestamp)) / - remote_frame_time_interval; // an IEEE double calculation with two 64-bit + remote_frame_time_interval; // an IEEE double calculation with a 32-bit numerator and 64-bit denominator // integers conn->remote_frame_rate = conn->remote_frame_rate * (uint64_t)0x100000000; // this should just change the @@ -395,7 +394,7 @@ void *rtp_control_receiver(void *arg) { // this is for debugging uint64_t old_remote_reference_time = conn->remote_reference_timestamp_time; - int64_t old_reference_timestamp = conn->reference_timestamp; + uint32_t old_reference_timestamp = conn->reference_timestamp; // int64_t old_latency_delayed_timestamp = conn->latency_delayed_timestamp; conn->remote_reference_timestamp_time = remote_time_of_sync; @@ -444,14 +443,13 @@ void *rtp_control_receiver(void *arg) { debug(3, "Control Receiver -- Retransmitted Audio Data Packet %u received.", seqno); uint32_t actual_timestamp = ntohl(*(uint32_t *)(pktp + 4)); - int64_t timestamp = monotonic_timestamp(actual_timestamp, conn); pktp += 12; plen -= 12; // check if packet contains enough content to be reasonable if (plen >= 16) { - player_put_packet(seqno, actual_timestamp, timestamp, pktp, plen, conn); + player_put_packet(seqno, actual_timestamp, pktp, plen, conn); continue; } else { debug(3, "Too-short retransmitted audio packet received in control port, ignored."); @@ -1004,7 +1002,7 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, uint16_t cport, uint16_t tport } } -void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time, +void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time, uint64_t *remote_timestamp_time, rtsp_conn_info *conn) { // types okay debug_mutex_lock(&conn->reference_time_mutex, 1000, 1); diff --git a/rtp.h b/rtp.h index 13bf2b3f..9bf5c696 100644 --- a/rtp.h +++ b/rtp.h @@ -17,7 +17,7 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, uint16_t controlport, uint16_t void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn); void rtp_request_client_pause(rtsp_conn_info *conn); // ask the client to pause -void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time, +void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time, uint64_t *remote_timestamp_time, rtsp_conn_info *conn); void clear_reference_timestamp(rtsp_conn_info *conn); From b53fc39dd2e29dc18cb44ba3d4510c13c1a3c893 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 19 Oct 2018 14:04:30 +0100 Subject: [PATCH 155/569] Replace a mutex unlock deleted by accident. --- player.c | 1 + 1 file changed, 1 insertion(+) diff --git a/player.c b/player.c index 235b03ab..a96fc845 100644 --- a/player.c +++ b/player.c @@ -602,6 +602,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, j)) { // prevent multiple requests from the same level of lookback check_buf->resend_level = j; if (config.disable_resend_requests == 0) { + debug_mutex_unlock(&conn->ab_mutex, 3); rtp_request_resend(next, 1, conn); conn->resend_requests++; debug_mutex_lock(&conn->ab_mutex, 20000, 1); From 6de64d5954e4223615784584b5906c4bd83e5da3 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 19 Oct 2018 14:05:59 +0100 Subject: [PATCH 156/569] Replace the entire timestamp processing software -- preliminary attempt --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index cfda987c..c66718a8 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.3d16], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d17], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From d2dba7cdba6ddbe433563db6c9aa2bd493f7ad4b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 19 Oct 2018 22:14:23 +0100 Subject: [PATCH 157/569] Seems to work for higher rates --- player.c | 38 +++++++++++++++++++------------------- player.h | 2 ++ rtp.c | 38 +++++++++++++++++--------------------- rtp.h | 6 +++--- 4 files changed, 41 insertions(+), 43 deletions(-) diff --git a/player.c b/player.c index a96fc845..7c055a5c 100644 --- a/player.c +++ b/player.c @@ -99,11 +99,11 @@ // static abuf_t audio_buffer[BUFFER_FRAMES]; #define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES) -uint32_t rtp_frame_offset(uint32_t base, uint32_t frame_in_question) { - if (base <= frame_in_question) - return frame_in_question - base; +uint32_t rtp_frame_offset(uint32_t from, uint32_t to) { + if (from <= to) + return to - from; else - return UINT32_MAX - base + frame_in_question + 1; + return UINT32_MAX - from + to + 1; } void do_flush(uint32_t timestamp, rtsp_conn_info *conn); @@ -944,8 +944,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { uint64_t should_be_time; frame_to_local_time( - conn->first_packet_timestamp + conn->latency * conn->output_sample_ratio + - (int64_t)(config.audio_backend_latency_offset * config.output_rate), + conn->first_packet_timestamp + conn->latency + + (uint32_t)(config.audio_backend_latency_offset * conn->input_rate), // this will go modulo 2^32 &should_be_time, conn); conn->first_packet_time_to_play = should_be_time; @@ -965,8 +965,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { uint64_t should_be_time; frame_to_local_time( - conn->first_packet_timestamp + conn->latency * conn->output_sample_ratio + - (int64_t)(config.audio_backend_latency_offset * config.output_rate), + conn->first_packet_timestamp + conn->latency + + (uint32_t)(config.audio_backend_latency_offset * conn->input_rate), // this should go modulo 2^32 &should_be_time, conn); conn->first_packet_time_to_play = should_be_time; @@ -1163,9 +1163,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { uint64_t time_to_play; frame_to_local_time( - (curframe->given_timestamp + conn->latency) * conn->output_sample_ratio + - (int64_t)(config.audio_backend_latency_offset * config.output_rate) - - config.audio_backend_buffer_desired_length * config.output_rate, + curframe->given_timestamp + conn->latency + + (uint32_t)(config.audio_backend_latency_offset * conn->input_rate) - + (uint32_t)(config.audio_backend_buffer_desired_length * conn->input_rate), // this will go modulo 2^32 &time_to_play, conn); if (local_time_now >= time_to_play) { @@ -1908,7 +1908,6 @@ void *player_thread_func(void *arg) { uint64_t reference_timestamp_time, remote_reference_timestamp_time; get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time, conn); // types okay - reference_timestamp *= conn->output_sample_ratio; int64_t rt, nt; rt = reference_timestamp; // uint32_t to int64_t nt = inframe->given_timestamp; // uint32_t to int64_t @@ -2000,8 +1999,9 @@ void *player_thread_func(void *arg) { if (resp >= 0) { - int64_t should_be_frame; - local_time_to_frame(local_time_now, &should_be_frame, conn); + 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; // int64_t absolute_difference_in_frames = td_in_frames + rt - should_be_frame; // if (absolute_difference_in_frames < 0) @@ -2041,11 +2041,11 @@ void *player_thread_func(void *arg) { (config.resyncthreshold > 0.0) && (abs_sync_error > config.resyncthreshold * config.output_rate)) { if (abs_sync_error > 3 * config.output_rate) { - warn("Very large sync error: %" PRId64 " frames, with delay: %" PRId64 - ", td_in_frames: %" PRId64 ", rt: %" PRId64 ", nt: %" PRId64 - ", current_delay: %" PRId64 ", seqno: %u, given timestamp: %" PRIu32 ".", - sync_error, delay, td_in_frames, rt, nt, current_delay, - inframe->sequence_number, inframe->given_timestamp); + + warn("Very large sync error: %" PRId64 " frames, with should_be_frame: %" PRId64 + ", nt: %" PRId64 + ", current_delay: %" PRId64 ", given timestamp %" PRIX32 ", reference timestamp %" PRIX32 ", should_be_frame %" PRIX32 ".", + sync_error, should_be_frame, nt, current_delay, inframe->given_timestamp, reference_timestamp, should_be_frame_32); } sync_error_out_of_bounds++; } else { diff --git a/player.h b/player.h index 6c10d34d..43d93c05 100644 --- a/player.h +++ b/player.h @@ -242,6 +242,8 @@ typedef struct { void *dapo_private_storage; // this is used for compatibility, if dacp stuff isn't enabled. } rtsp_conn_info; +uint32_t rtp_frame_offset(uint32_t from, uint32_t to); + int player_play(rtsp_conn_info *conn); int player_stop(rtsp_conn_info *conn); diff --git a/rtp.c b/rtp.c index 4cd96aba..599d5a48 100644 --- a/rtp.c +++ b/rtp.c @@ -1034,13 +1034,14 @@ int have_timestamp_timing_information(rtsp_conn_info *conn) { // right... const int use_nominal_rate = 0; // specify whether to use the nominal input rate, usually 44100 fps -int sanitised_source_rate_information(int64_t *frames, uint64_t *time, rtsp_conn_info *conn) { +int sanitised_source_rate_information(uint32_t *frames, uint64_t *time, rtsp_conn_info *conn) { int result = 1; *frames = conn->input_rate; *time = (uint64_t)(0x100000000); // one second in fp form if ((conn->packet_stream_established) && (conn->initial_reference_time) && (conn->initial_reference_timestamp)) { - int64_t local_frames = conn->reference_timestamp - conn->initial_reference_timestamp; +// uint32_t local_frames = conn->reference_timestamp - conn->initial_reference_timestamp; + uint32_t local_frames = rtp_frame_offset(conn->initial_reference_timestamp, conn->reference_timestamp); uint64_t local_time = conn->remote_reference_timestamp_time - conn->initial_reference_time; if ((local_frames == 0) || (local_time == 0) || (use_nominal_rate)) { result = 1; @@ -1060,39 +1061,34 @@ int sanitised_source_rate_information(int64_t *frames, uint64_t *time, rtsp_conn return result; } -// we assume here that the timestamp is a timestamp calculated at the output rate, which could be an -// integer multiple of the input rate +// the timestamp is a timestamp calculated at the input rate // the reference timestamps are denominated in terms of the input rate -int frame_to_local_time(int64_t timestamp, uint64_t *time, rtsp_conn_info *conn) { +int frame_to_local_time(uint32_t timestamp, uint64_t *time, rtsp_conn_info *conn) { debug_mutex_lock(&conn->reference_time_mutex, 1000, 1); int result = 0; uint64_t time_difference; - int64_t frame_difference; + uint32_t frame_difference; result = sanitised_source_rate_information(&frame_difference, &time_difference, conn); - int64_t timestamp_interval = - timestamp - - conn->reference_timestamp * - conn->output_sample_ratio; // we could be dealing with multiples of 44100 (nominally) - // debug(1, "Timestamp interval: %" PRId64 " frames with reference timestamp %" PRId64 - // ".",timestamp_interval,conn->reference_timestamp); uint64_t timestamp_interval_time; uint64_t remote_time_of_timestamp; - if (timestamp_interval >= 0) { + uint32_t timestamp_interval = rtp_frame_offset(conn->reference_timestamp,timestamp); + if (timestamp_interval <= 44100*10) { // i.e. timestamp was really after the reference timestamp timestamp_interval_time = (timestamp_interval * time_difference) / - (frame_difference * conn->output_sample_ratio); // this is the nominal time, based on the + (frame_difference); // this is the nominal time, based on the // fps specified between current and // previous sync frame. remote_time_of_timestamp = conn->remote_reference_timestamp_time + timestamp_interval_time; // based on the reference timestamp time // plus the time interval calculated based // on the specified fps. - } else { + } else { // i.e. timestamp was actually after the reference timestamp + timestamp_interval = rtp_frame_offset(timestamp,conn->reference_timestamp); // fix the calculation timestamp_interval_time = - ((-timestamp_interval) * time_difference) / - (frame_difference * conn->output_sample_ratio); // this is the nominal time, based on the + (timestamp_interval * time_difference) / + (frame_difference); // this is the nominal time, based on the // fps specified between current and // previous sync frame. remote_time_of_timestamp = conn->remote_reference_timestamp_time - @@ -1105,12 +1101,12 @@ int frame_to_local_time(int64_t timestamp, uint64_t *time, rtsp_conn_info *conn) return result; } -int local_time_to_frame(uint64_t time, int64_t *frame, rtsp_conn_info *conn) { +int local_time_to_frame(uint64_t time, uint32_t *frame, rtsp_conn_info *conn) { debug_mutex_lock(&conn->reference_time_mutex, 1000, 1); int result = 0; uint64_t time_difference; - int64_t frame_difference; + uint32_t frame_difference; result = sanitised_source_rate_information(&frame_difference, &time_difference, conn); // first, get from [local] time to remote time. @@ -1129,10 +1125,10 @@ int local_time_to_frame(uint64_t time, int64_t *frame, rtsp_conn_info *conn) { int64_t frame_interval = (time_interval * frame_difference) / time_difference; if (remote_time >= conn->remote_reference_timestamp_time) { // debug(1,"Frame interval is %" PRId64 " frames.",frame_interval); - *frame = (conn->reference_timestamp + frame_interval) * conn->output_sample_ratio; + *frame = (conn->reference_timestamp + frame_interval); } else { // debug(1,"Frame interval is %" PRId64 " frames.",-frame_interval); - *frame = (conn->reference_timestamp - frame_interval) * conn->output_sample_ratio; + *frame = (conn->reference_timestamp - frame_interval); } debug_mutex_unlock(&conn->reference_time_mutex, 3); return result; diff --git a/rtp.h b/rtp.h index 9bf5c696..171bd651 100644 --- a/rtp.h +++ b/rtp.h @@ -25,9 +25,9 @@ int have_timestamp_timing_information(rtsp_conn_info *conn); int get_frame_play_time(int64_t timestamp, int sample_ratio, uint64_t *time_to_play); -int frame_to_local_time(int64_t timestamp, uint64_t *time, rtsp_conn_info *conn); -int local_time_to_frame(uint64_t time, int64_t *frame, rtsp_conn_info *conn); +int frame_to_local_time(uint32_t timestamp, uint64_t *time, rtsp_conn_info *conn); +int local_time_to_frame(uint64_t time, uint32_t *frame, rtsp_conn_info *conn); -int sanitised_source_rate_information(int64_t *frames, uint64_t *time, rtsp_conn_info *conn); +int sanitised_source_rate_information(uint32_t *frames, uint64_t *time, rtsp_conn_info *conn); #endif // _RTP_H From a5f165d982774665bec5988085579c65c2b03f3d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 21 Oct 2018 16:31:55 +0100 Subject: [PATCH 158/569] Use a parameter rather than a hard-wired input rate. --- rtp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtp.c b/rtp.c index 599d5a48..e2c8f944 100644 --- a/rtp.c +++ b/rtp.c @@ -1074,7 +1074,7 @@ int frame_to_local_time(uint32_t timestamp, uint64_t *time, rtsp_conn_info *conn uint64_t timestamp_interval_time; uint64_t remote_time_of_timestamp; uint32_t timestamp_interval = rtp_frame_offset(conn->reference_timestamp,timestamp); - if (timestamp_interval <= 44100*10) { // i.e. timestamp was really after the reference timestamp + if (timestamp_interval <= conn->input_rate*10) { // i.e. timestamp was really after the reference timestamp timestamp_interval_time = (timestamp_interval * time_difference) / (frame_difference); // this is the nominal time, based on the From e5259e8764cc35b6f7ea510a8a2627eb7b87305c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 21 Oct 2018 17:00:56 +0100 Subject: [PATCH 159/569] Remove a few compiler warnings. --- player.c | 2 +- player.h | 2 +- rtsp.c | 32 ++++++++++++++++---------------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/player.c b/player.c index 7c055a5c..ac799409 100644 --- a/player.c +++ b/player.c @@ -1610,7 +1610,7 @@ void *player_thread_func(void *arg) { signed short *inbuf; int inbuflength; - int output_bit_depth = 16; // default; + unsigned int output_bit_depth = 16; // default; switch (config.output_format) { case SPS_FORMAT_S8: diff --git a/player.h b/player.h index 43d93c05..5c0adc24 100644 --- a/player.h +++ b/player.h @@ -111,7 +111,7 @@ typedef struct { // other stuff... pthread_t *player_thread; abuf_t audio_buffer[BUFFER_FRAMES]; - int max_frames_per_packet, input_num_channels, input_bit_depth, input_rate; + unsigned int max_frames_per_packet, input_num_channels, input_bit_depth, input_rate; int input_bytes_per_frame, output_bytes_per_frame, output_sample_ratio; int max_frame_size_change; int64_t previous_random_number; diff --git a/rtsp.c b/rtsp.c index 32759d43..834e56e8 100644 --- a/rtsp.c +++ b/rtsp.c @@ -285,7 +285,7 @@ void cancel_all_RTSP_threads(void) { } } -static void cleanup_threads(void) { +void cleanup_threads(void) { void *retval; int i; // debug(2, "culling threads."); @@ -348,7 +348,7 @@ static char *nextline(char *in, int inbuf) { return out; } -static void msg_retain(rtsp_message *msg) { +void msg_retain(rtsp_message *msg) { if (msg) { int rc = pthread_mutex_lock(&reference_counter_lock); if (rc) @@ -362,7 +362,7 @@ static void msg_retain(rtsp_message *msg) { } } -static rtsp_message *msg_init(void) { +rtsp_message *msg_init(void) { rtsp_message *msg = malloc(sizeof(rtsp_message)); if (msg) { memset(msg, 0, sizeof(rtsp_message)); @@ -373,7 +373,7 @@ static rtsp_message *msg_init(void) { return msg; } -static int msg_add_header(rtsp_message *msg, char *name, char *value) { +int msg_add_header(rtsp_message *msg, char *name, char *value) { if (msg->nheaders >= sizeof(msg->name) / sizeof(char *)) { warn("too many headers?!"); return 1; @@ -386,7 +386,7 @@ static int msg_add_header(rtsp_message *msg, char *name, char *value) { return 0; } -static char *msg_get_header(rtsp_message *msg, char *name) { +char *msg_get_header(rtsp_message *msg, char *name) { unsigned int i; for (i = 0; i < msg->nheaders; i++) if (!strcasecmp(msg->name[i], name)) @@ -394,7 +394,7 @@ static char *msg_get_header(rtsp_message *msg, char *name) { return NULL; } -static void debug_print_msg_headers(int level, rtsp_message *msg) { +void debug_print_msg_headers(int level, rtsp_message *msg) { unsigned int i; for (i = 0; i < msg->nheaders; i++) { debug(level, " Type: \"%s\", content: \"%s\"", msg->name[i], msg->value[i]); @@ -424,7 +424,7 @@ static void debug_print_msg_content(int level, rtsp_message *msg) { } */ -static void msg_free(rtsp_message *msg) { +void msg_free(rtsp_message *msg) { if (msg) { int rc = pthread_mutex_lock(&reference_counter_lock); @@ -452,7 +452,7 @@ static void msg_free(rtsp_message *msg) { } } -static int msg_handle_line(rtsp_message **pmsg, char *line) { +int msg_handle_line(rtsp_message **pmsg, char *line) { rtsp_message *msg = *pmsg; if (!msg) { @@ -507,7 +507,7 @@ fail: return 0; } -static enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, +enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) { enum rtsp_read_request_response reply = rtsp_read_request_response_ok; ssize_t buflen = 4096; @@ -649,7 +649,7 @@ shutdown: return reply; } -static void msg_write_response(int fd, rtsp_message *resp) { +void msg_write_response(int fd, rtsp_message *resp) { char pkt[2048]; int pktfree = sizeof(pkt); char *p = pkt; @@ -697,7 +697,7 @@ static void msg_write_response(int fd, rtsp_message *resp) { debug(1, "Error writing an RTSP packet -- requested bytes not fully written."); } -static void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { +void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(2, "Connection %d: RECORD", conn->connection_number); if (conn->player_thread) @@ -737,7 +737,7 @@ static void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message } } -static void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, +void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, rtsp_message *resp) { debug(3, "Connection %d: OPTIONS", conn->connection_number); resp->respcode = 200; @@ -746,7 +746,7 @@ static void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_me "OPTIONS, GET_PARAMETER, SET_PARAMETER"); } -static void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, +void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, rtsp_message *resp) { debug(2, "Connection %d: TEARDOWN", conn->connection_number); // if (!rtsp_playing()) @@ -763,7 +763,7 @@ static void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_m conn->connection_number); } -static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { +void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(3, "Connection %d: FLUSH", conn->connection_number); // if (!rtsp_playing()) // debug(1, "This RTSP conversation thread (%d) doesn't think it's playing, but " @@ -793,7 +793,7 @@ static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message * resp->respcode = 200; } -static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { +void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(3, "Connection %d: SETUP", conn->connection_number); uint16_t cport, tport; @@ -903,7 +903,7 @@ static void handle_ignore(rtsp_conn_info *conn, rtsp_message *req, rtsp_message } */ -static void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req, +void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req, __attribute__((unused)) rtsp_message *resp) { char *cp = req->content; int cp_left = req->contentlength; From e0a4e5cde80abeebaa399367df8a7c07ee607b7c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 21 Oct 2018 17:10:23 +0100 Subject: [PATCH 160/569] Format tidy-up --- audio_alsa.c | 6 +- audio_pa.c | 8 ++- common.h | 6 +- player.c | 200 ++++++++++++++++++++++++++++----------------------- player.h | 14 ++-- rtp.c | 47 ++++++------ rtsp.c | 9 ++- 7 files changed, 160 insertions(+), 130 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 5dc4d32f..056fe7a7 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -894,15 +894,15 @@ int delay(long *the_delay) { } } } else { - reply = -EIO; // shomething is wrong + reply = -EIO; // shomething is wrong frame_index = 0; // we'll be starting over... measurement_data_is_valid = 0; if (dac_state == SND_PCM_STATE_PREPARED) { - debug(1,"delay not available -- state is SND_PCM_STATE_PREPARED"); + debug(1, "delay not available -- state is SND_PCM_STATE_PREPARED"); } else { if (dac_state == SND_PCM_STATE_XRUN) { - debug(1,"delay not available -- state is SND_PCM_STATE_XRUN"); + debug(1, "delay not available -- state is SND_PCM_STATE_XRUN"); } else { debug(1, "Error -- ALSA delay(): bad state: %d.", dac_state); diff --git a/audio_pa.c b/audio_pa.c index aec4d992..ca79b4dc 100644 --- a/audio_pa.c +++ b/audio_pa.c @@ -187,12 +187,14 @@ static void start(__attribute__((unused)) int sample_rate, if (config.pa_sink) { // Connect stream to the sink specified in the config - connect_result = pa_stream_connect_playback(stream, config.pa_sink, &buffer_attr, stream_flags, NULL, NULL); + connect_result = + pa_stream_connect_playback(stream, config.pa_sink, &buffer_attr, stream_flags, NULL, NULL); } else { // Connect stream to the default audio output sink - connect_result = pa_stream_connect_playback(stream, NULL, &buffer_attr, stream_flags, NULL, NULL); + connect_result = + pa_stream_connect_playback(stream, NULL, &buffer_attr, stream_flags, NULL, NULL); } - + if (connect_result != 0) die("could not connect to the pulseaudio playback stream -- the error message is \"%s\".", pa_strerror(pa_context_errno(context))); diff --git a/common.h b/common.h index 0042d2c4..c9674c41 100644 --- a/common.h +++ b/common.h @@ -84,9 +84,9 @@ typedef struct { #ifdef CONFIG_PA char *pa_application_name; // the name under which Shairport Sync shows up as an "Application" in // the Sound Preferences in most desktop Linuxes. - // Defaults to "Shairport Sync". Shairport Sync must be playing to see it. + // Defaults to "Shairport Sync". Shairport Sync must be playing to see it. - char *pa_sink; // the name (or id) of the sink that Shairport Sync will play on. + char *pa_sink; // the name (or id) of the sink that Shairport Sync will play on. #endif #ifdef CONFIG_METADATA int metadata_enabled; @@ -136,7 +136,7 @@ typedef struct { int buffer_start_fill; uint32_t userSuppliedLatency; // overrides all other latencies -- use with caution uint32_t fixedLatencyOffset; // add this to all automatic latencies supplied to get the actual - // total latency + // total latency // the total latency will be limited to the min and max-latency values, if supplied int daemonise; int daemonise_store_pid; // don't try to save a PID file diff --git a/player.c b/player.c index ac799409..f51d8aef 100644 --- a/player.c +++ b/player.c @@ -100,10 +100,10 @@ #define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES) uint32_t rtp_frame_offset(uint32_t from, uint32_t to) { - if (from <= to) - return to - from; - else - return UINT32_MAX - from + to + 1; + if (from <= to) + return to - from; + else + return UINT32_MAX - from + to + 1; } void do_flush(uint32_t timestamp, rtsp_conn_info *conn); @@ -120,33 +120,34 @@ static void ab_resync(rtsp_conn_info *conn) { conn->ab_buffering = 1; } -// given starting and ending points as unsigned 32-bit integers running modulo 2^32, returns the position of x in the interval in *pos +// given starting and ending points as unsigned 32-bit integers running modulo 2^32, returns the +// position of x in the interval in *pos // returns true if x is actually within the buffer -int position_in_modulo_uint32_t_buffer(uint32_t x, uint32_t start, uint32_t end, uint32_t *pos ) { - int response = 0; // not in the buffer - if (start <= end ) { - if (x < start) { - if (pos) - *pos = UINT32_MAX - start + 1 + x; - } else { - if (pos) - *pos = x - start; - if (x < end) - response = 1; - } - } else if ((x >= start) && (x <= UINT32_MAX)) { - response = 1; - if (pos) - *pos = x - start; - } else { - if (pos) - *pos = UINT32_MAX - start + 1 + x; - if (x < end) { - response = 1; - } - } - return response; +int position_in_modulo_uint32_t_buffer(uint32_t x, uint32_t start, uint32_t end, uint32_t *pos) { + int response = 0; // not in the buffer + if (start <= end) { + if (x < start) { + if (pos) + *pos = UINT32_MAX - start + 1 + x; + } else { + if (pos) + *pos = x - start; + if (x < end) + response = 1; + } + } else if ((x >= start) && (x <= UINT32_MAX)) { + response = 1; + if (pos) + *pos = x - start; + } else { + if (pos) + *pos = UINT32_MAX - start + 1 + x; + if (x < end) { + response = 1; + } + } + return response; } // this is used. @@ -441,8 +442,8 @@ static void free_audio_buffers(rtsp_conn_info *conn) { free(conn->audio_buffer[i].data); } -void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, - int len, rtsp_conn_info *conn) { +void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, int len, + rtsp_conn_info *conn) { // ignore a request to flush that has been made before the first packet... if (conn->packet_count == 0) { @@ -460,26 +461,31 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, // if (flush_rtp_timestamp != 0) // debug(1,"Flush_rtp_timestamp is %u",flush_rtp_timestamp); - - // now, if a flush_rtp_timestamp has been defined and the incoming timestamp is "before" it, drop it… - - if ((conn->flush_rtp_timestamp != 0) && (rtp_frame_offset(actual_timestamp,conn->flush_rtp_timestamp) < 8820)) {// if it's less than 0.2 seconds - debug(2, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %" PRIu32 - ", flushing to " - "timestamp: %" PRIu32 ".", - seqno, actual_timestamp, conn->flush_rtp_timestamp); - conn->initial_reference_time = 0; - conn->initial_reference_timestamp = 0; + + // now, if a flush_rtp_timestamp has been defined and the incoming timestamp is "before" it, + // drop it… + + if ((conn->flush_rtp_timestamp != 0) && + (rtp_frame_offset(actual_timestamp, conn->flush_rtp_timestamp) < + 8820)) { // if it's less than 0.2 seconds + debug(2, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %" PRIu32 + ", flushing to " + "timestamp: %" PRIu32 ".", + seqno, actual_timestamp, conn->flush_rtp_timestamp); + conn->initial_reference_time = 0; + conn->initial_reference_timestamp = 0; } else { - if ((conn->flush_rtp_timestamp != 0) && (rtp_frame_offset(conn->flush_rtp_timestamp,actual_timestamp) > 8820) && (rtp_frame_offset(conn->flush_rtp_timestamp,actual_timestamp) < 44100)) { - debug(2,"Dropping flush request"); + if ((conn->flush_rtp_timestamp != 0) && + (rtp_frame_offset(conn->flush_rtp_timestamp, actual_timestamp) > 8820) && + (rtp_frame_offset(conn->flush_rtp_timestamp, actual_timestamp) < 44100)) { + debug(2, "Dropping flush request"); conn->flush_rtp_timestamp = 0; } abuf_t *abuf = 0; if (!conn->ab_synced) { - // if this is the first packet… + // if this is the first packet… debug(3, "syncing to seqno %u.", seqno); conn->ab_write = seqno; conn->ab_read = seqno; @@ -500,7 +506,8 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, conn->resend_interval = resend_interval; } - if (conn->ab_write == seqno) { // if this is the expected packet (which could be the first packet…) + if (conn->ab_write == + seqno) { // if this is the expected packet (which could be the first packet…) uint64_t reception_time = get_absolute_time_in_fp(); if (conn->input_frame_rate_starting_point_is_valid == 0) { if ((conn->packet_count_since_flush >= 500) && (conn->packet_count_since_flush <= 510)) { @@ -602,7 +609,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, j)) { // prevent multiple requests from the same level of lookback check_buf->resend_level = j; if (config.disable_resend_requests == 0) { - debug_mutex_unlock(&conn->ab_mutex, 3); + debug_mutex_unlock(&conn->ab_mutex, 3); rtp_request_resend(next, 1, conn); conn->resend_requests++; debug_mutex_lock(&conn->ab_mutex, 20000, 1); @@ -853,12 +860,14 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { debug(1, "Inconsistent sequence numbers detected"); } } - - if ((conn->flush_rtp_timestamp != 0) && (rtp_frame_offset(curframe->given_timestamp,conn->flush_rtp_timestamp) < 8820)) {// if it's less than 0.2 seconds - debug(2, "Dropping flushed packet in buffer_get_frame, seqno %u, timestamp %" PRIu32 - ", flushing to " - "timestamp: %" PRIu32 ".", - curframe->sequence_number, curframe->given_timestamp, conn->flush_rtp_timestamp); + + if ((conn->flush_rtp_timestamp != 0) && + (rtp_frame_offset(curframe->given_timestamp, conn->flush_rtp_timestamp) < + 8820)) { // if it's less than 0.2 seconds + debug(2, "Dropping flushed packet in buffer_get_frame, seqno %u, timestamp %" PRIu32 + ", flushing to " + "timestamp: %" PRIu32 ".", + curframe->sequence_number, curframe->given_timestamp, conn->flush_rtp_timestamp); curframe->ready = 0; curframe->resend_level = 0; flush_limit += curframe->length; @@ -866,10 +875,12 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { conn->initial_reference_time = 0; conn->initial_reference_timestamp = 0; } - if ((conn->flush_rtp_timestamp != 0) && (rtp_frame_offset(conn->flush_rtp_timestamp,curframe->given_timestamp) > 8820) && (rtp_frame_offset(conn->flush_rtp_timestamp,curframe->given_timestamp) < 44100)) { - debug(2,"Dropping flush request"); - conn->flush_rtp_timestamp = 0; - } + if ((conn->flush_rtp_timestamp != 0) && + (rtp_frame_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) > 8820) && + (rtp_frame_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) < 44100)) { + debug(2, "Dropping flush request"); + conn->flush_rtp_timestamp = 0; + } } } while ((conn->flush_rtp_timestamp != 0) && (flush_limit <= 8820) && (curframe->ready == 0)); @@ -901,7 +912,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // debug(1,"First frame seen with timestamp..."); conn->first_packet_timestamp = curframe->given_timestamp; // we will keep buffering until we are - // supposed to start playing this + // supposed to start playing this have_sent_prefiller_silence = 0; conn->packet_stream_established = 1; @@ -943,10 +954,11 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // frame should be played" uint64_t should_be_time; - frame_to_local_time( - conn->first_packet_timestamp + conn->latency + - (uint32_t)(config.audio_backend_latency_offset * conn->input_rate), // this will go modulo 2^32 - &should_be_time, conn); + frame_to_local_time(conn->first_packet_timestamp + conn->latency + + (uint32_t)(config.audio_backend_latency_offset * + conn->input_rate), // this will go modulo 2^32 + &should_be_time, + conn); conn->first_packet_time_to_play = should_be_time; @@ -964,10 +976,11 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // recalculate conn->first_packet_time_to_play -- the latency might change uint64_t should_be_time; - frame_to_local_time( - conn->first_packet_timestamp + conn->latency + - (uint32_t)(config.audio_backend_latency_offset * conn->input_rate), // this should go modulo 2^32 - &should_be_time, conn); + frame_to_local_time(conn->first_packet_timestamp + conn->latency + + (uint32_t)(config.audio_backend_latency_offset * + conn->input_rate), // this should go modulo 2^32 + &should_be_time, + conn); conn->first_packet_time_to_play = should_be_time; @@ -979,9 +992,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { int64_t max_dac_delay = config.output_rate / 10; // so the lead-in time must be greater // than this, say 0.2 sec, to allow for - // dynamic adjustment + // dynamic adjustment int64_t filler_size = max_dac_delay; - + if (local_time_now >= conn->first_packet_time_to_play) { // debug(1,"Gone past starting time"); have_sent_prefiller_silence = 1; @@ -1006,34 +1019,38 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { int64_t lead_time = conn->first_packet_time_to_play - local_time_now; // an audio_backend_silent_lead_in_time of less than zero means start filling ASAP int64_t lead_in_time = -1; - if (config.audio_backend_silent_lead_in_time>=0) + if (config.audio_backend_silent_lead_in_time >= 0) lead_in_time = - (int64_t)(config.audio_backend_silent_lead_in_time * (int64_t)0x100000000); + (int64_t)(config.audio_backend_silent_lead_in_time * (int64_t)0x100000000); // debug(1,"Lead time is %llx at fpttp // %llx.",lead_time,conn->first_packet_time_to_play); if ((lead_in_time < 0) || (lead_time <= lead_in_time)) { - // debug(1,"Lead time is %" PRIx64 ", lead-in time is %" PRIx64 " at fpttp %llx.",lead_time,conn->first_packet_time_to_play); + // debug(1,"Lead time is %" PRIx64 ", lead-in time is %" PRIx64 " at fpttp + // %llx.",lead_time,conn->first_packet_time_to_play); // debug(1,"Checking"); if (config.output->delay) { // conn->first_packet_time_to_play is definitely later than local_time_now int resp = 0; dac_delay = 0; - if (have_sent_prefiller_silence != 0) + if (have_sent_prefiller_silence != 0) resp = config.output->delay(&dac_delay); if (resp == 0) { int64_t gross_frame_gap = ((conn->first_packet_time_to_play - local_time_now) * config.output_rate) >> 32; int64_t exact_frame_gap = gross_frame_gap - dac_delay; - // debug(1,"Exact and gross frame gaps are %" PRId64 " and %" PRId64 " frames, and the dac delay is %ld.", exact_frame_gap, gross_frame_gap, dac_delay); + // debug(1,"Exact and gross frame gaps are %" PRId64 " and %" PRId64 " frames, + // and the dac delay is %ld.", exact_frame_gap, gross_frame_gap, dac_delay); if (exact_frame_gap < 0) { // we've gone past the time... // debug(1,"Run past time."); - - // this might happen if a big clock adjustment was made at just the wrong time. - - debug(1,"Run a bit past the exact start time by %" PRId64 " frames.",-exact_frame_gap); + + // this might happen if a big clock adjustment was made at just the wrong + // time. + + debug(1, "Run a bit past the exact start time by %" PRId64 " frames.", + -exact_frame_gap); if (config.output->flush) config.output->flush(); ab_resync(conn); @@ -1044,7 +1061,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (fs > (max_dac_delay - dac_delay)) fs = max_dac_delay - dac_delay; if (fs < 0) { - // this could happen if the dac delay mysteriously grows between samples, which could happen in a transition between having no interpolation and having interpolated buffer numbers. + // this could happen if the dac delay mysteriously grows between samples, + // which could happen in a transition between having no interpolation and + // having interpolated buffer numbers. debug(2, "frame size (fs) < 0 with max_dac_delay of %lld and dac_delay of %ld", max_dac_delay, dac_delay); @@ -1062,7 +1081,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { } void *silence; // if (fs==0) - // debug(2,"Zero length silence buffer needed with gross_frame_gap of %lld and + // debug(2,"Zero length silence buffer needed with gross_frame_gap of %lld + // and // dac_delay of %lld.",gross_frame_gap,dac_delay); // the fs (number of frames of silence to play) can be zero in the DAC doesn't // start @@ -1162,11 +1182,12 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (have_timestamp_timing_information(conn)) { // if we have a reference time uint64_t time_to_play; - frame_to_local_time( - curframe->given_timestamp + conn->latency + - (uint32_t)(config.audio_backend_latency_offset * conn->input_rate) - - (uint32_t)(config.audio_backend_buffer_desired_length * conn->input_rate), // this will go modulo 2^32 - &time_to_play, conn); + frame_to_local_time(curframe->given_timestamp + conn->latency + + (uint32_t)(config.audio_backend_latency_offset * conn->input_rate) - + (uint32_t)(config.audio_backend_buffer_desired_length * + conn->input_rate), // this will go modulo 2^32 + &time_to_play, + conn); if (local_time_now >= time_to_play) { do_wait = 0; @@ -1909,8 +1930,8 @@ void *player_thread_func(void *arg) { get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time, conn); // types okay int64_t rt, nt; - rt = reference_timestamp; // uint32_t to int64_t - nt = inframe->given_timestamp; // uint32_t to int64_t + rt = reference_timestamp; // uint32_t to int64_t + nt = inframe->given_timestamp; // uint32_t to int64_t rt *= conn->output_sample_ratio; nt *= conn->output_sample_ratio; @@ -2041,11 +2062,12 @@ void *player_thread_func(void *arg) { (config.resyncthreshold > 0.0) && (abs_sync_error > config.resyncthreshold * config.output_rate)) { if (abs_sync_error > 3 * config.output_rate) { - + warn("Very large sync error: %" PRId64 " frames, with should_be_frame: %" PRId64 - ", nt: %" PRId64 - ", current_delay: %" PRId64 ", given timestamp %" PRIX32 ", reference timestamp %" PRIX32 ", should_be_frame %" PRIX32 ".", - sync_error, should_be_frame, nt, current_delay, inframe->given_timestamp, reference_timestamp, should_be_frame_32); + ", nt: %" PRId64 ", current_delay: %" PRId64 ", given timestamp %" PRIX32 + ", reference timestamp %" PRIX32 ", should_be_frame %" PRIX32 ".", + sync_error, should_be_frame, nt, current_delay, inframe->given_timestamp, + reference_timestamp, should_be_frame_32); } sync_error_out_of_bounds++; } else { diff --git a/player.h b/player.h index 5c0adc24..a7531b90 100644 --- a/player.h +++ b/player.h @@ -68,14 +68,14 @@ typedef struct { } stream_cfg; typedef struct { - int connection_number; // for debug ID purposes, nothing else... - int resend_interval; // this is really just for debugging - int AirPlayVersion; // zero if not an AirPlay session. Used to help calculate latency + int connection_number; // for debug ID purposes, nothing else... + int resend_interval; // this is really just for debugging + int AirPlayVersion; // zero if not an AirPlay session. Used to help calculate latency uint32_t latency; // the actual latency used for this play session uint32_t minimum_latency; // set if an a=min-latency: line appears in the ANNOUNCE message; zero - // otherwise + // otherwise uint32_t maximum_latency; // set if an a=max-latency: line appears in the ANNOUNCE message; zero - // otherwise + // otherwise int fd; int authorized; // set if a password is required and has been supplied @@ -250,8 +250,8 @@ int player_stop(rtsp_conn_info *conn); void player_volume(double f, rtsp_conn_info *conn); void player_volume_without_notification(double f, rtsp_conn_info *conn); void player_flush(uint32_t timestamp, rtsp_conn_info *conn); -void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, - int len, rtsp_conn_info *conn); +void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, int len, + rtsp_conn_info *conn); int64_t monotonic_timestamp(uint32_t timestamp, rtsp_conn_info *conn); // add an epoch to the timestamp. The monotonic // timestamp guaranteed to start between 2^32 2^33 diff --git a/rtp.c b/rtp.c index e2c8f944..eb86da48 100644 --- a/rtp.c +++ b/rtp.c @@ -329,10 +329,14 @@ void *rtp_control_receiver(void *arg) { // Sigh, it would be nice to have a published protocol... uint16_t flags = nctohs(&packet[2]); - uint32_t la = sync_rtp_timestamp - rtp_timestamp_less_latency; // note, this might loop around in modulo. Not sure if you'll get an error! + uint32_t la = sync_rtp_timestamp - rtp_timestamp_less_latency; // note, this might + // loop around in + // modulo. Not sure if + // you'll get an error! // debug(3, "Latency derived just from the sync packet is %" PRIu32 " frames.", la); - - if ((flags == 7) || ((conn->AirPlayVersion > 0) && (conn->AirPlayVersion <= 353)) || ((conn->AirPlayVersion > 0) && (conn->AirPlayVersion >= 371))) { + + if ((flags == 7) || ((conn->AirPlayVersion > 0) && (conn->AirPlayVersion <= 353)) || + ((conn->AirPlayVersion > 0) && (conn->AirPlayVersion >= 371))) { la += config.fixedLatencyOffset; // debug(3, "A fixed latency offset of %d frames has been added, giving a latency of // " @@ -350,7 +354,8 @@ void *rtp_control_receiver(void *arg) { if (la > max_frames) { warn("An out-of-range latency request of %" PRIu32 - " frames was ignored. Must be %" PRIu32 " frames or less (44,100 frames per second). " + " frames was ignored. Must be %" PRIu32 + " frames or less (44,100 frames per second). " "Latency remains at %" PRIu32 " frames.", la, max_frames, conn->latency); } else { @@ -379,7 +384,8 @@ void *rtp_control_receiver(void *arg) { if (remote_frame_time_interval) { conn->remote_frame_rate = (1.0 * (conn->reference_timestamp - conn->initial_reference_timestamp)) / - remote_frame_time_interval; // an IEEE double calculation with a 32-bit numerator and 64-bit denominator + remote_frame_time_interval; // an IEEE double calculation with a 32-bit + // numerator and 64-bit denominator // integers conn->remote_frame_rate = conn->remote_frame_rate * (uint64_t)0x100000000; // this should just change the @@ -1040,8 +1046,9 @@ int sanitised_source_rate_information(uint32_t *frames, uint64_t *time, rtsp_con *time = (uint64_t)(0x100000000); // one second in fp form if ((conn->packet_stream_established) && (conn->initial_reference_time) && (conn->initial_reference_timestamp)) { -// uint32_t local_frames = conn->reference_timestamp - conn->initial_reference_timestamp; - uint32_t local_frames = rtp_frame_offset(conn->initial_reference_timestamp, conn->reference_timestamp); + // uint32_t local_frames = conn->reference_timestamp - conn->initial_reference_timestamp; + uint32_t local_frames = + rtp_frame_offset(conn->initial_reference_timestamp, conn->reference_timestamp); uint64_t local_time = conn->remote_reference_timestamp_time - conn->initial_reference_time; if ((local_frames == 0) || (local_time == 0) || (use_nominal_rate)) { result = 1; @@ -1073,24 +1080,24 @@ int frame_to_local_time(uint32_t timestamp, uint64_t *time, rtsp_conn_info *conn uint64_t timestamp_interval_time; uint64_t remote_time_of_timestamp; - uint32_t timestamp_interval = rtp_frame_offset(conn->reference_timestamp,timestamp); - if (timestamp_interval <= conn->input_rate*10) { // i.e. timestamp was really after the reference timestamp - timestamp_interval_time = - (timestamp_interval * time_difference) / - (frame_difference); // this is the nominal time, based on the - // fps specified between current and - // previous sync frame. + uint32_t timestamp_interval = rtp_frame_offset(conn->reference_timestamp, timestamp); + if (timestamp_interval <= + conn->input_rate * 10) { // i.e. timestamp was really after the reference timestamp + timestamp_interval_time = (timestamp_interval * time_difference) / + (frame_difference); // this is the nominal time, based on the + // fps specified between current and + // previous sync frame. remote_time_of_timestamp = conn->remote_reference_timestamp_time + timestamp_interval_time; // based on the reference timestamp time // plus the time interval calculated based // on the specified fps. } else { // i.e. timestamp was actually after the reference timestamp - timestamp_interval = rtp_frame_offset(timestamp,conn->reference_timestamp); // fix the calculation - timestamp_interval_time = - (timestamp_interval * time_difference) / - (frame_difference); // this is the nominal time, based on the - // fps specified between current and - // previous sync frame. + timestamp_interval = + rtp_frame_offset(timestamp, conn->reference_timestamp); // fix the calculation + timestamp_interval_time = (timestamp_interval * time_difference) / + (frame_difference); // this is the nominal time, based on the + // fps specified between current and + // previous sync frame. remote_time_of_timestamp = conn->remote_reference_timestamp_time - timestamp_interval_time; // based on the reference timestamp time // plus the time interval calculated based diff --git a/rtsp.c b/rtsp.c index 834e56e8..c1ba268f 100644 --- a/rtsp.c +++ b/rtsp.c @@ -507,8 +507,7 @@ fail: return 0; } -enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, - rtsp_message **the_packet) { +enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) { enum rtsp_read_request_response reply = rtsp_read_request_response_ok; ssize_t buflen = 4096; char *buf = malloc(buflen + 1); @@ -738,7 +737,7 @@ void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) } void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, - rtsp_message *resp) { + rtsp_message *resp) { debug(3, "Connection %d: OPTIONS", conn->connection_number); resp->respcode = 200; msg_add_header(resp, "Public", "ANNOUNCE, SETUP, RECORD, " @@ -747,7 +746,7 @@ void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message * } void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, - rtsp_message *resp) { + rtsp_message *resp) { debug(2, "Connection %d: TEARDOWN", conn->connection_number); // if (!rtsp_playing()) // debug(1, "This RTSP connection thread (%d) doesn't think it's playing, but " @@ -904,7 +903,7 @@ static void handle_ignore(rtsp_conn_info *conn, rtsp_message *req, rtsp_message */ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req, - __attribute__((unused)) rtsp_message *resp) { + __attribute__((unused)) rtsp_message *resp) { char *cp = req->content; int cp_left = req->contentlength; char *next; From 7c35f647467c758ce2d67ee1d85ccacf5ed53f00 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 21 Oct 2018 18:32:18 +0100 Subject: [PATCH 161/569] Fix a reporting error, allow a larger time window for legitimate flush requests. --- player.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/player.c b/player.c index f51d8aef..a374c0c9 100644 --- a/player.c +++ b/player.c @@ -465,9 +465,9 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in // now, if a flush_rtp_timestamp has been defined and the incoming timestamp is "before" it, // drop it… - if ((conn->flush_rtp_timestamp != 0) && + if ((conn->flush_rtp_timestamp != 0) && (actual_timestamp != conn->flush_rtp_timestamp) && (rtp_frame_offset(actual_timestamp, conn->flush_rtp_timestamp) < - 8820)) { // if it's less than 0.2 seconds + conn->input_rate * 10)) { // if it's less than 10 seconds debug(2, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %" PRIu32 ", flushing to " "timestamp: %" PRIu32 ".", @@ -476,9 +476,10 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in conn->initial_reference_timestamp = 0; } else { if ((conn->flush_rtp_timestamp != 0) && - (rtp_frame_offset(conn->flush_rtp_timestamp, actual_timestamp) > 8820) && - (rtp_frame_offset(conn->flush_rtp_timestamp, actual_timestamp) < 44100)) { - debug(2, "Dropping flush request"); + (rtp_frame_offset(conn->flush_rtp_timestamp, actual_timestamp) > conn->input_rate/5) && + (rtp_frame_offset(conn->flush_rtp_timestamp, actual_timestamp) < conn->input_rate)) { + // between 0.2 and 1 second + debug(2, "Dropping flush request in player_put_packet"); conn->flush_rtp_timestamp = 0; } @@ -501,7 +502,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in resend_interval = latency_based_resend_interval; if (conn->resend_interval != resend_interval) { - debug(2, "Resend interval for latency of %" PRId64 " frames is %d frames.", conn->latency, + debug(2, "Resend interval for latency of %u frames is %d frames.", conn->latency, resend_interval); conn->resend_interval = resend_interval; } @@ -861,9 +862,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { } } - if ((conn->flush_rtp_timestamp != 0) && + if ((conn->flush_rtp_timestamp != 0) && (curframe->given_timestamp != conn->flush_rtp_timestamp) && (rtp_frame_offset(curframe->given_timestamp, conn->flush_rtp_timestamp) < - 8820)) { // if it's less than 0.2 seconds + conn->input_rate * 10)) { // if it's less than ten seconds debug(2, "Dropping flushed packet in buffer_get_frame, seqno %u, timestamp %" PRIu32 ", flushing to " "timestamp: %" PRIu32 ".", @@ -876,9 +877,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { conn->initial_reference_timestamp = 0; } if ((conn->flush_rtp_timestamp != 0) && - (rtp_frame_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) > 8820) && - (rtp_frame_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) < 44100)) { - debug(2, "Dropping flush request"); + (rtp_frame_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) > conn->input_rate / 5) && + (rtp_frame_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) < conn->input_rate * 10 )) { + debug(2, "Dropping flush request in buffer_get_frame"); conn->flush_rtp_timestamp = 0; } } From 63cae88aca91a9fd794d03f092d2c0cd6d240d96 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 21 Oct 2018 18:33:47 +0100 Subject: [PATCH 162/569] Further improve the modulo 32 rtpstamp-based missing frames erro handling. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index c66718a8..55f63090 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.3d17], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d18], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 8914b68b42d1a2a281fc3b0932a343a195156316 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 21 Oct 2018 21:24:38 +0100 Subject: [PATCH 163/569] Calculate silence memory requirements properly. Remove some alarming messages. --- player.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/player.c b/player.c index a374c0c9..3898c62d 100644 --- a/player.c +++ b/player.c @@ -1832,12 +1832,14 @@ void *player_thread_func(void *arg) { free(silence); } } else if (frames_to_drop) { + /* if (frames_to_drop > 3 * config.output_rate) { warn("Shome mhistake shurely: very large number of frames to drop: %" PRId64 " -- setting it to %" PRId64 ".", frames_to_drop, 3 * config.output_rate); frames_to_drop = 3 * config.output_rate; } + */ debug(3, "%" PRId64 " frames to drop.", frames_to_drop); frames_to_drop -= inframe->length; if (frames_to_drop < 0) @@ -2062,6 +2064,7 @@ void *player_thread_func(void *arg) { if ((config.no_sync == 0) && (inframe->given_timestamp != 0) && (config.resyncthreshold > 0.0) && (abs_sync_error > config.resyncthreshold * config.output_rate)) { + /* if (abs_sync_error > 3 * config.output_rate) { warn("Very large sync error: %" PRId64 " frames, with should_be_frame: %" PRId64 @@ -2070,6 +2073,7 @@ void *player_thread_func(void *arg) { sync_error, should_be_frame, nt, current_delay, inframe->given_timestamp, reference_timestamp, should_be_frame_32); } + */ sync_error_out_of_bounds++; } else { sync_error_out_of_bounds = 0; @@ -2081,25 +2085,25 @@ void *player_thread_func(void *arg) { // sync_error_out_of_bounds, sync_error); sync_error_out_of_bounds = 0; - int filler_length = config.resyncthreshold * config.output_rate; // number of samples + int64_t filler_length = (int64_t)config.resyncthreshold * config.output_rate; // number of samples if ((sync_error > 0) && (sync_error > filler_length)) { - // debug(1, "Large positive sync error: %lld.", sync_error); + debug(1, "Large positive sync error: %" PRId64 ".", sync_error); frames_to_drop = sync_error / conn->output_sample_ratio; } else if ((sync_error < 0) && ((-sync_error) > filler_length)) { - // debug(1, "Large negative sync error: %lld. Inserting silence.", sync_error); - int silence_length = -sync_error; + debug(1, "Large negative sync error: %" PRId64 ". Inserting silence.", sync_error); + int64_t silence_length = -sync_error; if (silence_length > (filler_length * 5)) silence_length = filler_length * 5; - - char *long_silence = malloc(conn->output_bytes_per_frame * silence_length); + size_t silence_length_sized = silence_length; + char *long_silence = malloc(conn->output_bytes_per_frame * silence_length_sized); if (long_silence) { - memset(long_silence, 0, conn->output_bytes_per_frame * silence_length); - config.output->play(long_silence, silence_length); + memset(long_silence, 0, conn->output_bytes_per_frame * silence_length_sized); + config.output->play(long_silence, silence_length_sized); free(long_silence); } else { warn("Failed to allocate memory for a long_silence buffer of %d frames for a " - "sync error of %" PRId64 " frames.", - silence_length, sync_error); + "sync error of %d frames.", + silence_length_sized, sync_error); } } } else { From ab139ffc8c0c8521058583b717568f6c15030c6e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 21 Oct 2018 22:52:06 +0100 Subject: [PATCH 164/569] Fix calculation of filler length. --- player.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/player.c b/player.c index 3898c62d..2291182b 100644 --- a/player.c +++ b/player.c @@ -2085,7 +2085,7 @@ void *player_thread_func(void *arg) { // sync_error_out_of_bounds, sync_error); sync_error_out_of_bounds = 0; - int64_t filler_length = (int64_t)config.resyncthreshold * config.output_rate; // number of samples + int64_t filler_length = (int64_t)(config.resyncthreshold * config.output_rate); // number of samples if ((sync_error > 0) && (sync_error > filler_length)) { debug(1, "Large positive sync error: %" PRId64 ".", sync_error); frames_to_drop = sync_error / conn->output_sample_ratio; @@ -2098,6 +2098,7 @@ void *player_thread_func(void *arg) { char *long_silence = malloc(conn->output_bytes_per_frame * silence_length_sized); if (long_silence) { memset(long_silence, 0, conn->output_bytes_per_frame * silence_length_sized); + debug(1,"Play a silence of %d frames.",silence_length_sized); config.output->play(long_silence, silence_length_sized); free(long_silence); } else { From 77176ec4e0b03bbd7ff3fad4c55d5a6c59ec5a1a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 22 Oct 2018 20:53:44 +0000 Subject: [PATCH 165/569] See where a weird large negative delay is being calculated. --- player.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/player.c b/player.c index 2291182b..f69a563c 100644 --- a/player.c +++ b/player.c @@ -1935,8 +1935,8 @@ void *player_thread_func(void *arg) { int64_t rt, nt; rt = reference_timestamp; // uint32_t to int64_t nt = inframe->given_timestamp; // uint32_t to int64_t - rt *= conn->output_sample_ratio; - nt *= conn->output_sample_ratio; + rt = rt*conn->output_sample_ratio; + nt = nt*conn->output_sample_ratio; uint64_t local_time_now = get_absolute_time_in_fp(); // types okay // struct timespec tn; @@ -2050,7 +2050,7 @@ void *player_thread_func(void *arg) { // if negative, the packet will be early -- the delay is less than expected. sync_error = - delay - (conn->latency * conn->output_sample_ratio + + delay - ((int64_t)conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate)); // int64_t from int64_t - int32_t, so okay @@ -2090,7 +2090,8 @@ void *player_thread_func(void *arg) { debug(1, "Large positive sync error: %" PRId64 ".", sync_error); frames_to_drop = sync_error / conn->output_sample_ratio; } else if ((sync_error < 0) && ((-sync_error) > filler_length)) { - debug(1, "Large negative sync error: %" PRId64 ". Inserting silence.", sync_error); + debug(1, "Large negative sync error: %" PRId64 " with should_be_frame_32 of %" PRIu32 + ", nt of %" PRId64 " and current_delay of %" PRId64 ".", sync_error, should_be_frame_32, nt, current_delay); int64_t silence_length = -sync_error; if (silence_length > (filler_length * 5)) silence_length = filler_length * 5; From 3cb359c747e9ea606e133fac919fe44f7e0896a8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 23 Oct 2018 10:24:40 +0000 Subject: [PATCH 166/569] Tighten up integer promotion checks to calculate large delays properly. Fix input rate calculation restarting. --- audio_alsa.c | 6 +++--- player.c | 50 ++++++++++++++++++++++++++++++-------------------- player.h | 6 ++++-- rtp.c | 45 +++++++++++++++++++++++++-------------------- 4 files changed, 62 insertions(+), 45 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 056fe7a7..f0fae65a 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -899,13 +899,13 @@ int delay(long *the_delay) { measurement_data_is_valid = 0; if (dac_state == SND_PCM_STATE_PREPARED) { - debug(1, "delay not available -- state is SND_PCM_STATE_PREPARED"); + debug(2, "delay not available -- state is SND_PCM_STATE_PREPARED"); } else { if (dac_state == SND_PCM_STATE_XRUN) { - debug(1, "delay not available -- state is SND_PCM_STATE_XRUN"); + debug(2, "delay not available -- state is SND_PCM_STATE_XRUN"); } else { - debug(1, "Error -- ALSA delay(): bad state: %d.", dac_state); + debug(2, "Error -- ALSA delay(): bad state: %d.", dac_state); } if ((derr = snd_pcm_prepare(alsa_handle))) { snd_pcm_recover(alsa_handle, derr, 1); diff --git a/player.c b/player.c index f69a563c..c107bc34 100644 --- a/player.c +++ b/player.c @@ -99,13 +99,20 @@ // static abuf_t audio_buffer[BUFFER_FRAMES]; #define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES) -uint32_t rtp_frame_offset(uint32_t from, uint32_t to) { +uint32_t modulo_32_offset(uint32_t from, uint32_t to) { if (from <= to) return to - from; else return UINT32_MAX - from + to + 1; } +uint64_t modulo_64_offset(uint64_t from, uint64_t to) { + if (from <= to) + return to - from; + else + return UINT64_MAX - from + to + 1; +} + void do_flush(uint32_t timestamp, rtsp_conn_info *conn); static void ab_resync(rtsp_conn_info *conn) { @@ -223,6 +230,15 @@ static inline int seq32_order(uint32_t a, uint32_t b) { return (C & 0x80000000) == 0; } +void reset_input_flow_metrics(rtsp_conn_info *conn) { + conn->play_number_after_flush = 0; + conn->packet_count_since_flush = 0; + conn->packet_stream_established = 0; + conn->input_frame_rate_starting_point_is_valid = 0; + conn->initial_reference_time = 0; + conn->initial_reference_timestamp = 0; +} + static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_conn_info *conn) { // parameters: where the decoded stuff goes, its length in samples, // the incoming packet, the length of the incoming packet in bytes @@ -466,7 +482,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in // drop it… if ((conn->flush_rtp_timestamp != 0) && (actual_timestamp != conn->flush_rtp_timestamp) && - (rtp_frame_offset(actual_timestamp, conn->flush_rtp_timestamp) < + (modulo_32_offset(actual_timestamp, conn->flush_rtp_timestamp) < conn->input_rate * 10)) { // if it's less than 10 seconds debug(2, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %" PRIu32 ", flushing to " @@ -476,8 +492,8 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in conn->initial_reference_timestamp = 0; } else { if ((conn->flush_rtp_timestamp != 0) && - (rtp_frame_offset(conn->flush_rtp_timestamp, actual_timestamp) > conn->input_rate/5) && - (rtp_frame_offset(conn->flush_rtp_timestamp, actual_timestamp) < conn->input_rate)) { + (modulo_32_offset(conn->flush_rtp_timestamp, actual_timestamp) > conn->input_rate/5) && + (modulo_32_offset(conn->flush_rtp_timestamp, actual_timestamp) < conn->input_rate)) { // between 0.2 and 1 second debug(2, "Dropping flush request in player_put_packet"); conn->flush_rtp_timestamp = 0; @@ -863,7 +879,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { } if ((conn->flush_rtp_timestamp != 0) && (curframe->given_timestamp != conn->flush_rtp_timestamp) && - (rtp_frame_offset(curframe->given_timestamp, conn->flush_rtp_timestamp) < + (modulo_32_offset(curframe->given_timestamp, conn->flush_rtp_timestamp) < conn->input_rate * 10)) { // if it's less than ten seconds debug(2, "Dropping flushed packet in buffer_get_frame, seqno %u, timestamp %" PRIu32 ", flushing to " @@ -877,8 +893,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { conn->initial_reference_timestamp = 0; } if ((conn->flush_rtp_timestamp != 0) && - (rtp_frame_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) > conn->input_rate / 5) && - (rtp_frame_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) < conn->input_rate * 10 )) { + (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) > conn->input_rate / 5) && + (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) < conn->input_rate * 10 )) { debug(2, "Dropping flush request in buffer_get_frame"); conn->flush_rtp_timestamp = 0; } @@ -1200,9 +1216,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (notified_buffer_empty == 0) { debug(3, "Buffers exhausted."); notified_buffer_empty = 1; - conn->initial_reference_time = 0; - conn->initial_reference_timestamp = 0; - conn->input_frame_rate_starting_point_is_valid = 0; + reset_input_flow_metrics(conn); } do_wait = 1; } @@ -2009,7 +2023,7 @@ void *player_thread_func(void *arg) { current_delay = l_delay; if (resp == 0) { // no error if (current_delay < 0) { - debug(1, "Underrun of %lld frames reported, but ignored.", current_delay); + debug(2, "Underrun of %lld frames reported, but ignored.", current_delay); current_delay = 0; // could get a negative value if there was underrun, but ignore it. } @@ -2087,10 +2101,11 @@ void *player_thread_func(void *arg) { int64_t filler_length = (int64_t)(config.resyncthreshold * config.output_rate); // number of samples if ((sync_error > 0) && (sync_error > filler_length)) { - debug(1, "Large positive sync error: %" PRId64 ".", sync_error); + debug(2, "Large positive sync error: %" PRId64 ".", sync_error); frames_to_drop = sync_error / conn->output_sample_ratio; + reset_input_flow_metrics(conn); } else if ((sync_error < 0) && ((-sync_error) > filler_length)) { - debug(1, "Large negative sync error: %" PRId64 " with should_be_frame_32 of %" PRIu32 + debug(2, "Large negative sync error: %" PRId64 " with should_be_frame_32 of %" PRIu32 ", nt of %" PRId64 " and current_delay of %" PRId64 ".", sync_error, should_be_frame_32, nt, current_delay); int64_t silence_length = -sync_error; if (silence_length > (filler_length * 5)) @@ -2107,6 +2122,7 @@ void *player_thread_func(void *arg) { "sync error of %d frames.", silence_length_sized, sync_error); } + reset_input_flow_metrics(conn); } } else { @@ -2744,13 +2760,7 @@ void do_flush(uint32_t timestamp, rtsp_conn_info *conn) { // if (timestamp!=0) conn->flush_rtp_timestamp = timestamp; // flush all packets up to (and including?) this // conn->play_segment_reference_frame = 0; - conn->play_number_after_flush = 0; - conn->packet_count_since_flush = 0; - conn->packet_stream_established = 0; - conn->input_frame_rate_starting_point_is_valid = 0; - conn->initial_reference_time = 0; - conn->initial_reference_timestamp = 0; - + reset_input_flow_metrics(conn); debug_mutex_unlock(&conn->flush_mutex, 3); #ifdef CONFIG_METADATA diff --git a/player.h b/player.h index a7531b90..2f1a3284 100644 --- a/player.h +++ b/player.h @@ -218,7 +218,8 @@ typedef struct { // slightly above or below. int local_to_remote_time_gradient_sample_count; // the number of samples used to calculate the // gradient - uint64_t local_to_remote_time_difference; // used to switch between local and remote clocks + // add the following to the local time to get the remote time modulo 2^64 + uint64_t local_to_remote_time_difference; // used to switch between local and remote clocks uint64_t local_to_remote_time_difference_measurement_time; // when the above was calculated int last_stuff_request; @@ -242,7 +243,8 @@ typedef struct { void *dapo_private_storage; // this is used for compatibility, if dacp stuff isn't enabled. } rtsp_conn_info; -uint32_t rtp_frame_offset(uint32_t from, uint32_t to); +uint32_t modulo_32_offset(uint32_t from, uint32_t to); +uint64_t modulo_64_offset(uint64_t from, uint64_t to); int player_play(rtsp_conn_info *conn); int player_stop(rtsp_conn_info *conn); diff --git a/rtp.c b/rtp.c index eb86da48..35b2a2bc 100644 --- a/rtp.c +++ b/rtp.c @@ -1042,21 +1042,23 @@ const int use_nominal_rate = 0; // specify whether to use the nominal input rate int sanitised_source_rate_information(uint32_t *frames, uint64_t *time, rtsp_conn_info *conn) { int result = 1; - *frames = conn->input_rate; - *time = (uint64_t)(0x100000000); // one second in fp form + uint32_t fs = conn->input_rate; + *frames = fs; + uint64_t one_fp = (uint64_t)(0x100000000); // one second in fp form + *time = one_fp; if ((conn->packet_stream_established) && (conn->initial_reference_time) && (conn->initial_reference_timestamp)) { // uint32_t local_frames = conn->reference_timestamp - conn->initial_reference_timestamp; uint32_t local_frames = - rtp_frame_offset(conn->initial_reference_timestamp, conn->reference_timestamp); + modulo_32_offset(conn->initial_reference_timestamp, conn->reference_timestamp); uint64_t local_time = conn->remote_reference_timestamp_time - conn->initial_reference_time; if ((local_frames == 0) || (local_time == 0) || (use_nominal_rate)) { result = 1; } else { - double calculated_frame_rate = ((1.0 * local_frames) / local_time) * (uint64_t)0x100000000; - if (((calculated_frame_rate / conn->input_rate) > 1.001) || - ((calculated_frame_rate / conn->input_rate) < 0.999)) { - // debug(1, "input frame rate out of bounds at %.2f fps.", calculated_frame_rate); + double calculated_frame_rate = ((1.0 * local_frames) / local_time) * one_fp; + if (((calculated_frame_rate / conn->input_rate) > 1.002) || + ((calculated_frame_rate / conn->input_rate) < 0.998)) { + debug(1, "input frame rate out of bounds at %.2f fps.", calculated_frame_rate); result = 1; } else { *frames = local_frames; @@ -1080,24 +1082,24 @@ int frame_to_local_time(uint32_t timestamp, uint64_t *time, rtsp_conn_info *conn uint64_t timestamp_interval_time; uint64_t remote_time_of_timestamp; - uint32_t timestamp_interval = rtp_frame_offset(conn->reference_timestamp, timestamp); + uint32_t timestamp_interval = modulo_32_offset(conn->reference_timestamp, timestamp); if (timestamp_interval <= - conn->input_rate * 10) { // i.e. timestamp was really after the reference timestamp + conn->input_rate * 3600) { // i.e. timestamp was really after the reference timestamp timestamp_interval_time = (timestamp_interval * time_difference) / - (frame_difference); // this is the nominal time, based on the - // fps specified between current and - // previous sync frame. + frame_difference; // this is the nominal time, based on the + // fps specified between current and + // previous sync frame. remote_time_of_timestamp = conn->remote_reference_timestamp_time + timestamp_interval_time; // based on the reference timestamp time // plus the time interval calculated based // on the specified fps. - } else { // i.e. timestamp was actually after the reference timestamp + } else { // i.e. timestamp was actually before the reference timestamp timestamp_interval = - rtp_frame_offset(timestamp, conn->reference_timestamp); // fix the calculation + modulo_32_offset(timestamp, conn->reference_timestamp); // fix the calculation timestamp_interval_time = (timestamp_interval * time_difference) / - (frame_difference); // this is the nominal time, based on the - // fps specified between current and - // previous sync frame. + frame_difference; // this is the nominal time, based on the + // fps specified between current and + // previous sync frame. remote_time_of_timestamp = conn->remote_reference_timestamp_time - timestamp_interval_time; // based on the reference timestamp time // plus the time interval calculated based @@ -1122,15 +1124,18 @@ int local_time_to_frame(uint64_t time, uint32_t *frame, rtsp_conn_info *conn) { uint64_t time_interval; // here, we calculate the time interval, in terms of remote time - if (remote_time >= conn->remote_reference_timestamp_time) + uint64_t offset = modulo_64_offset(conn->remote_reference_timestamp_time, remote_time); + int reference_time_was_earlier = (offset <= (uint64_t)0x100000000 * 3600); + if (reference_time_was_earlier) // if we haven't had a reference within the last hour, it'll be + // taken as afterwards time_interval = remote_time - conn->remote_reference_timestamp_time; else time_interval = conn->remote_reference_timestamp_time - remote_time; // now, convert the remote time interval into frames using the frame rate we have observed or // which has been nominated - int64_t frame_interval = (time_interval * frame_difference) / time_difference; - if (remote_time >= conn->remote_reference_timestamp_time) { + uint32_t frame_interval = (time_interval * frame_difference) / time_difference; + if (reference_time_was_earlier) { // debug(1,"Frame interval is %" PRId64 " frames.",frame_interval); *frame = (conn->reference_timestamp + frame_interval); } else { From 06716b70a9d7d073a1b1c02f1567be370096affb Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 23 Oct 2018 10:29:09 +0000 Subject: [PATCH 167/569] Improve handling of network delays and outages -- more robust error handling. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 55f63090..2968d410 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.3d18], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d19], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 3ae8c4e9bc08f0324576c148a48c670370773cc5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 23 Oct 2018 12:15:00 +0100 Subject: [PATCH 168/569] Quieten an unnecessary debug message. --- player.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player.c b/player.c index c107bc34..a4b14bbb 100644 --- a/player.c +++ b/player.c @@ -2114,7 +2114,7 @@ void *player_thread_func(void *arg) { char *long_silence = malloc(conn->output_bytes_per_frame * silence_length_sized); if (long_silence) { memset(long_silence, 0, conn->output_bytes_per_frame * silence_length_sized); - debug(1,"Play a silence of %d frames.",silence_length_sized); + debug(2,"Play a silence of %d frames.",silence_length_sized); config.output->play(long_silence, silence_length_sized); free(long_silence); } else { From 4faa654ba995b2ef2adbfe0664bd074024f6ebaf Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 28 Oct 2018 14:20:22 +0000 Subject: [PATCH 169/569] Remove a signed/unsigned comparison warning --- player.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player.c b/player.c index a4b14bbb..436eeb0c 100644 --- a/player.c +++ b/player.c @@ -1136,7 +1136,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { int64_t frame_gap = (lead_time * config.output_rate) >> 32; // debug(1,"%d frames needed.",frame_gap); while (frame_gap > 0) { - size_t fs = config.output_rate / 10; + ssize_t fs = config.output_rate / 10; if (fs > frame_gap) fs = frame_gap; From b91df8ec735509d40041132d8eae0a2c64d0bc0e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 30 Oct 2018 19:49:32 +0000 Subject: [PATCH 170/569] Start using libraries to be able to compile other people's or automatically generated sources using standard compilation settings. --- .gitignore | 1 + Makefile.am | 22 ++++++++++++++++++---- configure.ac | 1 + 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 09c6aa66..6f76aec5 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ shairport-sync.core /org.gnome.ShairportSync.service /dbus-interface.* /mpris-interface.* +/lib_*.* # Some eclipse project files .cproject diff --git a/Makefile.am b/Makefile.am index feaf800b..9e6b4e91 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,5 @@ SUBDIRS = man +ARFLAGS = cr bin_PROGRAMS = shairport-sync @@ -9,6 +10,8 @@ shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp. if BUILD_FOR_FREEBSD AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\" -O2 AM_CFLAGS = -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\" -O2 + lib_dbus_interface_a_CFLAGS = -pthread -O2 + lib_mpris_interface_a_CFLAGS = -pthread -O2 else if BUILD_FOR_OPENBSD AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\" @@ -16,6 +19,7 @@ if BUILD_FOR_OPENBSD else AM_CXXFLAGS = -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\" AM_CFLAGS = -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\" + lib_dbus_interface_a_CFLAGS = -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -Wno-unused-parameter -pthread -DSYSCONFDIR=\"$(sysconfdir)\" endif endif @@ -96,9 +100,14 @@ endif BUILT_SOURCES = noinst_HEADERS = CLEANFILES = +shairport_sync_LDADD = +noinst_LIBRARIES = if USE_DBUS -shairport_sync_SOURCES += dbus-service.c dbus-interface.c +shairport_sync_LDADD += lib_dbus_interface.a +noinst_LIBRARIES += lib_dbus_interface.a +lib_dbus_interface_a_SOURCES = dbus-interface.c +shairport_sync_SOURCES += dbus-service.c BUILT_SOURCES += dbus-interface.h dbus-interface.c # We don't want to install this header noinst_HEADERS += $(BUILT_SOURCES) @@ -110,7 +119,10 @@ dbus-interface.h dbus-interface.c: org.gnome.ShairportSync.xml endif if USE_MPRIS -shairport_sync_SOURCES += mpris-service.c mpris-interface.c +shairport_sync_LDADD += lib_mpris_interface.a +noinst_LIBRARIES += lib_mpris_interface.a +lib_mpris_interface_a_SOURCES = mpris-interface.c +shairport_sync_SOURCES += mpris-service.c BUILT_SOURCES += mpris-interface.h mpris-interface.c # We don't want to install this header noinst_HEADERS += $(BUILT_SOURCES) @@ -126,13 +138,15 @@ noinst_PROGRAMS = if USE_DBUS_CLIENT #Make it, but don't install it anywhere noinst_PROGRAMS += shairport-sync-dbus-test-client -shairport_sync_dbus_test_client_SOURCES = dbus-interface.c dbus-interface.h shairport-sync-dbus-test-client.c +shairport_sync_dbus_test_client_SOURCES = shairport-sync-dbus-test-client.c +shairport_sync_dbus_test_client_LDADD = lib_dbus_interface.a endif if USE_MPRIS_CLIENT #Make it, but don't install it anywhere noinst_PROGRAMS += shairport-sync-mpris-test-client -shairport_sync_mpris_test_client_SOURCES = mpris-interface.c mpris-interface.h shairport-sync-mpris-test-client.c +shairport_sync_mpris_test_client_SOURCES = shairport-sync-mpris-test-client.c +shairport_sync_mpris_test_client_LDADD = lib_mpris_interface.a endif install-exec-hook: diff --git a/configure.ac b/configure.ac index 2968d410..65e362fd 100644 --- a/configure.ac +++ b/configure.ac @@ -6,6 +6,7 @@ AC_INIT([shairport-sync], [3.3d19], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) +AC_PROG_RANLIB # Derived from the Avahi configure.ac file # Specifying the OS type, defaulting to linux. From 580d75a6edb35a58a91ba350b03335758488ffc5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 30 Oct 2018 19:58:34 +0000 Subject: [PATCH 171/569] Make the lib settings standard for all builds, not individual for Linux/FreeBSD/OpenBSD. Let's hope. --- Makefile.am | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile.am b/Makefile.am index 9e6b4e91..3cc5534d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,8 @@ SUBDIRS = man ARFLAGS = cr +lib_dbus_interface_a_CFLAGS = -pthread +lib_mpris_interface_a_CFLAGS = -pthread + bin_PROGRAMS = shairport-sync @@ -8,10 +11,8 @@ bin_PROGRAMS = shairport-sync shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c loudness.c if BUILD_FOR_FREEBSD - AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\" -O2 - AM_CFLAGS = -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\" -O2 - lib_dbus_interface_a_CFLAGS = -pthread -O2 - lib_mpris_interface_a_CFLAGS = -pthread -O2 + AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\" + AM_CFLAGS = -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\" else if BUILD_FOR_OPENBSD AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\" @@ -19,7 +20,6 @@ if BUILD_FOR_OPENBSD else AM_CXXFLAGS = -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\" AM_CFLAGS = -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\" - lib_dbus_interface_a_CFLAGS = -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -Wno-unused-parameter -pthread -DSYSCONFDIR=\"$(sysconfdir)\" endif endif From 8a6eb02d59f56b0f03f7dcf3626d96789c1f0671 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 30 Oct 2018 20:57:42 +0000 Subject: [PATCH 172/569] Chanmge order so that it will compile in openbsd. --- shairport.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shairport.c b/shairport.c index 5e36e911..d4a29410 100644 --- a/shairport.c +++ b/shairport.c @@ -25,6 +25,8 @@ * OTHER DEALINGS IN THE SOFTWARE. */ +#include +#include #include #include #include @@ -35,9 +37,7 @@ #include #include #include -#include #include -#include #include #include From cd71b3f727f63720816a308e9389bcb2f9425414 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 31 Oct 2018 16:33:16 +0000 Subject: [PATCH 173/569] Enhance error messages to warn if the alsa output device doesn't recover. --- audio_alsa.c | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index f0fae65a..48f6a650 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -883,7 +883,11 @@ int delay(long *the_delay) { if (reply != 0) { debug(1, "Error %d in delay(): \"%s\". Delay reported is %d frames.", reply, snd_strerror(reply), *the_delay); - snd_pcm_recover(alsa_handle, reply, 1); + derr = snd_pcm_recover(alsa_handle, reply, 1); + if (derr < 0) + warn("Error %d -- could not clear an error after attempting delay(): \"%s\".", derr, + snd_strerror(derr)); + frame_index = 0; measurement_data_is_valid = 0; } else { @@ -908,8 +912,11 @@ int delay(long *the_delay) { debug(2, "Error -- ALSA delay(): bad state: %d.", dac_state); } if ((derr = snd_pcm_prepare(alsa_handle))) { - snd_pcm_recover(alsa_handle, derr, 1); debug(1, "Error preparing after delay error: \"%s\".", snd_strerror(derr)); + derr = snd_pcm_recover(alsa_handle, derr, 1); + if (derr < 0) + warn("Error %d -- could not clear an error after attempting to recover following a delay(): \"%s\".", derr, + snd_strerror(derr)); } } } @@ -957,8 +964,11 @@ static int play(void *buf, int samples) { int err, err2; if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) { if ((err = snd_pcm_prepare(alsa_handle))) { - snd_pcm_recover(alsa_handle, err, 1); debug(1, "Error preparing after underrun: \"%s\".", snd_strerror(err)); + err = snd_pcm_recover(alsa_handle, err, 1); + if (err < 0) + warn("Error %d -- could not clear an error after detecting underrun in play(): \"%s\".", err, + snd_strerror(err)); } frame_index = 0; // we'll be starting over measurement_data_is_valid = 0; @@ -975,7 +985,10 @@ static int play(void *buf, int samples) { measurement_data_is_valid = 0; debug(1, "Error %d writing %d samples in play(): \"%s\".", err, samples, snd_strerror(err)); - snd_pcm_recover(alsa_handle, err, 1); + err = snd_pcm_recover(alsa_handle, err, 1); + if (err < 0) + warn("Error %d -- could not clear an error after attempting to write %d samples in play(): \"%s\".", err, samples, + snd_strerror(err)); } if (frame_index == 0) { frames_sent_for_playing = samples; @@ -989,9 +1002,12 @@ static int play(void *buf, int samples) { long fl = 0; err2 = snd_pcm_delay(alsa_handle, &fl); if (err2 != 0) { - debug(1, "Error %d in delay(): \"%s\". Delay reported is %d frames.", err2, + debug(1, "Error %d in delay in play(): \"%s\". Delay reported is %d frames.", err2, snd_strerror(err2), fl); - snd_pcm_recover(alsa_handle, err2, 1); + err2 = snd_pcm_recover(alsa_handle, err2, 1); + if (err2 < 0) + warn("Error %d -- could not clear an error after checking delay in play(): \"%s\".", err2, + snd_strerror(err2)); frame_index = 0; measurement_data_is_valid = 0; } else { @@ -1016,8 +1032,11 @@ static int play(void *buf, int samples) { debug(1, "Error -- ALSA device in incorrect state (%d) for play.", snd_pcm_state(alsa_handle)); if ((err = snd_pcm_prepare(alsa_handle))) { - snd_pcm_recover(alsa_handle, err, 1); debug(1, "Error preparing after play error: \"%s\".", snd_strerror(err)); + err2 = snd_pcm_recover(alsa_handle, err, 1); + if (err2 < 0) + warn("Error %d -- could not clear an error after reporting ALSA device in incorrect state for play: \"%s\".", err2, + snd_strerror(err2)); } frame_index = 0; measurement_data_is_valid = 0; From ee6ffb54d540608dd07c95c5b1dc73d6378f7d76 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 1 Nov 2018 08:50:37 +0000 Subject: [PATCH 174/569] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 65e362fd..cb19bb5b 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.3d19], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d20], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 8e0c52d822c06299ba917f5c3592bc40d1b3a420 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 1 Nov 2018 09:03:52 +0000 Subject: [PATCH 175/569] Update RELEASENOTES.md --- RELEASENOTES.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 316282d9..23d26aab 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,4 +1,14 @@ +Version 3.3d20 +==== +**Enhancements** +* The code that deals with packet loss resulting from network problems has been completely rewritten. The reason is that, while the old code worked quite well, it could be overwhelmed if the network problems were very serious. The new code is simpler and more robust in testing so far. This code interacts with the code for flushing audio before and after a play session, so it may have introduced regressions. +* The build instructions have been changed to avoid compiler warnings coming from automatically-generated code produced for the D-Bus-based interfaces. +* Warnings are now logged if the ALSA subsystem fails to recover properly after an error has been cleared. + +**Bug Fix** +* A compilation problem in OpenBSD has been fixed by changing the order of some include files. + Version 3.3d16 ==== **Enhancement** From 35860f7bb474c43eeec2aa7a4f2135c9d1364783 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 1 Nov 2018 11:06:32 +0000 Subject: [PATCH 176/569] Stop multiple calls to gdbus_codegen on parallel makes --- Makefile.am | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile.am b/Makefile.am index 3cc5534d..c4f1911f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -116,6 +116,7 @@ CLEANFILES += $(BUILT_SOURCES) dbus-interface.h dbus-interface.c: org.gnome.ShairportSync.xml gdbus-codegen --interface-prefix org.gnome --generate-c-code dbus-interface org.gnome.ShairportSync.xml +dbus-interface.h: dbus-interface.c endif if USE_MPRIS @@ -131,6 +132,7 @@ CLEANFILES += $(BUILT_SOURCES) mpris-interface.h mpris-interface.c: org.mpris.MediaPlayer2.xml gdbus-codegen --interface-prefix org.mpris --generate-c-code mpris-interface org.mpris.MediaPlayer2.xml +mpris-interface.h: mpris-interface.c endif noinst_PROGRAMS = From 06c45c497024f10e451c2be70346cbe1acaef1b8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 1 Nov 2018 11:28:22 +0000 Subject: [PATCH 177/569] Remove a few unused functions. --- player.c | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/player.c b/player.c index 436eeb0c..43461bf3 100644 --- a/player.c +++ b/player.c @@ -165,14 +165,6 @@ static inline seq_t SUCCESSOR(seq_t x) { return p; } -// this is not used -static inline seq_t PREDECESSOR(seq_t x) { - uint32_t p = (x & 0xffff) + 0x10000; - p -= 1; - p = p & 0xffff; - return p; -} - // used in seq_diff and seq_order // anything with ORDINATE in it must be proctected by the ab_mutex @@ -211,25 +203,6 @@ static inline seq_t seq_sum(seq_t a, seq_t b) { return r; } -// now for 32-bit wrapping in timestamps - -// this returns true if the second arg is strictly after the first -// on the assumption that the gap between them is never greater than (2^31)-1 -// Represent a and b in 64 bits -static inline int seq32_order(uint32_t a, uint32_t b) { - if (a == b) - return 0; - int64_t A = a & 0xffffffff; - int64_t B = b & 0xffffffff; - int64_t C = B - A; - // if bit 31 is set, it means either b is before (i.e. less than) a or - // b is (2^31)-1 ahead of a. - - // If we assume the gap between b and a should never reach 2 billion, then - // bit 31 == 0 means b is strictly after a - return (C & 0x80000000) == 0; -} - void reset_input_flow_metrics(rtsp_conn_info *conn) { conn->play_number_after_flush = 0; conn->packet_count_since_flush = 0; @@ -1267,16 +1240,6 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { return curframe; } -static inline short shortmean(short a, short b) { - long al = (long)a; - long bl = (long)b; - long longmean = (al + bl) / 2; - short r = (short)longmean; - if (r != longmean) - debug(1, "Error calculating average of two shorts"); - return r; -} - static inline int32_t mean_32(int32_t a, int32_t b) { int64_t al = a; int64_t bl = b; From 203f02604d153db33aae2739df5af6e71f167cec Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 1 Nov 2018 12:56:32 +0000 Subject: [PATCH 178/569] Improve build instruction to avoid rebuilding stuff when there has been no change. --- Makefile.am | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index c4f1911f..7106f35a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -114,9 +114,10 @@ noinst_HEADERS += $(BUILT_SOURCES) # Correctly clean the generated headers, but keep the xml description CLEANFILES += $(BUILT_SOURCES) -dbus-interface.h dbus-interface.c: org.gnome.ShairportSync.xml +dbus-interface.c: org.gnome.ShairportSync.xml gdbus-codegen --interface-prefix org.gnome --generate-c-code dbus-interface org.gnome.ShairportSync.xml dbus-interface.h: dbus-interface.c + touch dbus-interface.h endif if USE_MPRIS @@ -130,9 +131,10 @@ noinst_HEADERS += $(BUILT_SOURCES) # Correctly clean the generated headers, but keep the xml description CLEANFILES += $(BUILT_SOURCES) -mpris-interface.h mpris-interface.c: org.mpris.MediaPlayer2.xml +mpris-interface.c: org.mpris.MediaPlayer2.xml gdbus-codegen --interface-prefix org.mpris --generate-c-code mpris-interface org.mpris.MediaPlayer2.xml mpris-interface.h: mpris-interface.c + touch mpris-interface.h endif noinst_PROGRAMS = From 2f1f259d03928542a5b1fef9de6fb354de9b5f5b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 3 Nov 2018 12:46:17 +0000 Subject: [PATCH 179/569] Use "/dev/urandom" instead of "/dev/random" to generate a nonce for AirPlay passwords. --- rtsp.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rtsp.c b/rtsp.c index c1ba268f..ab097a74 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1784,12 +1784,12 @@ static void apple_challenge(int fd, rtsp_message *req, rtsp_message *resp) { static char *make_nonce(void) { uint8_t random[8]; - int fd = open("/dev/random", O_RDONLY); + int fd = open("/dev/urandom", O_RDONLY); if (fd < 0) - die("could not open /dev/random!"); + die("could not open /dev/urandom!"); // int ignore = if (read(fd, random, sizeof(random)) != sizeof(random)) - debug(1, "Error reading /dev/random"); + debug(1, "Error reading /dev/urandom"); close(fd); return base64_enc(random, 8); // returns a pointer to malloc'ed memory } From 66e1dde37c06dda52712d2b3200cecee58043d00 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 3 Nov 2018 12:47:55 +0000 Subject: [PATCH 180/569] Use "/dev/urandom" instead of "/dev/random" to generate a nonce for AirPlay passwords. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index cb19bb5b..335f2a6e 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.3d20], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d21], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 48ab075a1348d3fe47a92f3a134fefbe6acfd282 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 3 Nov 2018 12:54:58 +0000 Subject: [PATCH 181/569] Update RELEASENOTES.md --- RELEASENOTES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 23d26aab..927b7f65 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,4 +1,9 @@ +Version 3.3d21 +==== +**Enhancements** +* Use `/dev/urandom` rather than `/dev/random` as a source of some kind of randomness for the crtptographic nonce used in AirPlay password exchange, as `/dev/random` blocks occasionally. Please see [here](https://unix.stackexchange.com/questions/324209/when-to-use-dev-random-vs-dev-urandom) for a discussion of the merits of both. The effect of `/dev/random`'s blocking on Shairport Sync was to make the source of randomness somewhat less random. (By the way, you should never use an important password as an AirPlay password for Shairport Sync -- it is stored in Shairport Sync's configuration file in plain text.) + Version 3.3d20 ==== **Enhancements** From dba48ddc35faa5667b442ff1df1a1c6845d23470 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 3 Nov 2018 13:00:15 +0000 Subject: [PATCH 182/569] Update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 45309fd3..0800ae38 100644 --- a/README.md +++ b/README.md @@ -303,7 +303,11 @@ general = // ... other general settings }; ``` -(Remember, anything preceded by `//` is a comment and will have no effect on the setting of Shairport Sync.) No backend is specified here, so it will default to the `alsa` backend if more than one back end has been compiled. To route the output to PulseAudio, add: +(Remember, anything preceded by `//` is a comment and will have no effect on the setting of Shairport Sync.) + +**Important:** You should *never* use an important password as the AirPlay password for a Shairport Sync player -- the password is stored in Shairport Sync's configuration file in plain text and is thus completely vulnerable. + +No backend is specified here, so it will default to the `alsa` backend if more than one back end has been compiled. To route the output to PulseAudio, add: ``` output_backend = "pa"; From cb38e90e20db37d062b7b349689c180dac9dbae9 Mon Sep 17 00:00:00 2001 From: Till Zimmermann Date: Sat, 3 Nov 2018 14:34:12 +0100 Subject: [PATCH 183/569] Small logic fix Fixes #767 --- mqtt.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mqtt.c b/mqtt.c index 6d160e81..520e17d4 100644 --- a/mqtt.c +++ b/mqtt.c @@ -60,13 +60,14 @@ void on_message(__attribute__((unused)) struct mosquitto *mosq, int it = 0; // send command if it's a valid one - while (commands[it++] != NULL) { + while (commands[it] != NULL) { if ((size_t)msg->payloadlen >= strlen(commands[it]) && strncmp(msg->payload, commands[it], strlen(commands[it])) == 0) { debug(1, "[MQTT]: DACP Command: %s\n", commands[it]); send_simple_dacp_command(commands[it]); break; } + it++; } } From 1ee63bb2d22e05b045af746cffb93578150830ec Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 8 Nov 2018 11:25:24 +0000 Subject: [PATCH 184/569] Investigating alternate timing approaches. --- audio_alsa.c | 80 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 48f6a650..37e6f1ca 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -74,11 +74,13 @@ audio_output audio_alsa = { static pthread_mutex_t alsa_mutex = PTHREAD_MUTEX_INITIALIZER; +static snd_output_t *output = NULL; static unsigned int desired_sample_rate; static enum sps_format_t sample_format; static snd_pcm_t *alsa_handle = NULL; static snd_pcm_hw_params_t *alsa_params = NULL; +static snd_pcm_sw_params_t *alsa_swparams = NULL; static snd_ctl_t *ctl = NULL; static snd_ctl_elem_id_t *elem_id = NULL; static snd_mixer_t *alsa_mix_handle = NULL; @@ -194,6 +196,9 @@ void do_snd_mixer_selem_set_playback_dB_all(snd_mixer_elem_t *mix_elem, double v } static int init(int argc, char **argv) { + snd_output_stdio_attach(&output, stdout, 0); + + // debug(2,"audio_alsa init called."); int response = 0; // this will be what we return to the caller. const char *str; @@ -502,6 +507,7 @@ static int init(int argc, char **argv) { static void deinit(void) { // debug(2,"audio_alsa deinit called."); stop(); + } int open_alsa_device(void) { @@ -536,7 +542,8 @@ int open_alsa_device(void) { return (-10); snd_pcm_hw_params_alloca(&alsa_params); - + snd_pcm_sw_params_alloca(&alsa_swparams); + ret = snd_pcm_hw_params_any(alsa_handle, alsa_params); if (ret < 0) { warn("audio_alsa: Broken configuration for device \"%s\": no configurations " @@ -691,7 +698,36 @@ int open_alsa_device(void) { snd_strerror(ret)); return -9; } + + ret = snd_pcm_sw_params_current(alsa_handle, alsa_swparams); + if (ret < 0) { + warn("audio_alsa: Unable to get current sw parameters for device \"%s\": %s.", alsa_out_dev, + snd_strerror(ret)); + return -10; + } + ret = snd_pcm_sw_params_set_tstamp_mode(alsa_handle, alsa_swparams, SND_PCM_TSTAMP_ENABLE); + if (ret < 0) { + warn("audio_alsa: Can't enable timestamp mode of device: \"%s\": %s.", alsa_out_dev, + snd_strerror(ret)); + return -11; + } + + ret = snd_pcm_sw_params_set_tstamp_type(alsa_handle, alsa_swparams, SND_PCM_TSTAMP_TYPE_MONOTONIC); + if (ret < 0) { + warn("audio_alsa: Can't set timestamp type of device: \"%s\": %s.", alsa_out_dev, + snd_strerror(ret)); + return -12; + } + + /* write the sw parameters */ + ret = snd_pcm_sw_params(alsa_handle, alsa_swparams); + if (ret < 0) { + warn("audio_alsa: Unable to set swparams_p of device: \"%s\": %s.", alsa_out_dev, + snd_strerror(ret)); + return -13; + } + if (actual_buffer_length < config.audio_backend_buffer_desired_length + minimal_buffer_headroom) { /* // the dac buffer is too small, so let's try to set it @@ -719,7 +755,7 @@ int open_alsa_device(void) { "length (%ld) you have chosen.", actual_buffer_length, config.audio_backend_buffer_desired_length); } - + if (alsa_characteristics_already_listed == 0) { alsa_characteristics_already_listed = 1; int log_level = 2; // the level at which debug information should be output @@ -867,6 +903,42 @@ static void start(int i_sample_rate, int i_sample_format) { measurement_data_is_valid = 0; } +int my_snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { + snd_pcm_status_t *alsa_snd_pcm_status; + snd_pcm_status_alloca(&alsa_snd_pcm_status); + + + struct timespec tn = {0,0}; + + snd_htimestamp_t snd_timestamp = {0,0}; //actually a struct timespec + snd_htimestamp_t audio_timestamp = {0,0}; //actually a struct timespec + snd_htimestamp_t driver_timestamp = {0,0}; //actually a struct timespec + if (snd_pcm_status(alsa_handle, alsa_snd_pcm_status) == 0) { + clock_gettime(CLOCK_MONOTONIC, &tn); + snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &snd_timestamp); + + uint64_t t1 = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; + uint64_t t2 = snd_timestamp.tv_sec * (uint64_t)1000000000 + snd_timestamp.tv_nsec; + uint64_t delta = t1 - t2; + + + snd_pcm_status_get_audio_htstamp(alsa_snd_pcm_status, &audio_timestamp); + snd_pcm_status_get_driver_htstamp(alsa_snd_pcm_status, &driver_timestamp); + snd_pcm_sframes_t delay = snd_pcm_status_get_delay(alsa_snd_pcm_status); + debug(1,"Time now: %ld,%ld, Timestamp: %ld,%ld, delta: %" PRIu64 ", audio_timestamp: %ld,%ld, driver_timestamp: %ld,%ld, Delay: %ld.", + tn.tv_sec,tn.tv_nsec, + snd_timestamp.tv_sec,snd_timestamp.tv_nsec, + delta, + audio_timestamp.tv_sec,audio_timestamp.tv_nsec, + driver_timestamp.tv_sec,driver_timestamp.tv_nsec, + delay); + // snd_pcm_status_dump(alsa_snd_pcm_status, output); + + } + + return snd_pcm_delay(pcm, delayp); +} + int delay(long *the_delay) { // snd_pcm_sframes_t is a signed long -- hence the return of a "long" int reply = 0; @@ -879,7 +951,7 @@ int delay(long *the_delay) { snd_pcm_state_t dac_state = snd_pcm_state(alsa_handle); if (dac_state == SND_PCM_STATE_RUNNING) { *the_delay = 0; // just to see what happens - reply = snd_pcm_delay(alsa_handle, the_delay); + reply = my_snd_pcm_delay(alsa_handle, the_delay); if (reply != 0) { debug(1, "Error %d in delay(): \"%s\". Delay reported is %d frames.", reply, snd_strerror(reply), *the_delay); @@ -1000,7 +1072,7 @@ static int play(void *buf, int samples) { frame_index++; if ((frame_index == start_measurement_from_this_frame) || (frame_index % 32 == 0)) { long fl = 0; - err2 = snd_pcm_delay(alsa_handle, &fl); + err2 = my_snd_pcm_delay(alsa_handle, &fl); if (err2 != 0) { debug(1, "Error %d in delay in play(): \"%s\". Delay reported is %d frames.", err2, snd_strerror(err2), fl); From d90fec4a8c0e0e7d7a90ae095e2652ad85d9252c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 8 Nov 2018 12:23:52 +0000 Subject: [PATCH 185/569] Prototype working estimated delay. --- audio_alsa.c | 74 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 37e6f1ca..d26822bc 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -903,40 +903,60 @@ static void start(int i_sample_rate, int i_sample_format) { measurement_data_is_valid = 0; } -int my_snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { +int my_snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { + int ret; snd_pcm_status_t *alsa_snd_pcm_status; snd_pcm_status_alloca(&alsa_snd_pcm_status); - - + struct timespec tn = {0,0}; + snd_htimestamp_t update_timestamp = {0,0}; //actually a struct timespec - snd_htimestamp_t snd_timestamp = {0,0}; //actually a struct timespec - snd_htimestamp_t audio_timestamp = {0,0}; //actually a struct timespec - snd_htimestamp_t driver_timestamp = {0,0}; //actually a struct timespec - if (snd_pcm_status(alsa_handle, alsa_snd_pcm_status) == 0) { - clock_gettime(CLOCK_MONOTONIC, &tn); - snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &snd_timestamp); - - uint64_t t1 = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; - uint64_t t2 = snd_timestamp.tv_sec * (uint64_t)1000000000 + snd_timestamp.tv_nsec; - uint64_t delta = t1 - t2; - - - snd_pcm_status_get_audio_htstamp(alsa_snd_pcm_status, &audio_timestamp); - snd_pcm_status_get_driver_htstamp(alsa_snd_pcm_status, &driver_timestamp); - snd_pcm_sframes_t delay = snd_pcm_status_get_delay(alsa_snd_pcm_status); - debug(1,"Time now: %ld,%ld, Timestamp: %ld,%ld, delta: %" PRIu64 ", audio_timestamp: %ld,%ld, driver_timestamp: %ld,%ld, Delay: %ld.", - tn.tv_sec,tn.tv_nsec, - snd_timestamp.tv_sec,snd_timestamp.tv_nsec, - delta, - audio_timestamp.tv_sec,audio_timestamp.tv_nsec, - driver_timestamp.tv_sec,driver_timestamp.tv_nsec, - delay); - // snd_pcm_status_dump(alsa_snd_pcm_status, output); + //snd_htimestamp_t audio_timestamp = {0,0}; //actually a struct timespec + //snd_htimestamp_t driver_timestamp = {0,0}; //actually a struct timespec + ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); + if (ret) { + *delayp = 0; + return ret; } - return snd_pcm_delay(pcm, delayp); + snd_pcm_state_t state = snd_pcm_status_get_state(alsa_snd_pcm_status); + if (state != SND_PCM_STATE_RUNNING) { + *delayp = 0; + return -EIO; // might be a better code than this... + } + + clock_gettime(CLOCK_MONOTONIC, &tn); + snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); + + uint64_t t1 = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; + uint64_t t2 = update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; + uint64_t delta = t1 - t2; + + //snd_pcm_status_get_audio_htstamp(alsa_snd_pcm_status, &audio_timestamp); + //snd_pcm_status_get_driver_htstamp(alsa_snd_pcm_status, &driver_timestamp); + snd_pcm_sframes_t delay = snd_pcm_status_get_delay(alsa_snd_pcm_status); + //debug(1,"Time now: %ld,%ld, Timestamp: %ld,%ld, delta: %.3f milliseconds, audio_timestamp: %ld,%ld, driver_timestamp: %ld,%ld, Delay: %ld.", + //tn.tv_sec,tn.tv_nsec, + //snd_timestamp.tv_sec,snd_timestamp.tv_nsec, + //(delta*1.0)/1000000, + //audio_timestamp.tv_sec,audio_timestamp.tv_nsec, + //driver_timestamp.tv_sec,driver_timestamp.tv_nsec, + //delay); + + uint64_t frames_played_since_last_interrupt = ((uint64_t)desired_sample_rate * delta)/1000000000; + snd_pcm_sframes_t frames_played_since_last_interrupt_sized = frames_played_since_last_interrupt; + + //debug(1,"Delta: %.3f, frames played in interval: %" PRIu64 ".",(delta*1.0)/1000000,frames_played_since_last_interrupt); + // snd_pcm_status_dump(alsa_snd_pcm_status, output); + snd_pcm_sframes_t reported_delay = 0; + snd_pcm_delay(pcm, &reported_delay); + if (reported_delay != delay) + debug(1,"Difference between reported delay and status delay is: %ld.",reported_delay-delay); + + *delayp = delay - frames_played_since_last_interrupt_sized; + return 0; + //return snd_pcm_delay(pcm, delayp); } int delay(long *the_delay) { From 3c00d14d2a10e8006b4d77d71d6cb4a2101683b1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 8 Nov 2018 12:48:42 +0000 Subject: [PATCH 186/569] Refinements... --- audio_alsa.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index d26822bc..d067d4f5 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -914,12 +914,15 @@ int my_snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { //snd_htimestamp_t audio_timestamp = {0,0}; //actually a struct timespec //snd_htimestamp_t driver_timestamp = {0,0}; //actually a struct timespec - ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); + ret = snd_pcm_status(pcm, alsa_snd_pcm_status); if (ret) { *delayp = 0; return ret; } + //snd_pcm_sframes_t reported_delay = 0; + //snd_pcm_delay(pcm, &reported_delay); + snd_pcm_state_t state = snd_pcm_status_get_state(alsa_snd_pcm_status); if (state != SND_PCM_STATE_RUNNING) { *delayp = 0; @@ -949,10 +952,8 @@ int my_snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { //debug(1,"Delta: %.3f, frames played in interval: %" PRIu64 ".",(delta*1.0)/1000000,frames_played_since_last_interrupt); // snd_pcm_status_dump(alsa_snd_pcm_status, output); - snd_pcm_sframes_t reported_delay = 0; - snd_pcm_delay(pcm, &reported_delay); - if (reported_delay != delay) - debug(1,"Difference between reported delay and status delay is: %ld.",reported_delay-delay); + //if (reported_delay != delay) + // debug(1,"Difference between reported delay and status delay is: %ld.",reported_delay-delay); *delayp = delay - frames_played_since_last_interrupt_sized; return 0; From cfd10647aad65314cbbde1ef5d959a57221d3090 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 8 Nov 2018 14:39:57 +0000 Subject: [PATCH 187/569] Make the changes to audio_alsa.c work back to alsa 1.0.28. --- audio_alsa.c | 44 +++++++------------------------------------- 1 file changed, 7 insertions(+), 37 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index d067d4f5..815f3423 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -712,20 +712,13 @@ int open_alsa_device(void) { snd_strerror(ret)); return -11; } - - ret = snd_pcm_sw_params_set_tstamp_type(alsa_handle, alsa_swparams, SND_PCM_TSTAMP_TYPE_MONOTONIC); - if (ret < 0) { - warn("audio_alsa: Can't set timestamp type of device: \"%s\": %s.", alsa_out_dev, - snd_strerror(ret)); - return -12; - } - + /* write the sw parameters */ ret = snd_pcm_sw_params(alsa_handle, alsa_swparams); if (ret < 0) { - warn("audio_alsa: Unable to set swparams_p of device: \"%s\": %s.", alsa_out_dev, + warn("audio_alsa: Unable to set software parameters of device: \"%s\": %s.", alsa_out_dev, snd_strerror(ret)); - return -13; + return -12; } if (actual_buffer_length < config.audio_backend_buffer_desired_length + minimal_buffer_headroom) { @@ -908,11 +901,8 @@ int my_snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { snd_pcm_status_t *alsa_snd_pcm_status; snd_pcm_status_alloca(&alsa_snd_pcm_status); - struct timespec tn = {0,0}; - snd_htimestamp_t update_timestamp = {0,0}; //actually a struct timespec - - //snd_htimestamp_t audio_timestamp = {0,0}; //actually a struct timespec - //snd_htimestamp_t driver_timestamp = {0,0}; //actually a struct timespec + struct timespec tn; // time now + snd_htimestamp_t update_timestamp; //actually a struct timespec ret = snd_pcm_status(pcm, alsa_snd_pcm_status); if (ret) { @@ -920,9 +910,6 @@ int my_snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { return ret; } - //snd_pcm_sframes_t reported_delay = 0; - //snd_pcm_delay(pcm, &reported_delay); - snd_pcm_state_t state = snd_pcm_status_get_state(alsa_snd_pcm_status); if (state != SND_PCM_STATE_RUNNING) { *delayp = 0; @@ -935,29 +922,12 @@ int my_snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { uint64_t t1 = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; uint64_t t2 = update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; uint64_t delta = t1 - t2; - - //snd_pcm_status_get_audio_htstamp(alsa_snd_pcm_status, &audio_timestamp); - //snd_pcm_status_get_driver_htstamp(alsa_snd_pcm_status, &driver_timestamp); - snd_pcm_sframes_t delay = snd_pcm_status_get_delay(alsa_snd_pcm_status); - //debug(1,"Time now: %ld,%ld, Timestamp: %ld,%ld, delta: %.3f milliseconds, audio_timestamp: %ld,%ld, driver_timestamp: %ld,%ld, Delay: %ld.", - //tn.tv_sec,tn.tv_nsec, - //snd_timestamp.tv_sec,snd_timestamp.tv_nsec, - //(delta*1.0)/1000000, - //audio_timestamp.tv_sec,audio_timestamp.tv_nsec, - //driver_timestamp.tv_sec,driver_timestamp.tv_nsec, - //delay); - + uint64_t frames_played_since_last_interrupt = ((uint64_t)desired_sample_rate * delta)/1000000000; snd_pcm_sframes_t frames_played_since_last_interrupt_sized = frames_played_since_last_interrupt; - //debug(1,"Delta: %.3f, frames played in interval: %" PRIu64 ".",(delta*1.0)/1000000,frames_played_since_last_interrupt); - // snd_pcm_status_dump(alsa_snd_pcm_status, output); - //if (reported_delay != delay) - // debug(1,"Difference between reported delay and status delay is: %ld.",reported_delay-delay); - - *delayp = delay - frames_played_since_last_interrupt_sized; + *delayp = snd_pcm_status_get_delay(alsa_snd_pcm_status) - frames_played_since_last_interrupt_sized; return 0; - //return snd_pcm_delay(pcm, delayp); } int delay(long *the_delay) { From e7f70fba328bdcc88994b1963c1e85ffaba29af7 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 8 Nov 2018 14:43:52 +0000 Subject: [PATCH 188/569] Tidy up with clang-format --- audio_alsa.c | 89 +++++++++++++++++++++++++++------------------------- player.c | 30 +++++++++++------- shairport.c | 4 +-- 3 files changed, 66 insertions(+), 57 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 815f3423..0a03957b 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -198,7 +198,6 @@ void do_snd_mixer_selem_set_playback_dB_all(snd_mixer_elem_t *mix_elem, double v static int init(int argc, char **argv) { snd_output_stdio_attach(&output, stdout, 0); - // debug(2,"audio_alsa init called."); int response = 0; // this will be what we return to the caller. const char *str; @@ -507,7 +506,6 @@ static int init(int argc, char **argv) { static void deinit(void) { // debug(2,"audio_alsa deinit called."); stop(); - } int open_alsa_device(void) { @@ -543,7 +541,7 @@ int open_alsa_device(void) { snd_pcm_hw_params_alloca(&alsa_params); snd_pcm_sw_params_alloca(&alsa_swparams); - + ret = snd_pcm_hw_params_any(alsa_handle, alsa_params); if (ret < 0) { warn("audio_alsa: Broken configuration for device \"%s\": no configurations " @@ -698,7 +696,7 @@ int open_alsa_device(void) { snd_strerror(ret)); return -9; } - + ret = snd_pcm_sw_params_current(alsa_handle, alsa_swparams); if (ret < 0) { warn("audio_alsa: Unable to get current sw parameters for device \"%s\": %s.", alsa_out_dev, @@ -712,15 +710,15 @@ int open_alsa_device(void) { snd_strerror(ret)); return -11; } - + /* write the sw parameters */ ret = snd_pcm_sw_params(alsa_handle, alsa_swparams); if (ret < 0) { warn("audio_alsa: Unable to set software parameters of device: \"%s\": %s.", alsa_out_dev, - snd_strerror(ret)); + snd_strerror(ret)); return -12; } - + if (actual_buffer_length < config.audio_backend_buffer_desired_length + minimal_buffer_headroom) { /* // the dac buffer is too small, so let's try to set it @@ -748,7 +746,7 @@ int open_alsa_device(void) { "length (%ld) you have chosen.", actual_buffer_length, config.audio_backend_buffer_desired_length); } - + if (alsa_characteristics_already_listed == 0) { alsa_characteristics_already_listed = 1; int log_level = 2; // the level at which debug information should be output @@ -896,38 +894,40 @@ static void start(int i_sample_rate, int i_sample_format) { measurement_data_is_valid = 0; } -int my_snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { - int ret; +int my_snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { + int ret; snd_pcm_status_t *alsa_snd_pcm_status; snd_pcm_status_alloca(&alsa_snd_pcm_status); - struct timespec tn; // time now - snd_htimestamp_t update_timestamp; //actually a struct timespec + struct timespec tn; // time now + snd_htimestamp_t update_timestamp; // actually a struct timespec ret = snd_pcm_status(pcm, alsa_snd_pcm_status); if (ret) { *delayp = 0; - return ret; + return ret; } - + snd_pcm_state_t state = snd_pcm_status_get_state(alsa_snd_pcm_status); if (state != SND_PCM_STATE_RUNNING) { *delayp = 0; - return -EIO; // might be a better code than this... + return -EIO; // might be a better code than this... } - + clock_gettime(CLOCK_MONOTONIC, &tn); snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); - - uint64_t t1 = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; - uint64_t t2 = update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; + + uint64_t t1 = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; + uint64_t t2 = update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; uint64_t delta = t1 - t2; - - uint64_t frames_played_since_last_interrupt = ((uint64_t)desired_sample_rate * delta)/1000000000; + + uint64_t frames_played_since_last_interrupt = + ((uint64_t)desired_sample_rate * delta) / 1000000000; snd_pcm_sframes_t frames_played_since_last_interrupt_sized = frames_played_since_last_interrupt; - - *delayp = snd_pcm_status_get_delay(alsa_snd_pcm_status) - frames_played_since_last_interrupt_sized; - return 0; + + *delayp = + snd_pcm_status_get_delay(alsa_snd_pcm_status) - frames_played_since_last_interrupt_sized; + return 0; } int delay(long *the_delay) { @@ -947,9 +947,9 @@ int delay(long *the_delay) { debug(1, "Error %d in delay(): \"%s\". Delay reported is %d frames.", reply, snd_strerror(reply), *the_delay); derr = snd_pcm_recover(alsa_handle, reply, 1); - if (derr < 0) - warn("Error %d -- could not clear an error after attempting delay(): \"%s\".", derr, - snd_strerror(derr)); + if (derr < 0) + warn("Error %d -- could not clear an error after attempting delay(): \"%s\".", derr, + snd_strerror(derr)); frame_index = 0; measurement_data_is_valid = 0; @@ -977,9 +977,10 @@ int delay(long *the_delay) { if ((derr = snd_pcm_prepare(alsa_handle))) { debug(1, "Error preparing after delay error: \"%s\".", snd_strerror(derr)); derr = snd_pcm_recover(alsa_handle, derr, 1); - if (derr < 0) - warn("Error %d -- could not clear an error after attempting to recover following a delay(): \"%s\".", derr, - snd_strerror(derr)); + if (derr < 0) + warn("Error %d -- could not clear an error after attempting to recover following a " + "delay(): \"%s\".", + derr, snd_strerror(derr)); } } } @@ -1028,10 +1029,10 @@ static int play(void *buf, int samples) { if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) { if ((err = snd_pcm_prepare(alsa_handle))) { debug(1, "Error preparing after underrun: \"%s\".", snd_strerror(err)); - err = snd_pcm_recover(alsa_handle, err, 1); - if (err < 0) - warn("Error %d -- could not clear an error after detecting underrun in play(): \"%s\".", err, - snd_strerror(err)); + err = snd_pcm_recover(alsa_handle, err, 1); + if (err < 0) + warn("Error %d -- could not clear an error after detecting underrun in play(): \"%s\".", + err, snd_strerror(err)); } frame_index = 0; // we'll be starting over measurement_data_is_valid = 0; @@ -1049,9 +1050,10 @@ static int play(void *buf, int samples) { debug(1, "Error %d writing %d samples in play(): \"%s\".", err, samples, snd_strerror(err)); err = snd_pcm_recover(alsa_handle, err, 1); - if (err < 0) - warn("Error %d -- could not clear an error after attempting to write %d samples in play(): \"%s\".", err, samples, - snd_strerror(err)); + if (err < 0) + warn("Error %d -- could not clear an error after attempting to write %d samples in " + "play(): \"%s\".", + err, samples, snd_strerror(err)); } if (frame_index == 0) { frames_sent_for_playing = samples; @@ -1068,9 +1070,9 @@ static int play(void *buf, int samples) { debug(1, "Error %d in delay in play(): \"%s\". Delay reported is %d frames.", err2, snd_strerror(err2), fl); err2 = snd_pcm_recover(alsa_handle, err2, 1); - if (err2 < 0) - warn("Error %d -- could not clear an error after checking delay in play(): \"%s\".", err2, - snd_strerror(err2)); + if (err2 < 0) + warn("Error %d -- could not clear an error after checking delay in play(): \"%s\".", + err2, snd_strerror(err2)); frame_index = 0; measurement_data_is_valid = 0; } else { @@ -1097,9 +1099,10 @@ static int play(void *buf, int samples) { if ((err = snd_pcm_prepare(alsa_handle))) { debug(1, "Error preparing after play error: \"%s\".", snd_strerror(err)); err2 = snd_pcm_recover(alsa_handle, err, 1); - if (err2 < 0) - warn("Error %d -- could not clear an error after reporting ALSA device in incorrect state for play: \"%s\".", err2, - snd_strerror(err2)); + if (err2 < 0) + warn("Error %d -- could not clear an error after reporting ALSA device in incorrect " + "state for play: \"%s\".", + err2, snd_strerror(err2)); } frame_index = 0; measurement_data_is_valid = 0; diff --git a/player.c b/player.c index 43461bf3..11bec280 100644 --- a/player.c +++ b/player.c @@ -454,7 +454,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in // now, if a flush_rtp_timestamp has been defined and the incoming timestamp is "before" it, // drop it… - if ((conn->flush_rtp_timestamp != 0) && (actual_timestamp != conn->flush_rtp_timestamp) && + if ((conn->flush_rtp_timestamp != 0) && (actual_timestamp != conn->flush_rtp_timestamp) && (modulo_32_offset(actual_timestamp, conn->flush_rtp_timestamp) < conn->input_rate * 10)) { // if it's less than 10 seconds debug(2, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %" PRIu32 @@ -465,9 +465,9 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in conn->initial_reference_timestamp = 0; } else { if ((conn->flush_rtp_timestamp != 0) && - (modulo_32_offset(conn->flush_rtp_timestamp, actual_timestamp) > conn->input_rate/5) && + (modulo_32_offset(conn->flush_rtp_timestamp, actual_timestamp) > conn->input_rate / 5) && (modulo_32_offset(conn->flush_rtp_timestamp, actual_timestamp) < conn->input_rate)) { - // between 0.2 and 1 second + // between 0.2 and 1 second debug(2, "Dropping flush request in player_put_packet"); conn->flush_rtp_timestamp = 0; } @@ -851,7 +851,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { } } - if ((conn->flush_rtp_timestamp != 0) && (curframe->given_timestamp != conn->flush_rtp_timestamp) && + if ((conn->flush_rtp_timestamp != 0) && + (curframe->given_timestamp != conn->flush_rtp_timestamp) && (modulo_32_offset(curframe->given_timestamp, conn->flush_rtp_timestamp) < conn->input_rate * 10)) { // if it's less than ten seconds debug(2, "Dropping flushed packet in buffer_get_frame, seqno %u, timestamp %" PRIu32 @@ -866,8 +867,10 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { conn->initial_reference_timestamp = 0; } if ((conn->flush_rtp_timestamp != 0) && - (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) > conn->input_rate / 5) && - (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) < conn->input_rate * 10 )) { + (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) > + conn->input_rate / 5) && + (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) < + conn->input_rate * 10)) { debug(2, "Dropping flush request in buffer_get_frame"); conn->flush_rtp_timestamp = 0; } @@ -1912,8 +1915,8 @@ void *player_thread_func(void *arg) { int64_t rt, nt; rt = reference_timestamp; // uint32_t to int64_t nt = inframe->given_timestamp; // uint32_t to int64_t - rt = rt*conn->output_sample_ratio; - nt = nt*conn->output_sample_ratio; + rt = rt * conn->output_sample_ratio; + nt = nt * conn->output_sample_ratio; uint64_t local_time_now = get_absolute_time_in_fp(); // types okay // struct timespec tn; @@ -2062,14 +2065,17 @@ void *player_thread_func(void *arg) { // sync_error_out_of_bounds, sync_error); sync_error_out_of_bounds = 0; - int64_t filler_length = (int64_t)(config.resyncthreshold * config.output_rate); // number of samples + int64_t filler_length = + (int64_t)(config.resyncthreshold * config.output_rate); // number of samples if ((sync_error > 0) && (sync_error > filler_length)) { debug(2, "Large positive sync error: %" PRId64 ".", sync_error); frames_to_drop = sync_error / conn->output_sample_ratio; reset_input_flow_metrics(conn); } else if ((sync_error < 0) && ((-sync_error) > filler_length)) { - debug(2, "Large negative sync error: %" PRId64 " with should_be_frame_32 of %" PRIu32 - ", nt of %" PRId64 " and current_delay of %" PRId64 ".", sync_error, should_be_frame_32, nt, current_delay); + debug(2, + "Large negative sync error: %" PRId64 " with should_be_frame_32 of %" PRIu32 + ", nt of %" PRId64 " and current_delay of %" PRId64 ".", + sync_error, should_be_frame_32, nt, current_delay); int64_t silence_length = -sync_error; if (silence_length > (filler_length * 5)) silence_length = filler_length * 5; @@ -2077,7 +2083,7 @@ void *player_thread_func(void *arg) { char *long_silence = malloc(conn->output_bytes_per_frame * silence_length_sized); if (long_silence) { memset(long_silence, 0, conn->output_bytes_per_frame * silence_length_sized); - debug(2,"Play a silence of %d frames.",silence_length_sized); + debug(2, "Play a silence of %d frames.", silence_length_sized); config.output->play(long_silence, silence_length_sized); free(long_silence); } else { diff --git a/shairport.c b/shairport.c index d4a29410..5e36e911 100644 --- a/shairport.c +++ b/shairport.c @@ -25,8 +25,6 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include #include #include #include @@ -37,7 +35,9 @@ #include #include #include +#include #include +#include #include #include From 501e349466df8bf483e873af0d4dcfd6b43fb980 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 12 Nov 2018 16:41:29 +0000 Subject: [PATCH 189/569] Add better error handling if a reply header message is faulty. --- rtsp.c | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/rtsp.c b/rtsp.c index ab097a74..4446d806 100644 --- a/rtsp.c +++ b/rtsp.c @@ -648,7 +648,7 @@ shutdown: return reply; } -void msg_write_response(int fd, rtsp_message *resp) { +int msg_write_response(int fd, rtsp_message *resp) { char pkt[2048]; int pktfree = sizeof(pkt); char *p = pkt; @@ -666,8 +666,10 @@ void msg_write_response(int fd, rtsp_message *resp) { n = snprintf(p, pktfree, "%s: %s\r\n", resp->name[i], resp->value[i]); pktfree -= n; p += n; - if (pktfree <= 1024) - die("Attempted to write overlong RTSP packet 1"); + if (pktfree <= 1024) { + debug(1,"Attempted to write overlong RTSP packet 1"); + return -1; + } } // Here, if there's content, write the Content-Length header ... @@ -677,8 +679,10 @@ void msg_write_response(int fd, rtsp_message *resp) { n = snprintf(p, pktfree, "Content-Length: %d\r\n", resp->contentlength); pktfree -= n; p += n; - if (pktfree <= 1024) - die("Attempted to write overlong RTSP packet 2"); + if (pktfree <= 1024) { + debug(1,"Attempted to write overlong RTSP packet 2"); + return -2; + } debug(1, "Content is \"%s\"", resp->content); memcpy(p, resp->content, resp->contentlength); pktfree -= resp->contentlength; @@ -689,11 +693,15 @@ void msg_write_response(int fd, rtsp_message *resp) { pktfree -= n; p += n; - if (pktfree <= 1024) - die("Attempted to write overlong RTSP packet 3"); - - if (write(fd, pkt, p - pkt) != p - pkt) + if (pktfree <= 1024) { + debug(1,"Attempted to write overlong RTSP packet 3"); + return -3; + } + if (write(fd, pkt, p - pkt) != p - pkt) { debug(1, "Error writing an RTSP packet -- requested bytes not fully written."); + return -4; + } + return 0; } void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { @@ -2069,7 +2077,12 @@ static void *rtsp_conversation_thread_func(void *pconn) { } while (conn->stop == 0 && pselect(conn->fd + 1, NULL, &writefds, NULL, NULL, &pselect_sigset) <= 0); if (conn->stop == 0) { - msg_write_response(conn->fd, resp); + int err = msg_write_response(conn->fd, resp); + if (err) { + debug(1,"A communication error was detected. Closing the play session."); + conn->stop = 1; + pthread_cancel(conn->thread); + } } pthread_cleanup_pop(1); pthread_cleanup_pop(1); From 67083665adf3adf6c5fdf66a74ef1230a7dd451a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 13 Nov 2018 11:00:05 +0000 Subject: [PATCH 190/569] Get more diagnostic messages when a TuneBlade-induced error occurs. --- player.c | 1 + rtsp.c | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/player.c b/player.c index 43461bf3..803288e6 100644 --- a/player.c +++ b/player.c @@ -2777,6 +2777,7 @@ int player_stop(rtsp_conn_info *conn) { if (conn->player_thread) { debug(2, "player_thread cancel..."); pthread_cancel(*conn->player_thread); + debug(2, "player_thread join..."); pthread_join(*conn->player_thread, NULL); debug(2, "player_thread joined."); free(conn->player_thread); diff --git a/rtsp.c b/rtsp.c index 4446d806..adb2dc50 100644 --- a/rtsp.c +++ b/rtsp.c @@ -2079,9 +2079,11 @@ static void *rtsp_conversation_thread_func(void *pconn) { if (conn->stop == 0) { int err = msg_write_response(conn->fd, resp); if (err) { + int odv = debuglev; + debuglev = 3; debug(1,"A communication error was detected. Closing the play session."); - conn->stop = 1; - pthread_cancel(conn->thread); + player_stop(conn); + debuglev = odv; } } pthread_cleanup_pop(1); From 29a37f44b9fbdfd2e8ba26c5cee1e02ddb292649 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 14 Nov 2018 12:25:56 +0000 Subject: [PATCH 191/569] Check timer values a bit more carefully and ignore crazy values. --- rtp.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/rtp.c b/rtp.c index 35b2a2bc..4d13199c 100644 --- a/rtp.c +++ b/rtp.c @@ -621,9 +621,14 @@ void *rtp_timing_receiver(void *arg) { distant_transmit_time = (uint64_t)nctohl(&packet[24]) << 32; distant_transmit_time += nctohl(&packet[28]); - - uint64_t remote_processing_time = distant_transmit_time - distant_receive_time; - + + uint64_t remote_processing_time = 0; + + if (distant_transmit_time >= distant_receive_time) + remote_processing_time = distant_transmit_time - distant_receive_time; + else { + debug(1, "Yikes: distant_transmit_time is before distant_receive_time; remote processing time set to zero."); + } // debug(1,"Return trip time: %" PRIu64 " uS, remote processing time: %" PRIu64 " // uS.",(return_time*1000000)>>32,(remote_processing_time*1000000)>>32); @@ -632,10 +637,10 @@ void *rtp_timing_receiver(void *arg) { // remove the remote processing time from the record of the return time, as long at the // processing time looks sensible. - if ((remote_processing_time > 0) && (remote_processing_time < return_time)) + if (remote_processing_time < return_time) return_time -= remote_processing_time; else - debug(1, "Non-sensical remote processing time -- ignored."); + debug(1, "Remote processing time greater than return time -- ignored."); int cc; for (cc = time_ping_history - 1; cc > 0; cc--) { From 99374ac11313d4d4798c99ec0ace140cfa662d68 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 14 Nov 2018 17:21:30 +0000 Subject: [PATCH 192/569] Add a little diagnostic to see what's in the POST message.: --- rtsp.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/rtsp.c b/rtsp.c index adb2dc50..73d8d05a 100644 --- a/rtsp.c +++ b/rtsp.c @@ -2063,9 +2063,28 @@ static void *rtsp_conversation_thread_func(void *pconn) { break; } } - if (method_selected == 0) + if (method_selected == 0) { debug(1, "RTSP thread %d: Unrecognised and unhandled rtsp request \"%s\".", conn->connection_number, req->method); + + + int y = req->contentlength; + if (y > 0) { + char obf[4096]; + if (y > 4096) + y = 4096; + char *p = req->content; + char *obfp = obf; + int obfc; + for (obfc = 0; obfc < y; obfc++) { + snprintf(obfp, 3, "%02X", (unsigned int)p); + p++; + obfp += 2; + }; + *obfp = 0; + debug(1, "Content: \"%s\".",obf); + } + } } debug(debug_level, "RTSP thread %d: RTSP Response:", conn->connection_number); debug_print_msg_headers(debug_level, resp); From ca3c05f8b53c767d7de088bc976e69631a989b9f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 14 Nov 2018 17:31:39 +0000 Subject: [PATCH 193/569] Fix silly issue with diagnostic code. --- rtsp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtsp.c b/rtsp.c index 73d8d05a..4663470a 100644 --- a/rtsp.c +++ b/rtsp.c @@ -2077,7 +2077,7 @@ static void *rtsp_conversation_thread_func(void *pconn) { char *obfp = obf; int obfc; for (obfc = 0; obfc < y; obfc++) { - snprintf(obfp, 3, "%02X", (unsigned int)p); + snprintf(obfp, 3, "%02X", (unsigned int)*p); p++; obfp += 2; }; From d3ff806412cdc2cd4247a465185f88aae07ca83b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 16 Nov 2018 10:05:37 +0000 Subject: [PATCH 194/569] Put a timeout on the rtsp writeback call. --- rtsp.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rtsp.c b/rtsp.c index 4663470a..352088cb 100644 --- a/rtsp.c +++ b/rtsp.c @@ -2220,6 +2220,13 @@ void rtsp_listen_loop(void) { // See: https://github.com/mikebrady/shairport-sync/issues/329 fcntl(fd, F_SETFD, FD_CLOEXEC); ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + + struct timeval tv; + tv.tv_sec = 3; // three seconds write timeout + tv.tv_usec = 0; + if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1) + debug(1, "Error %d setting send timeout for rtsp writeback.", errno); + #ifdef IPV6_V6ONLY // some systems don't support v4 access on v6 sockets, but some do. From 45c1c566aee846fc4836095c357356ae9c6604ab Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 18 Nov 2018 15:25:06 +0000 Subject: [PATCH 195/569] Fix a logical error in buffer_get_frame that could cause an endless loop if an unready frame occurred during a flush. --- player.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/player.c b/player.c index 803288e6..2d392c2c 100644 --- a/player.c +++ b/player.c @@ -831,7 +831,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { uint32_t flush_limit = 0; if (conn->ab_synced) { + int frame_flushed; do { + frame_flushed = 0; curframe = conn->audio_buffer + BUFIDX(conn->ab_read); if ((conn->ab_read != conn->ab_write) && (curframe->ready)) { // it could be synced and empty, under @@ -862,6 +864,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { curframe->resend_level = 0; flush_limit += curframe->length; conn->ab_read = SUCCESSOR(conn->ab_read); + frame_flushed = 1; conn->initial_reference_time = 0; conn->initial_reference_timestamp = 0; } @@ -871,8 +874,11 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { debug(2, "Dropping flush request in buffer_get_frame"); conn->flush_rtp_timestamp = 0; } + } else { + if (curframe->ready == 0) + debug(1, "An unready frame was seen in a flush sequence!"); } - } while ((conn->flush_rtp_timestamp != 0) && (flush_limit <= 8820) && (curframe->ready == 0)); + } while ((conn->flush_rtp_timestamp != 0) && (flush_limit <= 8820) && (frame_flushed)); if (flush_limit == 8820) { debug(1, "Flush hit the 8820 frame limit!"); @@ -1758,7 +1764,7 @@ void *player_thread_func(void *arg) { pthread_cleanup_push(player_thread_cleanup_handler, arg); // undo what's been done so far - // debug(1, "Play begin"); + debug(2, "Play begin"); while (1) { abuf_t *inframe = buffer_get_frame(conn); // this has cancellation point(s) if (inframe) { @@ -2772,6 +2778,9 @@ int player_play(rtsp_conn_info *conn) { } int player_stop(rtsp_conn_info *conn) { + int dl = debuglev; + if (debuglev) + debuglev = 3; // note -- this may be called from another connection thread. debug(3, "player_stop"); if (conn->player_thread) { @@ -2786,10 +2795,12 @@ int player_stop(rtsp_conn_info *conn) { debug(2, "pend"); send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points #endif + debuglev = dl; command_stop(); return 0; } else { - debug(3, "Connection %d: player thread already deleted.", conn->connection_number); + debug(3, "player_stop: player thread not running on connection %d.", conn->connection_number); + debuglev = dl; return -1; } } From 58a66ebb0edaa1b26994a6937bfeef951c499560 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 19 Nov 2018 14:24:24 +0000 Subject: [PATCH 196/569] Make debuglev volatile, over player_vol past the cleanup push, add some temporary diagnostics. --- common.c | 5 ++++- common.h | 2 +- player.c | 62 ++++++++++++++++++++++++++++++-------------------------- rtsp.c | 3 --- 4 files changed, 38 insertions(+), 34 deletions(-) diff --git a/common.c b/common.c index 3ba442d4..e4724954 100644 --- a/common.c +++ b/common.c @@ -90,7 +90,7 @@ static volatile int requested_connection_state_to_output = 1; shairport_cfg config; -int debuglev = 0; +volatile int debuglev = 0; sigset_t pselect_sigset; @@ -588,6 +588,7 @@ int config_set_lookup_bool(config_t *cfg, char *where, int *dst) { } void command_set_volume(double volume) { +//this has a cancellation point if waiting is enabled if (config.cmd_set_volume) { /*Spawn a child to run the program.*/ pid_t pid = fork(); @@ -634,6 +635,7 @@ void command_set_volume(double volume) { } void command_start(void) { + //this has a cancellation point if waiting is enabled or a response is awaited if (config.cmd_start) { pid_t pid; int pipes[2]; @@ -701,6 +703,7 @@ void command_start(void) { } void command_stop(void) { +//this has a cancellation point if waiting is enabled if (config.cmd_stop) { /*Spawn a child to run the program.*/ pid_t pid = fork(); diff --git a/common.h b/common.h index c9674c41..81aab4cb 100644 --- a/common.h +++ b/common.h @@ -243,7 +243,7 @@ void r64arrayinit(); uint64_t ranarray64u(); int64_t ranarray64i(); -extern int debuglev; +volatile int debuglev; void die(const char *format, ...); void warn(const char *format, ...); void inform(const char *format, ...); diff --git a/player.c b/player.c index 2d392c2c..41e528a5 100644 --- a/player.c +++ b/player.c @@ -831,9 +831,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { uint32_t flush_limit = 0; if (conn->ab_synced) { - int frame_flushed; do { - frame_flushed = 0; curframe = conn->audio_buffer + BUFIDX(conn->ab_read); if ((conn->ab_read != conn->ab_write) && (curframe->ready)) { // it could be synced and empty, under @@ -864,7 +862,6 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { curframe->resend_level = 0; flush_limit += curframe->length; conn->ab_read = SUCCESSOR(conn->ab_read); - frame_flushed = 1; conn->initial_reference_time = 0; conn->initial_reference_timestamp = 0; } @@ -874,11 +871,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { debug(2, "Dropping flush request in buffer_get_frame"); conn->flush_rtp_timestamp = 0; } - } else { - if (curframe->ready == 0) - debug(1, "An unready frame was seen in a flush sequence!"); } - } while ((conn->flush_rtp_timestamp != 0) && (flush_limit <= 8820) && (frame_flushed)); + } while ((conn->flush_rtp_timestamp != 0) && (flush_limit <= 8820) && (curframe->ready == 0)); if (flush_limit == 8820) { debug(1, "Flush hit the 8820 frame limit!"); @@ -1410,11 +1404,14 @@ typedef struct stats { // statistics for running averages int64_t sync_error, correction, drift; } stats_t; -void player_thread_cleanup_handler(void *arg) { - debug(1, "player_thread_cleanup_handler called"); +void player_thread_initial_cleanup_handler(__attribute__((unused)) void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; + debug(3, "Connection %d: player thread main loop exit via player_thread_initial_cleanup_handler.", conn->connection_number); +} - debug(3, "Connection %d: player thread main loop exit.", conn->connection_number); +void player_thread_cleanup_handler(void *arg) { + rtsp_conn_info *conn = (rtsp_conn_info *)arg; + debug(3, "Connection %d: player thread main loop exit via player_thread_cleanup_handler.", conn->connection_number); if (config.statistics_requested) { int rawSeconds = (int)difftime(time(NULL), conn->playstart); @@ -1486,7 +1483,7 @@ void player_thread_cleanup_handler(void *arg) { void *player_thread_func(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; - + pthread_cleanup_push(player_thread_initial_cleanup_handler, arg); conn->packet_count = 0; conn->packet_count_since_flush = 0; conn->previous_random_number = 0; @@ -1510,7 +1507,7 @@ void *player_thread_func(void *arg) { conn); // this sets up incoming rate, bit depth, channels. // No pthread cancellation point in here // This must be after init_decoder - init_buffer(conn); // will need a corresponding deallocation + init_buffer(conn); // will need a corresponding deallocation. No cancellation points in here if (conn->stream.encrypted) { #ifdef CONFIG_MBEDTLS @@ -1694,7 +1691,7 @@ void *player_thread_func(void *arg) { debug(1, "DACP monitor already initialised?"); else // this does not have pthread cancellation points in it (assuming avahi doesn't) - conn->dapo_private_storage = mdns_dacp_monitor(conn->dacp_id); // ?? + conn->dapo_private_storage = mdns_dacp_monitor(conn->dacp_id); #endif conn->framesProcessedInThisEpoch = 0; @@ -1752,9 +1749,6 @@ void *player_thread_func(void *arg) { } } - // set the default volume to whaterver it was before, as stored in the config airplay_volume - debug(3, "Set initial volume to %f.", config.airplay_volume); - player_volume(config.airplay_volume, conn); // ?? int64_t frames_to_drop = 0; // create and start the timing, control and audio receiver threads @@ -1764,6 +1758,10 @@ void *player_thread_func(void *arg) { pthread_cleanup_push(player_thread_cleanup_handler, arg); // undo what's been done so far + // set the default volume to whaterver it was before, as stored in the config airplay_volume + debug(2, "Set initial volume to %f.", config.airplay_volume); + player_volume(config.airplay_volume, conn); // will contain a cancellation point if asked to wait + debug(2, "Play begin"); while (1) { abuf_t *inframe = buffer_get_frame(conn); // this has cancellation point(s) @@ -2480,8 +2478,10 @@ void *player_thread_func(void *arg) { } debug(1, "This should never be called."); - pthread_cleanup_pop(1); - pthread_exit(NULL); + pthread_cleanup_pop(1); // pop the cleanup handler + debug(1, "This should never be called either."); + pthread_cleanup_pop(1); // pop the initial cleanup handler + pthread_exit(NULL); } // takes the volume as specified by the airplay protocol @@ -2756,11 +2756,9 @@ int player_play(rtsp_conn_info *conn) { if (config.buffer_start_fill > BUFFER_FRAMES) die("specified buffer starting fill %d > buffer size %d", config.buffer_start_fill, BUFFER_FRAMES); + debug(1,"Executing the run_this_before_play_begins hook, if any."); command_start(); -#ifdef CONFIG_METADATA - debug(2, "pbeg"); - send_ssnc_metadata('pbeg', NULL, 0, 1); // contains cancellation points -#endif + debug(1,"Create a player thread..."); pthread_t *pt = malloc(sizeof(pthread_t)); if (pt == NULL) die("Couldn't allocate space for pthread_t"); @@ -2772,16 +2770,22 @@ int player_play(rtsp_conn_info *conn) { if (rc) debug(1, "Error setting stack size for player_thread: %s", strerror(errno)); // finished initialising. - pthread_create(pt, &tattr, player_thread_func, (void *)conn); + rc = pthread_create(pt, &tattr, player_thread_func, (void *)conn); + if (rc) + debug(1, "Error creating player_thread: %s", strerror(errno)); pthread_attr_destroy(&tattr); + debug(1,"Player thread successfully created."); +#ifdef CONFIG_METADATA + debug(2, "pbeg"); + send_ssnc_metadata('pbeg', NULL, 0, 1); // contains cancellation points +#endif return 0; } int player_stop(rtsp_conn_info *conn) { - int dl = debuglev; - if (debuglev) - debuglev = 3; // note -- this may be called from another connection thread. + int dl = debuglev; + debuglev = 3; debug(3, "player_stop"); if (conn->player_thread) { debug(2, "player_thread cancel..."); @@ -2795,12 +2799,12 @@ int player_stop(rtsp_conn_info *conn) { debug(2, "pend"); send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points #endif - debuglev = dl; + debuglev = dl; command_stop(); return 0; } else { - debug(3, "player_stop: player thread not running on connection %d.", conn->connection_number); - debuglev = dl; + debug(3, "Connection %d: player thread already deleted.", conn->connection_number); + debuglev = dl; return -1; } } diff --git a/rtsp.c b/rtsp.c index 352088cb..5b887eb7 100644 --- a/rtsp.c +++ b/rtsp.c @@ -2098,11 +2098,8 @@ static void *rtsp_conversation_thread_func(void *pconn) { if (conn->stop == 0) { int err = msg_write_response(conn->fd, resp); if (err) { - int odv = debuglev; - debuglev = 3; debug(1,"A communication error was detected. Closing the play session."); player_stop(conn); - debuglev = odv; } } pthread_cleanup_pop(1); From 6ad0b15ec07a395b3892aad0722e667ec1bcadde Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 19 Nov 2018 15:35:04 +0000 Subject: [PATCH 197/569] Use pthread_cleanup to unlock alsa handle mutex. --- audio_alsa.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 48f6a650..9e28dd47 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -489,8 +489,7 @@ static int init(int argc, char **argv) { } close_mixer(); } - debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(0); // release the mutex + pthread_cleanup_pop(1); // release the mutex } else { // debug(1, "Has no mixer and thus no hardware mute."); } @@ -920,8 +919,8 @@ int delay(long *the_delay) { } } } - debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(0); + // debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(1); // here, occasionally pretend there's a problem with pcm_get_delay() // if ((random() % 100000) < 3) // keep it pretty rare // reply = -EIO; // pretend something bad has happened @@ -955,8 +954,8 @@ static int play(void *buf, int samples) { do_mute(0); } - debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(0); // release the mutex + // debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(1); // release the mutex } if (ret == 0) { pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); @@ -1041,8 +1040,8 @@ static int play(void *buf, int samples) { frame_index = 0; measurement_data_is_valid = 0; } - debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(0); // release the mutex + // debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(1); // release the mutex } return ret; } @@ -1068,8 +1067,8 @@ static void flush(void) { measurement_data_is_valid = 0; alsa_handle = NULL; } - debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(0); // release the mutex + // debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(1); // release the mutex } static void stop(void) { @@ -1125,8 +1124,8 @@ void volume(double vol) { pthread_cleanup_debug_mutex_lock(&alsa_mutex, 1000, 1); volume_set_request = 1; // an external request has been made to set the volume do_volume(vol); - debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(0); // release the mutex + // debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(1); // release the mutex } /* @@ -1153,8 +1152,8 @@ static void mute(int mute_state_requested) { mute_request_pending = 1; overriding_mute_state_requested = mute_state_requested; do_mute(mute_state_requested); - debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(0); // release the mutex + // debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(1); // release the mutex } void do_mute(int mute_state_requested) { From 7258b801c83bed8c23a5be038d5764596aa78b0b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 19 Nov 2018 15:35:47 +0000 Subject: [PATCH 198/569] Add extra checks to look for a hang in TuneBlade --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 335f2a6e..0e0bd333 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.3d21], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d22], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From d4435e2068b157148ec9ca9819fa514ae9badb98 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 19 Nov 2018 18:26:03 +0000 Subject: [PATCH 199/569] Try to make the debug stuff pthread_cancel-proof including the debug / warn / inform / die and debug_mutex calls. Tidy up usage of program_stop. --- audio_alsa.c | 24 ++++++++++++------------ common.c | 24 ++++++++++++++++++++++++ rtsp.c | 4 ++-- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 9e28dd47..faa6ef93 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -919,8 +919,8 @@ int delay(long *the_delay) { } } } - // debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(1); + debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(0); // here, occasionally pretend there's a problem with pcm_get_delay() // if ((random() % 100000) < 3) // keep it pretty rare // reply = -EIO; // pretend something bad has happened @@ -954,8 +954,8 @@ static int play(void *buf, int samples) { do_mute(0); } - // debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(1); // release the mutex + debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(0); // release the mutex } if (ret == 0) { pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); @@ -1040,8 +1040,8 @@ static int play(void *buf, int samples) { frame_index = 0; measurement_data_is_valid = 0; } - // debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(1); // release the mutex + debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(0); // release the mutex } return ret; } @@ -1067,8 +1067,8 @@ static void flush(void) { measurement_data_is_valid = 0; alsa_handle = NULL; } - // debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(1); // release the mutex + debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(0); // release the mutex } static void stop(void) { @@ -1124,8 +1124,8 @@ void volume(double vol) { pthread_cleanup_debug_mutex_lock(&alsa_mutex, 1000, 1); volume_set_request = 1; // an external request has been made to set the volume do_volume(vol); - // debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(1); // release the mutex + debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(0); // release the mutex } /* @@ -1152,8 +1152,8 @@ static void mute(int mute_state_requested) { mute_request_pending = 1; overriding_mute_state_requested = mute_state_requested; do_mute(mute_state_requested); - // debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(1); // release the mutex + debug_mutex_unlock(&alsa_mutex, 0); + pthread_cleanup_pop(0); // release the mutex } void do_mute(int mute_state_requested) { diff --git a/common.c b/common.c index e4724954..e0a35b8d 100644 --- a/common.c +++ b/common.c @@ -99,6 +99,8 @@ int get_requested_connection_state_to_output() { return requested_connection_sta void set_requested_connection_state_to_output(int v) { requested_connection_state_to_output = v; } void die(const char *format, ...) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); char s[1024]; s[0] = 0; uint64_t time_now = get_absolute_time_in_fp(); @@ -121,10 +123,13 @@ void die(const char *format, ...) { daemon_log(LOG_EMERG, "% 20.9f|*fatal error: %s", tss, s); else daemon_log(LOG_EMERG, "fatal error: %s", s); + pthread_setcancelstate(oldState,NULL); exit(1); } void warn(const char *format, ...) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); char s[1024]; s[0] = 0; uint64_t time_now = get_absolute_time_in_fp(); @@ -147,11 +152,14 @@ void warn(const char *format, ...) { daemon_log(LOG_WARNING, "% 20.9f|*warning: %s", tss, s); else daemon_log(LOG_WARNING, "%s", s); + pthread_setcancelstate(oldState,NULL); } void debug(int level, const char *format, ...) { if (level > debuglev) return; + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); char s[1024]; s[0] = 0; uint64_t time_now = get_absolute_time_in_fp(); @@ -173,9 +181,12 @@ void debug(int level, const char *format, ...) { daemon_log(LOG_DEBUG, "% 20.9f|%s", tss, s); else daemon_log(LOG_DEBUG, "%s", s); + pthread_setcancelstate(oldState,NULL); } void inform(const char *format, ...) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); char s[1024]; s[0] = 0; uint64_t time_now = get_absolute_time_in_fp(); @@ -198,6 +209,7 @@ void inform(const char *format, ...) { daemon_log(LOG_INFO, "% 20.9f|%s", tss, s); else daemon_log(LOG_INFO, "%s", s); + pthread_setcancelstate(oldState,NULL); } // The following two functions are adapted slightly and with thanks from Jonathan Leffler's sample @@ -1050,6 +1062,8 @@ void sps_nanosleep(const time_t sec, const long nanosec) { int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, const char *debugmessage, int debuglevel) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); struct timespec tn; clock_gettime(CLOCK_REALTIME, &tn); uint64_t tnfpsec = tn.tv_sec; @@ -1091,6 +1105,7 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, debug(debuglevel, "error %d: \"%s\" waiting for a mutex: \"%s\".", r, strerror_r(r, errstr, sizeof(errstr)), debugmessage); } + pthread_setcancelstate(oldState,NULL); return r; } #endif @@ -1099,6 +1114,8 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, const char *debugmessage, int debuglevel) { // this is not pthread_cancellation safe because is contains a cancellation point + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); useconds_t time_to_wait = dally_time; int r = pthread_mutex_trylock(mutex); while ((r == EBUSY) && (time_to_wait > 0)) { @@ -1121,6 +1138,7 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, strerror_r(r, errstr, sizeof(errstr)), debugmessage); } } + pthread_setcancelstate(oldState,NULL); return r; } #endif @@ -1129,6 +1147,8 @@ int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char const int line, int debuglevel) { if (debuglevel > debuglev) return pthread_mutex_lock(mutex); + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); uint64_t time_at_start = get_absolute_time_in_fp(); char dstring[1000]; memset(dstring, 0, sizeof(dstring)); @@ -1144,6 +1164,7 @@ int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char "debug_mutex_lock at \"%s\" expected max wait: %0.9f, actual wait: %0.9f sec.", dstring, (1.0 * dally_time) / 1000000, delay); } + pthread_setcancelstate(oldState,NULL); return result; } @@ -1151,6 +1172,8 @@ int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *filename, const int int debuglevel) { if (debuglevel > debuglev) return pthread_mutex_unlock(mutex); + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); char dstring[1000]; char errstr[512]; memset(dstring, 0, sizeof(dstring)); @@ -1160,6 +1183,7 @@ int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *filename, const int if (r != 0) debug(1, "error %d: \"%s\" unlocking a mutex: \"%s\".", r, strerror_r(r, errstr, sizeof(errstr)), dstring); + pthread_setcancelstate(oldState,NULL); return r; } diff --git a/rtsp.c b/rtsp.c index 5b887eb7..c819437f 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1954,7 +1954,8 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; // debug(1, "Connection %d: rtsp_conversation_thread_func_cleanup_function called.", // conn->connection_number); - player_stop(conn); + if (conn->player_thread) + player_stop(conn); if (conn->fd > 0) { // debug(1, "Connection %d: closing fd %d.", // conn->connection_number,conn->fd); @@ -2136,7 +2137,6 @@ static void *rtsp_conversation_thread_func(void *pconn) { if (conn->player_thread) debug(1, "RTSP Channel unexpectedly closed or a serious error occured -- closing the " "player thread."); - player_stop(conn); debug(3, "Successful termination of playing thread of RTSP conversation thread %d.", conn->connection_number); debug(3, "Request termination of RTSP conversation thread %d.", conn->connection_number); From a2681ba04c811faa8356574ee6cb8586c9e30f5d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 19 Nov 2018 18:26:42 +0000 Subject: [PATCH 200/569] Try to make the debug stuff pthread_cancel-proof including the debug / warn / inform / die and debug_mutex calls. Tidy up usage of program_stop. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 0e0bd333..8bcb8691 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.3d22], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d23], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 0052389d1ed1e16988d45bcc4e0f80993aa7e13d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 19 Nov 2018 19:43:17 +0000 Subject: [PATCH 201/569] Stop noisy mutex unlock --- audio_alsa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_alsa.c b/audio_alsa.c index faa6ef93..60b75d46 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1152,7 +1152,7 @@ static void mute(int mute_state_requested) { mute_request_pending = 1; overriding_mute_state_requested = mute_state_requested; do_mute(mute_state_requested); - debug_mutex_unlock(&alsa_mutex, 0); + debug_mutex_unlock(&alsa_mutex, 3); pthread_cleanup_pop(0); // release the mutex } From f7342a70dff4af9a0759fb464b1e21138b2c45fa Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 19 Nov 2018 20:12:57 +0000 Subject: [PATCH 202/569] Add the sort-of mutex name to the debug_mutex_lock and _unlock. Also, setting the debug level to 0 turns off the debug logging of them. Handy. --- common.c | 18 +++++++++--------- common.h | 10 +++++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/common.c b/common.c index e0a35b8d..df460bc8 100644 --- a/common.c +++ b/common.c @@ -1143,9 +1143,9 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, } #endif -int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char *filename, +int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char *mutexname, const char *filename, const int line, int debuglevel) { - if (debuglevel > debuglev) + if ((debuglevel > debuglev) || (debuglevel == 0)) return pthread_mutex_lock(mutex); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); @@ -1153,7 +1153,7 @@ int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char char dstring[1000]; memset(dstring, 0, sizeof(dstring)); snprintf(dstring, sizeof(dstring), "%s:%d", filename, line); - debug(3, "debug_mutex_lock at \"%s\".", dstring); + debug(3, "mutex_lock \"%s\" at \"%s\".", mutexname, dstring); int result = sps_pthread_mutex_timedlock(mutex, dally_time, dstring, debuglevel); if (result == ETIMEDOUT) { result = pthread_mutex_lock(mutex); @@ -1161,16 +1161,16 @@ int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char uint64_t divisor = (uint64_t)1 << 32; double delay = 1.0 * time_delay / divisor; debug(debuglevel, - "debug_mutex_lock at \"%s\" expected max wait: %0.9f, actual wait: %0.9f sec.", dstring, + "mutex_lock \"%s\" at \"%s\" expected max wait: %0.9f, actual wait: %0.9f sec.", mutexname, dstring, (1.0 * dally_time) / 1000000, delay); } pthread_setcancelstate(oldState,NULL); return result; } -int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *filename, const int line, +int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexname, const char *filename, const int line, int debuglevel) { - if (debuglevel > debuglev) + if ((debuglevel > debuglev) || (debuglevel == 0)) return pthread_mutex_unlock(mutex); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); @@ -1178,11 +1178,11 @@ int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *filename, const int char errstr[512]; memset(dstring, 0, sizeof(dstring)); snprintf(dstring, sizeof(dstring), "%s:%d", filename, line); - debug(debuglevel, "debug_mutex_unlock at \"%s\".", dstring); + debug(debuglevel, "mutex_unlock \"%s\" at \"%s\".", mutexname, dstring); int r = pthread_mutex_unlock(mutex); if (r != 0) - debug(1, "error %d: \"%s\" unlocking a mutex: \"%s\".", r, - strerror_r(r, errstr, sizeof(errstr)), dstring); + debug(1, "error %d: \"%s\" unlocking mutex \"%s\" at \"%s\".", r, + strerror_r(r, errstr, sizeof(errstr)), mutexname, dstring); pthread_setcancelstate(oldState,NULL); return r; } diff --git a/common.h b/common.h index 81aab4cb..2fec4c26 100644 --- a/common.h +++ b/common.h @@ -299,20 +299,20 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, const char *debugmessage, int debuglevel); // wait for the specified time, checking every 20 milliseconds, and block if it can't acquire the // lock -int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char *filename, +int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char *mutexName, const char *filename, const int line, int debuglevel); -#define debug_mutex_lock(mu, t, d) _debug_mutex_lock(mu, t, __FILE__, __LINE__, d) +#define debug_mutex_lock(mu, t, d) _debug_mutex_lock(mu, t, #mu , __FILE__, __LINE__, d) -int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *filename, const int line, +int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexName, const char *filename, const int line, int debuglevel); -#define debug_mutex_unlock(mu, d) _debug_mutex_unlock(mu, __FILE__, __LINE__, d) +#define debug_mutex_unlock(mu, d) _debug_mutex_unlock(mu, #mu, __FILE__, __LINE__, d) void pthread_cleanup_debug_mutex_unlock(void *arg); #define pthread_cleanup_debug_mutex_lock(mu, t, d) \ - if (_debug_mutex_lock(mu, t, __FILE__, __LINE__, d) == 0) \ + if (_debug_mutex_lock(mu, t, #mu, __FILE__, __LINE__, d) == 0) \ pthread_cleanup_push(pthread_cleanup_debug_mutex_unlock, (void *)mu) char *get_version_string(); // mallocs a string space -- remember to free it afterwards From 95f0f0121a2b86d1fbf13c79bf880eb6b55d611a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 19 Nov 2018 20:28:02 +0000 Subject: [PATCH 203/569] Stop logging an error if the pthread_cond_wait times out. --- player.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player.c b/player.c index 41e528a5..6436651d 100644 --- a/player.c +++ b/player.c @@ -1212,7 +1212,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex, &time_of_wakeup); int rc = pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex, &time_of_wakeup); // this is a pthread cancellation point - if (rc != 0) + if ((rc != 0) && (rc != ETIMEDOUT)) debug(3, "pthread_cond_timedwait returned error code %d.", rc); #endif #ifdef COMPILE_FOR_OSX From d17df82df3f05d85e4ddc0d3cd360b5cf61d1969 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 19 Nov 2018 20:30:00 +0000 Subject: [PATCH 204/569] Add the sort-of mutex name to the debug_mutex_lock and _unlock. Also, setting the debug level to 0 turns off the debug logging of them. Handy. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 8bcb8691..aafe3913 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.3d23], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d24], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 8991a08278ac2c6fe098373b7b6814a6398c8928 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 19 Nov 2018 20:51:49 +0000 Subject: [PATCH 205/569] Make debug_mutex lock and unlock completely quiet if the debug level is chosen to be 0. --- common.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/common.c b/common.c index df460bc8..6694a3e6 100644 --- a/common.c +++ b/common.c @@ -1095,7 +1095,7 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, int r = pthread_mutex_timedlock(mutex, &timeoutTime); - if ((r != 0) && (debugmessage != NULL)) { + if ((debuglevel != 0) && (r != 0) && (debugmessage != NULL)) { char errstr[1000]; if (r == ETIMEDOUT) debug(debuglevel, @@ -1126,7 +1126,7 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, time_to_wait -= st; r = pthread_mutex_trylock(mutex); } - if ((r != 0) && (debugmessage != NULL)) { + if ((debuglevel != 0) && (r != 0) && (debugmessage != NULL)) { char errstr[1000]; if (r == EBUSY) { debug(debuglevel, @@ -1153,7 +1153,8 @@ int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char char dstring[1000]; memset(dstring, 0, sizeof(dstring)); snprintf(dstring, sizeof(dstring), "%s:%d", filename, line); - debug(3, "mutex_lock \"%s\" at \"%s\".", mutexname, dstring); + if (debuglevel != 0) + debug(3, "mutex_lock \"%s\" at \"%s\".", mutexname, dstring); // only if you really ask for it! int result = sps_pthread_mutex_timedlock(mutex, dally_time, dstring, debuglevel); if (result == ETIMEDOUT) { result = pthread_mutex_lock(mutex); @@ -1180,7 +1181,7 @@ int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexname, const cha snprintf(dstring, sizeof(dstring), "%s:%d", filename, line); debug(debuglevel, "mutex_unlock \"%s\" at \"%s\".", mutexname, dstring); int r = pthread_mutex_unlock(mutex); - if (r != 0) + if ((debuglevel != 0) && (r != 0)) debug(1, "error %d: \"%s\" unlocking mutex \"%s\" at \"%s\".", r, strerror_r(r, errstr, sizeof(errstr)), mutexname, dstring); pthread_setcancelstate(oldState,NULL); From ec7d46bc322ad083e7a34684300671198b9e65a2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 20 Nov 2018 12:06:13 +0000 Subject: [PATCH 206/569] Double check debug is not a cancellation point and move the dacp monitor initialisation code to after the subsidiary session threads are created. --- common.c | 7 +++++++ player.c | 34 +++++++++++++++++----------------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/common.c b/common.c index 6694a3e6..2a7d9ad8 100644 --- a/common.c +++ b/common.c @@ -173,6 +173,10 @@ void debug(int level, const char *format, ...) { va_start(args, format); vsnprintf(s, sizeof(s), format, args); va_end(args); + int currentState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, ¤tState); + if (currentState == PTHREAD_CANCEL_ENABLE) + daemon_log(LOG_DEBUG, "Warning -- cancellation is enabled before logging"); if ((config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) daemon_log(LOG_DEBUG, "|% 20.9f|% 20.9f|%s", tss, tsl, s); else if (config.debugger_show_relative_time) @@ -181,6 +185,9 @@ void debug(int level, const char *format, ...) { daemon_log(LOG_DEBUG, "% 20.9f|%s", tss, s); else daemon_log(LOG_DEBUG, "%s", s); + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, ¤tState); + if (currentState == PTHREAD_CANCEL_ENABLE) + daemon_log(LOG_DEBUG, "Warning -- cancellation is enabled after logging"); pthread_setcancelstate(oldState,NULL); } diff --git a/player.c b/player.c index 6436651d..a2a1640d 100644 --- a/player.c +++ b/player.c @@ -1677,23 +1677,6 @@ 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 -// stop looking elsewhere for DACP stuff -#ifdef CONFIG_DACP_CLIENT - // debug(1, "Set dacp server info"); - // this does not have pthread cancellation points in it (assuming avahi doesn't) - set_dacp_server_information(conn); // this will start scanning when a port is registered by the - // code initiated by the mdns_dacp_monitor -#else - // this is only used for compatability, if dacp stuff isn't enabled. - // start an mdns/zeroconf thread to look for DACP messages containing our DACP_ID and getting the - // port number - if (conn->dapo_private_storage) - debug(1, "DACP monitor already initialised?"); - else - // this does not have pthread cancellation points in it (assuming avahi doesn't) - conn->dapo_private_storage = mdns_dacp_monitor(conn->dacp_id); -#endif - conn->framesProcessedInThisEpoch = 0; conn->framesGeneratedInThisEpoch = 0; conn->correctionsRequestedInThisEpoch = 0; @@ -1758,6 +1741,23 @@ void *player_thread_func(void *arg) { pthread_cleanup_push(player_thread_cleanup_handler, arg); // undo what's been done so far +// stop looking elsewhere for DACP stuff +#ifdef CONFIG_DACP_CLIENT + // debug(1, "Set dacp server info"); + // this does not have pthread cancellation points in it (assuming avahi doesn't) + set_dacp_server_information(conn); // this will start scanning when a port is registered by the + // code initiated by the mdns_dacp_monitor +#else + // this is only used for compatability, if dacp stuff isn't enabled. + // start an mdns/zeroconf thread to look for DACP messages containing our DACP_ID and getting the + // port number + if (conn->dapo_private_storage) + debug(1, "DACP monitor already initialised?"); + else + // this does not have pthread cancellation points in it (assuming avahi doesn't) + conn->dapo_private_storage = mdns_dacp_monitor(conn->dacp_id); +#endif + // set the default volume to whaterver it was before, as stored in the config airplay_volume debug(2, "Set initial volume to %f.", config.airplay_volume); player_volume(config.airplay_volume, conn); // will contain a cancellation point if asked to wait From 18497e64911f26bd007a719d6cedc22422d74ca5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 20 Nov 2018 12:07:15 +0000 Subject: [PATCH 207/569] Double check debug is not a cancellation point and move the dacp monitor initialisation code to after the subsidiary session threads are created. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index aafe3913..09cd93d3 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.3d24], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d25], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 6e6c8213e6d865c0a55560a36d29e262f88daadb Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 20 Nov 2018 14:18:33 +0000 Subject: [PATCH 208/569] Put in a cancellation point explicitly in the player_thread main loop. Tidy up some debug messages. --- player.c | 11 +++++------ rtsp.c | 9 ++------- start_script | 6 ++++++ 3 files changed, 13 insertions(+), 13 deletions(-) create mode 100755 start_script diff --git a/player.c b/player.c index a2a1640d..58e3c7d3 100644 --- a/player.c +++ b/player.c @@ -1742,9 +1742,10 @@ void *player_thread_func(void *arg) { pthread_cleanup_push(player_thread_cleanup_handler, arg); // undo what's been done so far // stop looking elsewhere for DACP stuff + #ifdef CONFIG_DACP_CLIENT // debug(1, "Set dacp server info"); - // this does not have pthread cancellation points in it (assuming avahi doesn't) + // may have pthread cancellation points in it -- beware set_dacp_server_information(conn); // this will start scanning when a port is registered by the // code initiated by the mdns_dacp_monitor #else @@ -1754,7 +1755,7 @@ void *player_thread_func(void *arg) { if (conn->dapo_private_storage) debug(1, "DACP monitor already initialised?"); else - // this does not have pthread cancellation points in it (assuming avahi doesn't) + // almost certainly, this has pthread cancellation points in it -- beware conn->dapo_private_storage = mdns_dacp_monitor(conn->dacp_id); #endif @@ -1764,7 +1765,8 @@ void *player_thread_func(void *arg) { debug(2, "Play begin"); while (1) { - abuf_t *inframe = buffer_get_frame(conn); // this has cancellation point(s) + pthread_testcancel(); // allow a pthread_cancel request to take effect. + abuf_t *inframe = buffer_get_frame(conn); // this has cancellation point(s), but it's not guaranteed that they'll aways be executed if (inframe) { inbuf = inframe->data; inbuflength = inframe->length; @@ -2756,9 +2758,7 @@ int player_play(rtsp_conn_info *conn) { if (config.buffer_start_fill > BUFFER_FRAMES) die("specified buffer starting fill %d > buffer size %d", config.buffer_start_fill, BUFFER_FRAMES); - debug(1,"Executing the run_this_before_play_begins hook, if any."); command_start(); - debug(1,"Create a player thread..."); pthread_t *pt = malloc(sizeof(pthread_t)); if (pt == NULL) die("Couldn't allocate space for pthread_t"); @@ -2774,7 +2774,6 @@ int player_play(rtsp_conn_info *conn) { if (rc) debug(1, "Error creating player_thread: %s", strerror(errno)); pthread_attr_destroy(&tattr); - debug(1,"Player thread successfully created."); #ifdef CONFIG_METADATA debug(2, "pbeg"); send_ssnc_metadata('pbeg', NULL, 0, 1); // contains cancellation points diff --git a/rtsp.c b/rtsp.c index c819437f..3dab2b34 100644 --- a/rtsp.c +++ b/rtsp.c @@ -2131,15 +2131,10 @@ static void *rtsp_conversation_thread_func(void *pconn) { debug(1, "rtsp_read_request error %d, packet ignored.", (int)reply); } if (tstop) { + if (conn->player_thread) + debug(1, "RTSP Channel unexpectedly closed or a serious error occurred."); debug(3, "Synchronously terminate playing thread of RTSP conversation thread %d.", conn->connection_number); - - if (conn->player_thread) - debug(1, "RTSP Channel unexpectedly closed or a serious error occured -- closing the " - "player thread."); - debug(3, "Successful termination of playing thread of RTSP conversation thread %d.", - conn->connection_number); - debug(3, "Request termination of RTSP conversation thread %d.", conn->connection_number); conn->stop = 1; } } diff --git a/start_script b/start_script new file mode 100755 index 00000000..a62ee519 --- /dev/null +++ b/start_script @@ -0,0 +1,6 @@ +#!/bin/bash +# My first script + +/bin/sleep 3 +/usr/bin/logger "Shairport Sync Play Script Executed!" + From 8644cfb00ae560d1f867efaa12951d806a60d5b8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 20 Nov 2018 14:19:35 +0000 Subject: [PATCH 209/569] Put in a cancellation point explicitly in the player_thread main loop. Tidy up some debug messages. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 09cd93d3..5a75a007 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.3d25], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d26], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 61cf21fda8f293b539b7d39e410043ff21e3c279 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 20 Nov 2018 14:21:00 +0000 Subject: [PATCH 210/569] Shouldn't have been in the repository. --- start_script | 6 ------ 1 file changed, 6 deletions(-) delete mode 100755 start_script diff --git a/start_script b/start_script deleted file mode 100755 index a62ee519..00000000 --- a/start_script +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -# My first script - -/bin/sleep 3 -/usr/bin/logger "Shairport Sync Play Script Executed!" - From 8f34c181e1e53bab096ddecf34584b90ae5233d4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 20 Nov 2018 22:41:23 +0000 Subject: [PATCH 211/569] Remove inner flush loop from get_buffer. Use flush for fixing large positive error. --- player.c | 135 +++++++++++++++++++++++-------------------------------- 1 file changed, 57 insertions(+), 78 deletions(-) diff --git a/player.c b/player.c index 58e3c7d3..3f468df7 100644 --- a/player.c +++ b/player.c @@ -763,7 +763,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // int16_t buf_fill; uint64_t local_time_now; // struct timespec tn; - abuf_t *curframe = 0; + abuf_t *curframe = NULL; int notified_buffer_empty = 0; // diagnostic only debug_mutex_lock(&conn->ab_mutex, 30000, 1); @@ -778,6 +778,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { local_time_now = get_absolute_time_in_fp(); // type okay debug(3, "buffer_get_frame is iterating"); + // if config.timeout (default 120) seconds have elapsed since the last audio packet was // received, then we should stop. // config.timeout of zero means don't check..., but iTunes may be confused by a long gap @@ -829,59 +830,52 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { } debug_mutex_unlock(&conn->flush_mutex, 3); - uint32_t flush_limit = 0; if (conn->ab_synced) { - do { - curframe = conn->audio_buffer + BUFIDX(conn->ab_read); - if ((conn->ab_read != conn->ab_write) && - (curframe->ready)) { // it could be synced and empty, under - // exceptional circumstances, with the - // frame unused, thus apparently ready + curframe = conn->audio_buffer + BUFIDX(conn->ab_read); + + + + + if ((conn->ab_read != conn->ab_write) && + (curframe->ready)) { // it could be synced and empty, under + // exceptional circumstances, with the + // frame unused, thus apparently ready - if (curframe->sequence_number != conn->ab_read) { - // some kind of sync problem has occurred. - if (BUFIDX(curframe->sequence_number) == BUFIDX(conn->ab_read)) { - // it looks like some kind of aliasing has happened - if (seq_order(conn->ab_read, curframe->sequence_number, conn->ab_read)) { - conn->ab_read = curframe->sequence_number; - debug(1, "Aliasing of buffer index -- reset."); - } - } else { - debug(1, "Inconsistent sequence numbers detected"); + if (curframe->sequence_number != conn->ab_read) { + // some kind of sync problem has occurred. + if (BUFIDX(curframe->sequence_number) == BUFIDX(conn->ab_read)) { + // it looks like some kind of aliasing has happened + if (seq_order(conn->ab_read, curframe->sequence_number, conn->ab_read)) { + conn->ab_read = curframe->sequence_number; + debug(1, "Aliasing of buffer index -- reset."); } - } - - if ((conn->flush_rtp_timestamp != 0) && (curframe->given_timestamp != conn->flush_rtp_timestamp) && - (modulo_32_offset(curframe->given_timestamp, conn->flush_rtp_timestamp) < - conn->input_rate * 10)) { // if it's less than ten seconds - debug(2, "Dropping flushed packet in buffer_get_frame, seqno %u, timestamp %" PRIu32 - ", flushing to " - "timestamp: %" PRIu32 ".", - curframe->sequence_number, curframe->given_timestamp, conn->flush_rtp_timestamp); - curframe->ready = 0; - curframe->resend_level = 0; - flush_limit += curframe->length; - conn->ab_read = SUCCESSOR(conn->ab_read); - conn->initial_reference_time = 0; - conn->initial_reference_timestamp = 0; - } - if ((conn->flush_rtp_timestamp != 0) && - (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) > conn->input_rate / 5) && - (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) < conn->input_rate * 10 )) { - debug(2, "Dropping flush request in buffer_get_frame"); - conn->flush_rtp_timestamp = 0; + } else { + debug(1, "Inconsistent sequence numbers detected"); } } - } while ((conn->flush_rtp_timestamp != 0) && (flush_limit <= 8820) && (curframe->ready == 0)); - if (flush_limit == 8820) { - debug(1, "Flush hit the 8820 frame limit!"); - flush_limit = 0; + if ((conn->flush_rtp_timestamp != 0) && (curframe->given_timestamp != conn->flush_rtp_timestamp) && + (modulo_32_offset(curframe->given_timestamp, conn->flush_rtp_timestamp) < + conn->input_rate * 10)) { // if it's less than ten seconds + debug(2, "Dropping flushed packet in buffer_get_frame, seqno %u, timestamp %" PRIu32 + ", flushing to " + "timestamp: %" PRIu32 ".", + curframe->sequence_number, curframe->given_timestamp, conn->flush_rtp_timestamp); + curframe->ready = 0; + curframe->resend_level = 0; + curframe = NULL; // this will be returned and will cause the loop to go around again + conn->initial_reference_time = 0; + conn->initial_reference_timestamp = 0; + } + if ((conn->flush_rtp_timestamp != 0) && + (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) > conn->input_rate / 5) && + (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) < conn->input_rate * 10 )) { + debug(2, "Dropping flush request in buffer_get_frame"); + conn->flush_rtp_timestamp = 0; + } } - - curframe = conn->audio_buffer + BUFIDX(conn->ab_read); - - if (curframe->ready) { + + 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 // player @@ -1227,15 +1221,16 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { } while (wait); // seq_t read = conn->ab_read; - if (!curframe->ready) { - // debug(1, "Supplying a silent frame for frame %u", read); - conn->missing_packets++; - curframe->given_timestamp = 0; // indicate a silent frame should be substituted + if (curframe) { + if (!curframe->ready) { + // debug(1, "Supplying a silent frame for frame %u", read); + conn->missing_packets++; + curframe->given_timestamp = 0; // indicate a silent frame should be substituted + } + curframe->ready = 0; + curframe->resend_level = 0; } - curframe->ready = 0; - curframe->resend_level = 0; conn->ab_read = SUCCESSOR(conn->ab_read); - pthread_cleanup_pop(1); return curframe; } @@ -1732,8 +1727,6 @@ void *player_thread_func(void *arg) { } } - int64_t frames_to_drop = 0; - // create and start the timing, control and audio receiver threads pthread_create(&conn->rtp_audio_thread, NULL, &rtp_audio_receiver, (void *)conn); pthread_create(&conn->rtp_control_thread, NULL, &rtp_control_receiver, (void *)conn); @@ -1814,19 +1807,6 @@ void *player_thread_func(void *arg) { config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio); free(silence); } - } else if (frames_to_drop) { - /* - if (frames_to_drop > 3 * config.output_rate) { - warn("Shome mhistake shurely: very large number of frames to drop: %" PRId64 - " -- setting it to %" PRId64 ".", - frames_to_drop, 3 * config.output_rate); - frames_to_drop = 3 * config.output_rate; - } - */ - debug(3, "%" PRId64 " frames to drop.", frames_to_drop); - frames_to_drop -= inframe->length; - if (frames_to_drop < 0) - frames_to_drop = 0; } else { int enable_dither = 0; if ((conn->fix_volume != 0x10000) || (conn->input_bit_depth > output_bit_depth) || @@ -2071,8 +2051,9 @@ void *player_thread_func(void *arg) { int64_t filler_length = (int64_t)(config.resyncthreshold * config.output_rate); // number of samples if ((sync_error > 0) && (sync_error > filler_length)) { debug(2, "Large positive sync error: %" PRId64 ".", sync_error); - frames_to_drop = sync_error / conn->output_sample_ratio; - reset_input_flow_metrics(conn); + int64_t local_frames_to_drop = sync_error / conn->output_sample_ratio; + uint32_t frames_to_drop_sized = local_frames_to_drop; + do_flush(inframe->given_timestamp+frames_to_drop_sized,conn); } else if ((sync_error < 0) && ((-sync_error) > filler_length)) { debug(2, "Large negative sync error: %" PRId64 " with should_be_frame_32 of %" PRIu32 ", nt of %" PRId64 " and current_delay of %" PRId64 ".", sync_error, should_be_frame_32, nt, current_delay); @@ -2733,7 +2714,12 @@ void do_flush(uint32_t timestamp, rtsp_conn_info *conn) { // conn->play_segment_reference_frame = 0; reset_input_flow_metrics(conn); debug_mutex_unlock(&conn->flush_mutex, 3); + debug(3, "Flush request made."); +} +void player_flush(uint32_t timestamp, rtsp_conn_info *conn) { + debug(3, "player_flush"); + do_flush(timestamp, conn); #ifdef CONFIG_METADATA // only send a flush metadata message if the first packet has been seen -- it's a bogus message // otherwise @@ -2742,13 +2728,6 @@ void do_flush(uint32_t timestamp, rtsp_conn_info *conn) { send_ssnc_metadata('pfls', NULL, 0, 1); // contains cancellation points } #endif - - debug(3, "Flush request made."); -} - -void player_flush(uint32_t timestamp, rtsp_conn_info *conn) { - debug(3, "player_flush"); - do_flush(timestamp, conn); } int player_play(rtsp_conn_info *conn) { From ccc4fe09af507a0a4597f7b4e88b858dff5843ff Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 20 Nov 2018 22:56:53 +0000 Subject: [PATCH 212/569] Tidy up the formats. --- audio_alsa.c | 41 +++++++++++++++++++------------------ common.c | 34 +++++++++++++++---------------- common.h | 12 +++++------ player.c | 57 ++++++++++++++++++++++++++++------------------------ rtp.c | 9 +++++---- rtsp.c | 50 ++++++++++++++++++++++----------------------- shairport.c | 4 ++-- 7 files changed, 107 insertions(+), 100 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 60b75d46..ab3d172c 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -883,9 +883,9 @@ int delay(long *the_delay) { debug(1, "Error %d in delay(): \"%s\". Delay reported is %d frames.", reply, snd_strerror(reply), *the_delay); derr = snd_pcm_recover(alsa_handle, reply, 1); - if (derr < 0) - warn("Error %d -- could not clear an error after attempting delay(): \"%s\".", derr, - snd_strerror(derr)); + if (derr < 0) + warn("Error %d -- could not clear an error after attempting delay(): \"%s\".", derr, + snd_strerror(derr)); frame_index = 0; measurement_data_is_valid = 0; @@ -913,9 +913,10 @@ int delay(long *the_delay) { if ((derr = snd_pcm_prepare(alsa_handle))) { debug(1, "Error preparing after delay error: \"%s\".", snd_strerror(derr)); derr = snd_pcm_recover(alsa_handle, derr, 1); - if (derr < 0) - warn("Error %d -- could not clear an error after attempting to recover following a delay(): \"%s\".", derr, - snd_strerror(derr)); + if (derr < 0) + warn("Error %d -- could not clear an error after attempting to recover following a " + "delay(): \"%s\".", + derr, snd_strerror(derr)); } } } @@ -964,10 +965,10 @@ static int play(void *buf, int samples) { if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) { if ((err = snd_pcm_prepare(alsa_handle))) { debug(1, "Error preparing after underrun: \"%s\".", snd_strerror(err)); - err = snd_pcm_recover(alsa_handle, err, 1); - if (err < 0) - warn("Error %d -- could not clear an error after detecting underrun in play(): \"%s\".", err, - snd_strerror(err)); + err = snd_pcm_recover(alsa_handle, err, 1); + if (err < 0) + warn("Error %d -- could not clear an error after detecting underrun in play(): \"%s\".", + err, snd_strerror(err)); } frame_index = 0; // we'll be starting over measurement_data_is_valid = 0; @@ -985,9 +986,10 @@ static int play(void *buf, int samples) { debug(1, "Error %d writing %d samples in play(): \"%s\".", err, samples, snd_strerror(err)); err = snd_pcm_recover(alsa_handle, err, 1); - if (err < 0) - warn("Error %d -- could not clear an error after attempting to write %d samples in play(): \"%s\".", err, samples, - snd_strerror(err)); + if (err < 0) + warn("Error %d -- could not clear an error after attempting to write %d samples in " + "play(): \"%s\".", + err, samples, snd_strerror(err)); } if (frame_index == 0) { frames_sent_for_playing = samples; @@ -1004,9 +1006,9 @@ static int play(void *buf, int samples) { debug(1, "Error %d in delay in play(): \"%s\". Delay reported is %d frames.", err2, snd_strerror(err2), fl); err2 = snd_pcm_recover(alsa_handle, err2, 1); - if (err2 < 0) - warn("Error %d -- could not clear an error after checking delay in play(): \"%s\".", err2, - snd_strerror(err2)); + if (err2 < 0) + warn("Error %d -- could not clear an error after checking delay in play(): \"%s\".", + err2, snd_strerror(err2)); frame_index = 0; measurement_data_is_valid = 0; } else { @@ -1033,9 +1035,10 @@ static int play(void *buf, int samples) { if ((err = snd_pcm_prepare(alsa_handle))) { debug(1, "Error preparing after play error: \"%s\".", snd_strerror(err)); err2 = snd_pcm_recover(alsa_handle, err, 1); - if (err2 < 0) - warn("Error %d -- could not clear an error after reporting ALSA device in incorrect state for play: \"%s\".", err2, - snd_strerror(err2)); + if (err2 < 0) + warn("Error %d -- could not clear an error after reporting ALSA device in incorrect " + "state for play: \"%s\".", + err2, snd_strerror(err2)); } frame_index = 0; measurement_data_is_valid = 0; diff --git a/common.c b/common.c index 2a7d9ad8..b0b34b3e 100644 --- a/common.c +++ b/common.c @@ -123,7 +123,7 @@ void die(const char *format, ...) { daemon_log(LOG_EMERG, "% 20.9f|*fatal error: %s", tss, s); else daemon_log(LOG_EMERG, "fatal error: %s", s); - pthread_setcancelstate(oldState,NULL); + pthread_setcancelstate(oldState, NULL); exit(1); } @@ -152,7 +152,7 @@ void warn(const char *format, ...) { daemon_log(LOG_WARNING, "% 20.9f|*warning: %s", tss, s); else daemon_log(LOG_WARNING, "%s", s); - pthread_setcancelstate(oldState,NULL); + pthread_setcancelstate(oldState, NULL); } void debug(int level, const char *format, ...) { @@ -188,7 +188,7 @@ void debug(int level, const char *format, ...) { pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, ¤tState); if (currentState == PTHREAD_CANCEL_ENABLE) daemon_log(LOG_DEBUG, "Warning -- cancellation is enabled after logging"); - pthread_setcancelstate(oldState,NULL); + pthread_setcancelstate(oldState, NULL); } void inform(const char *format, ...) { @@ -216,7 +216,7 @@ void inform(const char *format, ...) { daemon_log(LOG_INFO, "% 20.9f|%s", tss, s); else daemon_log(LOG_INFO, "%s", s); - pthread_setcancelstate(oldState,NULL); + pthread_setcancelstate(oldState, NULL); } // The following two functions are adapted slightly and with thanks from Jonathan Leffler's sample @@ -607,7 +607,7 @@ int config_set_lookup_bool(config_t *cfg, char *where, int *dst) { } void command_set_volume(double volume) { -//this has a cancellation point if waiting is enabled + // this has a cancellation point if waiting is enabled if (config.cmd_set_volume) { /*Spawn a child to run the program.*/ pid_t pid = fork(); @@ -654,7 +654,7 @@ void command_set_volume(double volume) { } void command_start(void) { - //this has a cancellation point if waiting is enabled or a response is awaited + // this has a cancellation point if waiting is enabled or a response is awaited if (config.cmd_start) { pid_t pid; int pipes[2]; @@ -722,7 +722,7 @@ void command_start(void) { } void command_stop(void) { -//this has a cancellation point if waiting is enabled + // this has a cancellation point if waiting is enabled if (config.cmd_stop) { /*Spawn a child to run the program.*/ pid_t pid = fork(); @@ -1112,7 +1112,7 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, debug(debuglevel, "error %d: \"%s\" waiting for a mutex: \"%s\".", r, strerror_r(r, errstr, sizeof(errstr)), debugmessage); } - pthread_setcancelstate(oldState,NULL); + pthread_setcancelstate(oldState, NULL); return r; } #endif @@ -1145,13 +1145,13 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, strerror_r(r, errstr, sizeof(errstr)), debugmessage); } } - pthread_setcancelstate(oldState,NULL); + pthread_setcancelstate(oldState, NULL); return r; } #endif -int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char *mutexname, const char *filename, - const int line, int debuglevel) { +int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char *mutexname, + const char *filename, const int line, int debuglevel) { if ((debuglevel > debuglev) || (debuglevel == 0)) return pthread_mutex_lock(mutex); int oldState; @@ -1169,15 +1169,15 @@ int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char uint64_t divisor = (uint64_t)1 << 32; double delay = 1.0 * time_delay / divisor; debug(debuglevel, - "mutex_lock \"%s\" at \"%s\" expected max wait: %0.9f, actual wait: %0.9f sec.", mutexname, dstring, - (1.0 * dally_time) / 1000000, delay); + "mutex_lock \"%s\" at \"%s\" expected max wait: %0.9f, actual wait: %0.9f sec.", + mutexname, dstring, (1.0 * dally_time) / 1000000, delay); } - pthread_setcancelstate(oldState,NULL); + pthread_setcancelstate(oldState, NULL); return result; } -int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexname, const char *filename, const int line, - int debuglevel) { +int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexname, const char *filename, + const int line, int debuglevel) { if ((debuglevel > debuglev) || (debuglevel == 0)) return pthread_mutex_unlock(mutex); int oldState; @@ -1191,7 +1191,7 @@ int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexname, const cha if ((debuglevel != 0) && (r != 0)) debug(1, "error %d: \"%s\" unlocking mutex \"%s\" at \"%s\".", r, strerror_r(r, errstr, sizeof(errstr)), mutexname, dstring); - pthread_setcancelstate(oldState,NULL); + pthread_setcancelstate(oldState, NULL); return r; } diff --git a/common.h b/common.h index 2fec4c26..0a84b725 100644 --- a/common.h +++ b/common.h @@ -299,20 +299,20 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, const char *debugmessage, int debuglevel); // wait for the specified time, checking every 20 milliseconds, and block if it can't acquire the // lock -int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char *mutexName, const char *filename, - const int line, int debuglevel); +int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char *mutexName, + const char *filename, const int line, int debuglevel); -#define debug_mutex_lock(mu, t, d) _debug_mutex_lock(mu, t, #mu , __FILE__, __LINE__, d) +#define debug_mutex_lock(mu, t, d) _debug_mutex_lock(mu, t, #mu, __FILE__, __LINE__, d) -int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexName, const char *filename, const int line, - int debuglevel); +int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexName, const char *filename, + const int line, int debuglevel); #define debug_mutex_unlock(mu, d) _debug_mutex_unlock(mu, #mu, __FILE__, __LINE__, d) void pthread_cleanup_debug_mutex_unlock(void *arg); #define pthread_cleanup_debug_mutex_lock(mu, t, d) \ - if (_debug_mutex_lock(mu, t, #mu, __FILE__, __LINE__, d) == 0) \ + if (_debug_mutex_lock(mu, t, #mu, __FILE__, __LINE__, d) == 0) \ pthread_cleanup_push(pthread_cleanup_debug_mutex_unlock, (void *)mu) char *get_version_string(); // mallocs a string space -- remember to free it afterwards diff --git a/player.c b/player.c index 3f468df7..a3759114 100644 --- a/player.c +++ b/player.c @@ -454,7 +454,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in // now, if a flush_rtp_timestamp has been defined and the incoming timestamp is "before" it, // drop it… - if ((conn->flush_rtp_timestamp != 0) && (actual_timestamp != conn->flush_rtp_timestamp) && + if ((conn->flush_rtp_timestamp != 0) && (actual_timestamp != conn->flush_rtp_timestamp) && (modulo_32_offset(actual_timestamp, conn->flush_rtp_timestamp) < conn->input_rate * 10)) { // if it's less than 10 seconds debug(2, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %" PRIu32 @@ -465,9 +465,9 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in conn->initial_reference_timestamp = 0; } else { if ((conn->flush_rtp_timestamp != 0) && - (modulo_32_offset(conn->flush_rtp_timestamp, actual_timestamp) > conn->input_rate/5) && + (modulo_32_offset(conn->flush_rtp_timestamp, actual_timestamp) > conn->input_rate / 5) && (modulo_32_offset(conn->flush_rtp_timestamp, actual_timestamp) < conn->input_rate)) { - // between 0.2 and 1 second + // between 0.2 and 1 second debug(2, "Dropping flush request in player_put_packet"); conn->flush_rtp_timestamp = 0; } @@ -778,7 +778,6 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { local_time_now = get_absolute_time_in_fp(); // type okay debug(3, "buffer_get_frame is iterating"); - // if config.timeout (default 120) seconds have elapsed since the last audio packet was // received, then we should stop. // config.timeout of zero means don't check..., but iTunes may be confused by a long gap @@ -832,10 +831,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (conn->ab_synced) { curframe = conn->audio_buffer + BUFIDX(conn->ab_read); - - - - + if ((conn->ab_read != conn->ab_write) && (curframe->ready)) { // it could be synced and empty, under // exceptional circumstances, with the @@ -854,7 +850,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { } } - if ((conn->flush_rtp_timestamp != 0) && (curframe->given_timestamp != conn->flush_rtp_timestamp) && + if ((conn->flush_rtp_timestamp != 0) && + (curframe->given_timestamp != conn->flush_rtp_timestamp) && (modulo_32_offset(curframe->given_timestamp, conn->flush_rtp_timestamp) < conn->input_rate * 10)) { // if it's less than ten seconds debug(2, "Dropping flushed packet in buffer_get_frame, seqno %u, timestamp %" PRIu32 @@ -868,13 +865,15 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { conn->initial_reference_timestamp = 0; } if ((conn->flush_rtp_timestamp != 0) && - (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) > conn->input_rate / 5) && - (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) < conn->input_rate * 10 )) { + (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) > + conn->input_rate / 5) && + (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) < + conn->input_rate * 10)) { debug(2, "Dropping flush request in buffer_get_frame"); conn->flush_rtp_timestamp = 0; } } - + 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 @@ -1401,12 +1400,14 @@ typedef struct stats { // statistics for running averages void player_thread_initial_cleanup_handler(__attribute__((unused)) void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; - debug(3, "Connection %d: player thread main loop exit via player_thread_initial_cleanup_handler.", conn->connection_number); + debug(3, "Connection %d: player thread main loop exit via player_thread_initial_cleanup_handler.", + conn->connection_number); } void player_thread_cleanup_handler(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; - debug(3, "Connection %d: player thread main loop exit via player_thread_cleanup_handler.", conn->connection_number); + debug(3, "Connection %d: player thread main loop exit via player_thread_cleanup_handler.", + conn->connection_number); if (config.statistics_requested) { int rawSeconds = (int)difftime(time(NULL), conn->playstart); @@ -1758,8 +1759,9 @@ void *player_thread_func(void *arg) { debug(2, "Play begin"); while (1) { - pthread_testcancel(); // allow a pthread_cancel request to take effect. - abuf_t *inframe = buffer_get_frame(conn); // this has cancellation point(s), but it's not guaranteed that they'll aways be executed + pthread_testcancel(); // allow a pthread_cancel request to take effect. + abuf_t *inframe = buffer_get_frame(conn); // this has cancellation point(s), but it's not + // guaranteed that they'll aways be executed if (inframe) { inbuf = inframe->data; inbuflength = inframe->length; @@ -1898,8 +1900,8 @@ void *player_thread_func(void *arg) { int64_t rt, nt; rt = reference_timestamp; // uint32_t to int64_t nt = inframe->given_timestamp; // uint32_t to int64_t - rt = rt*conn->output_sample_ratio; - nt = nt*conn->output_sample_ratio; + rt = rt * conn->output_sample_ratio; + nt = nt * conn->output_sample_ratio; uint64_t local_time_now = get_absolute_time_in_fp(); // types okay // struct timespec tn; @@ -2048,15 +2050,18 @@ void *player_thread_func(void *arg) { // sync_error_out_of_bounds, sync_error); sync_error_out_of_bounds = 0; - int64_t filler_length = (int64_t)(config.resyncthreshold * config.output_rate); // number of samples + int64_t filler_length = + (int64_t)(config.resyncthreshold * config.output_rate); // number of samples if ((sync_error > 0) && (sync_error > filler_length)) { debug(2, "Large positive sync error: %" PRId64 ".", sync_error); int64_t local_frames_to_drop = sync_error / conn->output_sample_ratio; uint32_t frames_to_drop_sized = local_frames_to_drop; - do_flush(inframe->given_timestamp+frames_to_drop_sized,conn); + do_flush(inframe->given_timestamp + frames_to_drop_sized, conn); } else if ((sync_error < 0) && ((-sync_error) > filler_length)) { - debug(2, "Large negative sync error: %" PRId64 " with should_be_frame_32 of %" PRIu32 - ", nt of %" PRId64 " and current_delay of %" PRId64 ".", sync_error, should_be_frame_32, nt, current_delay); + debug(2, + "Large negative sync error: %" PRId64 " with should_be_frame_32 of %" PRIu32 + ", nt of %" PRId64 " and current_delay of %" PRId64 ".", + sync_error, should_be_frame_32, nt, current_delay); int64_t silence_length = -sync_error; if (silence_length > (filler_length * 5)) silence_length = filler_length * 5; @@ -2064,7 +2069,7 @@ void *player_thread_func(void *arg) { char *long_silence = malloc(conn->output_bytes_per_frame * silence_length_sized); if (long_silence) { memset(long_silence, 0, conn->output_bytes_per_frame * silence_length_sized); - debug(2,"Play a silence of %d frames.",silence_length_sized); + debug(2, "Play a silence of %d frames.", silence_length_sized); config.output->play(long_silence, silence_length_sized); free(long_silence); } else { @@ -2464,7 +2469,7 @@ void *player_thread_func(void *arg) { pthread_cleanup_pop(1); // pop the cleanup handler debug(1, "This should never be called either."); pthread_cleanup_pop(1); // pop the initial cleanup handler - pthread_exit(NULL); + pthread_exit(NULL); } // takes the volume as specified by the airplay protocol @@ -2777,12 +2782,12 @@ int player_stop(rtsp_conn_info *conn) { debug(2, "pend"); send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points #endif - debuglev = dl; + debuglev = dl; command_stop(); return 0; } else { debug(3, "Connection %d: player thread already deleted.", conn->connection_number); - debuglev = dl; + debuglev = dl; return -1; } } diff --git a/rtp.c b/rtp.c index 4d13199c..3d555ff8 100644 --- a/rtp.c +++ b/rtp.c @@ -621,13 +621,14 @@ void *rtp_timing_receiver(void *arg) { distant_transmit_time = (uint64_t)nctohl(&packet[24]) << 32; distant_transmit_time += nctohl(&packet[28]); - + uint64_t remote_processing_time = 0; - + if (distant_transmit_time >= distant_receive_time) - remote_processing_time = distant_transmit_time - distant_receive_time; + remote_processing_time = distant_transmit_time - distant_receive_time; else { - debug(1, "Yikes: distant_transmit_time is before distant_receive_time; remote processing time set to zero."); + debug(1, "Yikes: distant_transmit_time is before distant_receive_time; remote " + "processing time set to zero."); } // debug(1,"Return trip time: %" PRIu64 " uS, remote processing time: %" PRIu64 " // uS.",(return_time*1000000)>>32,(remote_processing_time*1000000)>>32); diff --git a/rtsp.c b/rtsp.c index 3dab2b34..a9f64bab 100644 --- a/rtsp.c +++ b/rtsp.c @@ -667,7 +667,7 @@ int msg_write_response(int fd, rtsp_message *resp) { pktfree -= n; p += n; if (pktfree <= 1024) { - debug(1,"Attempted to write overlong RTSP packet 1"); + debug(1, "Attempted to write overlong RTSP packet 1"); return -1; } } @@ -680,7 +680,7 @@ int msg_write_response(int fd, rtsp_message *resp) { pktfree -= n; p += n; if (pktfree <= 1024) { - debug(1,"Attempted to write overlong RTSP packet 2"); + debug(1, "Attempted to write overlong RTSP packet 2"); return -2; } debug(1, "Content is \"%s\"", resp->content); @@ -694,7 +694,7 @@ int msg_write_response(int fd, rtsp_message *resp) { p += n; if (pktfree <= 1024) { - debug(1,"Attempted to write overlong RTSP packet 3"); + debug(1, "Attempted to write overlong RTSP packet 3"); return -3; } if (write(fd, pkt, p - pkt) != p - pkt) { @@ -1955,7 +1955,7 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { // debug(1, "Connection %d: rtsp_conversation_thread_func_cleanup_function called.", // conn->connection_number); if (conn->player_thread) - player_stop(conn); + player_stop(conn); if (conn->fd > 0) { // debug(1, "Connection %d: closing fd %d.", // conn->connection_number,conn->fd); @@ -2067,24 +2067,23 @@ static void *rtsp_conversation_thread_func(void *pconn) { if (method_selected == 0) { debug(1, "RTSP thread %d: Unrecognised and unhandled rtsp request \"%s\".", conn->connection_number, req->method); - - - int y = req->contentlength; - if (y > 0) { - char obf[4096]; - if (y > 4096) - y = 4096; - char *p = req->content; - char *obfp = obf; - int obfc; - for (obfc = 0; obfc < y; obfc++) { - snprintf(obfp, 3, "%02X", (unsigned int)*p); - p++; - obfp += 2; - }; - *obfp = 0; - debug(1, "Content: \"%s\".",obf); - } + + int y = req->contentlength; + if (y > 0) { + char obf[4096]; + if (y > 4096) + y = 4096; + char *p = req->content; + char *obfp = obf; + int obfc; + for (obfc = 0; obfc < y; obfc++) { + snprintf(obfp, 3, "%02X", (unsigned int)*p); + p++; + obfp += 2; + }; + *obfp = 0; + debug(1, "Content: \"%s\".", obf); + } } } debug(debug_level, "RTSP thread %d: RTSP Response:", conn->connection_number); @@ -2099,8 +2098,8 @@ static void *rtsp_conversation_thread_func(void *pconn) { if (conn->stop == 0) { int err = msg_write_response(conn->fd, resp); if (err) { - debug(1,"A communication error was detected. Closing the play session."); - player_stop(conn); + debug(1, "A communication error was detected. Closing the play session."); + player_stop(conn); } } pthread_cleanup_pop(1); @@ -2212,14 +2211,13 @@ void rtsp_listen_loop(void) { // See: https://github.com/mikebrady/shairport-sync/issues/329 fcntl(fd, F_SETFD, FD_CLOEXEC); ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - + struct timeval tv; tv.tv_sec = 3; // three seconds write timeout tv.tv_usec = 0; if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1) debug(1, "Error %d setting send timeout for rtsp writeback.", errno); - #ifdef IPV6_V6ONLY // some systems don't support v4 access on v6 sockets, but some do. // since we need to account for two sockets we might as well diff --git a/shairport.c b/shairport.c index d4a29410..5e36e911 100644 --- a/shairport.c +++ b/shairport.c @@ -25,8 +25,6 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include #include #include #include @@ -37,7 +35,9 @@ #include #include #include +#include #include +#include #include #include From 4911dacc7c9309f3c3fa647218ce738eff5bc457 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 20 Nov 2018 22:58:06 +0000 Subject: [PATCH 213/569] Rationalise some of the flush code -- reomve an inner loop, use the flush facility for skipping to sync. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 5a75a007..4a5f68d3 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.3d26], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d27], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 0529a581794b0789a136dfda6d2a129c406269cc Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 20 Nov 2018 23:49:22 +0000 Subject: [PATCH 214/569] Wait properly when a session is being interrupted. --- rtsp.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/rtsp.c b/rtsp.c index 3dab2b34..36d269ae 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1556,13 +1556,19 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag } if (should_wait) { - usleep(1000000); // here, it is possible for other connections to come in and nab the player. - debug(1, "Try to get the player now"); + useconds_t time_remaining = 3000000; // three seconds + + while ((time_remaining > 0) && (have_the_player == 0)) { + if (pthread_mutex_trylock(&play_lock) == 0) + have_the_player = 1; + else { + usleep(100000); + time_remaining -= 100000; + } + } + if (have_the_player == 0) + debug(1, "Connection %d: ANNOUNCE failed to get the player", conn->connection_number); } - if (pthread_mutex_trylock(&play_lock) == 0) - have_the_player = 1; - else - debug(1, "Connection %d: ANNOUNCE failed to get the player", conn->connection_number); } if (have_the_player) { From 303a85c3b5f34bf8b35e0900afcd8e330e18d38d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 21 Nov 2018 21:32:18 +0000 Subject: [PATCH 215/569] Add back in the ability to interrupt a session. Clean up a few debug messages. --- player.c | 6 +++--- rtp.c | 1 + rtsp.c | 45 +++++++++++++++++++++++---------------------- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/player.c b/player.c index a3759114..8b16f3c1 100644 --- a/player.c +++ b/player.c @@ -1479,7 +1479,7 @@ void player_thread_cleanup_handler(void *arg) { void *player_thread_func(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; - pthread_cleanup_push(player_thread_initial_cleanup_handler, arg); + // pthread_cleanup_push(player_thread_initial_cleanup_handler, arg); conn->packet_count = 0; conn->packet_count_since_flush = 0; conn->previous_random_number = 0; @@ -2467,8 +2467,8 @@ void *player_thread_func(void *arg) { debug(1, "This should never be called."); pthread_cleanup_pop(1); // pop the cleanup handler - debug(1, "This should never be called either."); - pthread_cleanup_pop(1); // pop the initial cleanup handler +// debug(1, "This should never be called either."); +// pthread_cleanup_pop(1); // pop the initial cleanup handler pthread_exit(NULL); } diff --git a/rtp.c b/rtp.c index 3d555ff8..8995571f 100644 --- a/rtp.c +++ b/rtp.c @@ -104,6 +104,7 @@ void rtp_audio_receiver_cleanup_handler(void *arg) { shutdown(conn->audio_socket, SHUT_RDWR); debug(1, "close audio socket."); close(conn->audio_socket); + debug(3, "Audio Receiver Cleanup Successful."); } diff --git a/rtsp.c b/rtsp.c index 1cd3ddab..9f34c5d1 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1537,26 +1537,27 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag if (!playing_conn) die("Non existent playing_conn with play_lock enabled."); - debug(1, "Connection %d: already playing when asked by connection %d.", + debug(1, "Connection %d: ANNOUNCE: already playing when checked by connection %d.", playing_conn->connection_number, conn->connection_number); if (playing_conn->stop) { - debug(1, "Playing connection is already shutting down; waiting for it..."); + debug(1, "Connetion %d: ANNOUNCE: already shutting down; waiting for it...",playing_conn->connection_number); should_wait = 1; } else if (config.allow_session_interruption == 1) { // some thread has the player ... ask it to relinquish the thread - debug(1, "ANNOUNCE: playing connection %d being interrupted by connection %d.", - playing_conn->connection_number, conn->connection_number); + debug(1, "Connection %d: ANNOUNCE: asking playing connection %d to shut down.", + conn->connection_number, playing_conn->connection_number); if (playing_conn == conn) { - debug(1, "ANNOUNCE asking to stop itself! Nothing done."); + debug(1, "Connection %d: ANNOUNCE: asking to stop itself! Nothing done.",conn->connection_number); } else { player_stop(playing_conn); playing_conn->stop = 1; + pthread_cancel(playing_conn->thread); // asking the RTSP thread to exit should_wait = 1; } } if (should_wait) { - useconds_t time_remaining = 3000000; // three seconds + useconds_t time_remaining = 3000000; while ((time_remaining > 0) && (have_the_player == 0)) { if (pthread_mutex_trylock(&play_lock) == 0) @@ -1566,7 +1567,10 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag time_remaining -= 100000; } } - if (have_the_player == 0) + if (have_the_player == 1) { + debug(1, "Connection %d: ANNOUNCE got the player", conn->connection_number); + usleep(1000000); // this is just to let everything settle a bit + } else debug(1, "Connection %d: ANNOUNCE failed to get the player", conn->connection_number); } } @@ -1973,7 +1977,7 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { } rtp_terminate(conn); if (playing_conn == conn) { - debug(3, "Unlocking play lock on RTSP conversation thread %d.", conn->connection_number); + debug(3, "Connection %d: Unlocking play lock.", conn->connection_number); playing_conn = NULL; pthread_mutex_unlock(&play_lock); } @@ -2048,7 +2052,7 @@ 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, "RTSP thread %d received an RTSP Packet of type \"%s\":", + debug(debug_level, "Connection %d: Received an RTSP Packet of type \"%s\":", conn->connection_number, req->method), debug_print_msg_headers(debug_level, req); @@ -2071,7 +2075,7 @@ static void *rtsp_conversation_thread_func(void *pconn) { } } if (method_selected == 0) { - debug(1, "RTSP thread %d: Unrecognised and unhandled rtsp request \"%s\".", + debug(1, "Connection %d: Unrecognised and unhandled rtsp request \"%s\".", conn->connection_number, req->method); int y = req->contentlength; @@ -2092,7 +2096,7 @@ static void *rtsp_conversation_thread_func(void *pconn) { } } } - debug(debug_level, "RTSP thread %d: RTSP Response:", conn->connection_number); + debug(debug_level, "Connection %d: RTSP Response:", conn->connection_number); debug_print_msg_headers(debug_level, resp); fd_set writefds; FD_ZERO(&writefds); @@ -2104,8 +2108,8 @@ static void *rtsp_conversation_thread_func(void *pconn) { if (conn->stop == 0) { int err = msg_write_response(conn->fd, resp); if (err) { - debug(1, "A communication error was detected. Closing the play session."); - player_stop(conn); + debug(1, "Connection %d: Unable to write an RTSP message response. Terminating the connection.", conn->connection_number); + conn->stop = 1; } } pthread_cleanup_pop(1); @@ -2122,24 +2126,21 @@ static void *rtsp_conversation_thread_func(void *pconn) { tstop = 1; else { if (reply == rtsp_read_request_response_channel_closed) - debug(2, "RTSP channel unexpectedly closed -- will try again %d time(s).", - rtsp_read_request_attempt_count); + debug(2, "Connection %d: RTSP channel unexpectedly closed -- will try again %d time(s).", + conn->connection_number, rtsp_read_request_attempt_count); if (reply == rtsp_read_request_response_read_error) - debug(2, "RTSP channel read error -- will try again %d time(s).", - rtsp_read_request_attempt_count); + debug(2, "Connection %d: RTSP channel read error -- will try again %d time(s).", + conn->connection_number, rtsp_read_request_attempt_count); usleep(20000); } } else { tstop = 1; } } else { - debug(1, "rtsp_read_request error %d, packet ignored.", (int)reply); + debug(1, "Connection %d: rtsp_read_request error %d, packet ignored.", conn->connection_number, (int)reply); } if (tstop) { - if (conn->player_thread) - debug(1, "RTSP Channel unexpectedly closed or a serious error occurred."); - debug(3, "Synchronously terminate playing thread of RTSP conversation thread %d.", - conn->connection_number); + debug(3, "Connection %d: Terminate RTSP connection.", conn->connection_number); conn->stop = 1; } } From d3aca80c9e3ffdb64edb648eaa88526d77ec9c76 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 21 Nov 2018 21:33:21 +0000 Subject: [PATCH 216/569] Add back in the ability to interrupt a session. Clean up a few debug messages. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 4a5f68d3..efc335ce 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.3d27], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d28], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 23eb7d7038cad59fee8ea7cbbfc3bafd296d30c2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 22 Nov 2018 10:08:00 +0000 Subject: [PATCH 217/569] Update shairport-sync.conf --- scripts/shairport-sync.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 4697a6a3..42c085b0 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -107,9 +107,9 @@ pipe = // name = "/path/to/pipe"; // there is no default pipe name for the output }; -// These are no configuration file parameters for the "stdout" audio back end. No interpolation is done. +// There are no configuration file parameters for the "stdout" audio back end. No interpolation is done. -// These are no configuration file parameters for the "ao" audio back end. No interpolation is done. +// There are no configuration file parameters for the "ao" audio back end. No interpolation is done. // Static latency settings are deprecated and the settings have been removed. From 0e6835589ca9adcbf0dd6906132e59ec127de643 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 22 Nov 2018 21:57:54 +0000 Subject: [PATCH 218/569] Try making dropping and closing the DAC un-cancelable to stop the bcm DMA problem. Add some diagnostics. --- audio_alsa.c | 3 +++ player.c | 21 +++++++++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index ab3d172c..3039fa65 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1051,6 +1051,8 @@ static int play(void *buf, int samples) { static void flush(void) { // debug(2,"audio_alsa flush called."); + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); int derr; do_mute(1); @@ -1072,6 +1074,7 @@ static void flush(void) { } debug_mutex_unlock(&alsa_mutex, 3); pthread_cleanup_pop(0); // release the mutex + pthread_setcancelstate(oldState, NULL); } static void stop(void) { diff --git a/player.c b/player.c index 8b16f3c1..751621e4 100644 --- a/player.c +++ b/player.c @@ -464,6 +464,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in conn->initial_reference_time = 0; conn->initial_reference_timestamp = 0; } else { + /* if ((conn->flush_rtp_timestamp != 0) && (modulo_32_offset(conn->flush_rtp_timestamp, actual_timestamp) > conn->input_rate / 5) && (modulo_32_offset(conn->flush_rtp_timestamp, actual_timestamp) < conn->input_rate)) { @@ -471,6 +472,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in debug(2, "Dropping flush request in player_put_packet"); conn->flush_rtp_timestamp = 0; } + */ abuf_t *abuf = 0; @@ -849,7 +851,10 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { debug(1, "Inconsistent sequence numbers detected"); } } - + + //if (conn->flush_rtp_timestamp != 0) + // debug(2,"flush_rtp_timestamp is %" PRIx32 " and curframe->given_timestamp is %" PRIx32 ".", conn->flush_rtp_timestamp , curframe->given_timestamp); + if ((conn->flush_rtp_timestamp != 0) && (curframe->given_timestamp != conn->flush_rtp_timestamp) && (modulo_32_offset(curframe->given_timestamp, conn->flush_rtp_timestamp) < @@ -945,11 +950,13 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { conn->first_packet_time_to_play = should_be_time; - if (local_time_now >= conn->first_packet_time_to_play) { + if (local_time_now > conn->first_packet_time_to_play) { + uint64_t lateness = local_time_now - conn->first_packet_time_to_play; + lateness = (lateness * 1000000) >> 32; // microseconds debug( 1, - "First packet is late! It should have played before now. Flushing 0.5 seconds"); - player_flush(conn->first_packet_timestamp + 5 * 4410 * conn->output_sample_ratio, + "First packet is %" PRIu64 " microseconds late! Flushing 0.5 seconds",lateness); + do_flush(conn->first_packet_timestamp + 5 * 4410, conn); } } @@ -978,8 +985,10 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // dynamic adjustment int64_t filler_size = max_dac_delay; - if (local_time_now >= conn->first_packet_time_to_play) { - // debug(1,"Gone past starting time"); + if (local_time_now > conn->first_packet_time_to_play) { + uint64_t lateness = local_time_now - conn->first_packet_time_to_play; + lateness = (lateness * 1000000) >> 32; // microseconds + debug(1,"Gone past starting time by %" PRIu64 " microseconds.", lateness); have_sent_prefiller_silence = 1; conn->ab_buffering = 0; From 3709ac8180a1c7ee9d8c3eaf02c385b06d48fbd9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 23 Nov 2018 08:53:27 +0000 Subject: [PATCH 219/569] experimental --- audio_alsa.c | 3 +- rtp.c | 46 +++++++++++----- rtsp.c | 151 ++++++++++++++++++++++++++++++--------------------- 3 files changed, 125 insertions(+), 75 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 3039fa65..5ca4836c 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -489,7 +489,8 @@ static int init(int argc, char **argv) { } close_mixer(); } - pthread_cleanup_pop(1); // release the mutex + debug_mutex_unlock(&alsa_mutex, 3); // release the mutex + pthread_cleanup_pop(0); } else { // debug(1, "Has no mixer and thus no hardware mute."); } diff --git a/rtp.c b/rtp.c index 8995571f..92837e58 100644 --- a/rtp.c +++ b/rtp.c @@ -58,7 +58,7 @@ void rtp_initialise(rtsp_conn_info *conn) { } void rtp_terminate(rtsp_conn_info *conn) { - + conn->reference_timestamp = 0; // destroy the timer mutex int rc = pthread_mutex_destroy(&conn->reference_time_mutex); if (rc) @@ -125,9 +125,15 @@ void *rtp_audio_receiver(void *arg) { float stat_mean = 0.0; float stat_M2 = 0.0; + int frame_count = 0; ssize_t nread; while (1) { nread = recv(conn->audio_socket, packet, sizeof(packet), 0); + + frame_count++; + if (frame_count<10) + debug(1,"Recv'ed %d bytes. First two bytes are %02X,%02X ",nread,packet[0],packet[1]); + else { uint64_t local_time_now_fp = get_absolute_time_in_fp(); if (time_of_previous_packet_fp) { @@ -167,6 +173,19 @@ void *rtp_audio_receiver(void *arg) { // increment last_seqno and see if it's the same as the incoming seqno if (type == 0x60) { // regular audio data + + /* + char obf[4096]; + char *obfp = obf; + int obfc; + for (obfc=0;obfclocal_to_remote_time_difference) { // need a time packet to be interchanged @@ -585,10 +605,10 @@ void *rtp_timing_receiver(void *arg) { (drand48() > config.diagnostic_drop_packet_fraction)) { arrival_time = get_absolute_time_in_fp(); - // ssize_t plen = nread; + ssize_t plen = nread; // debug(1,"Packet Received on Timing Port."); if (packet[1] == 0xd3) { // timing reply - /* + char obf[4096]; char *obfp = obf; int obfc; @@ -597,8 +617,8 @@ void *rtp_timing_receiver(void *arg) { obfp+=2; }; *obfp=0; - //debug(1,"Timing Packet Received: \"%s\"",obf); - */ + debug(1,"Timing Packet Received: \"%s\"",obf); + // arrival_time = ((uint64_t)att.tv_sec<<32)+((uint64_t)att.tv_nsec<<32)/1000000000; // departure_time = ((uint64_t)dtt.tv_sec<<32)+((uint64_t)dtt.tv_nsec<<32)/1000000000; diff --git a/rtsp.c b/rtsp.c index 9f34c5d1..db6e99f4 100644 --- a/rtsp.c +++ b/rtsp.c @@ -89,7 +89,9 @@ enum rtsp_read_request_response { }; // Mike Brady's part... -static pthread_mutex_t play_lock = PTHREAD_MUTEX_INITIALIZER; + +// always lock use this when accessing the playing conn value +static pthread_mutex_t playing_conn_lock = PTHREAD_MUTEX_INITIALIZER; // every time we want to retain or release a reference count, lock it with this // if a reference count is read as zero, it means the it's being deallocated. @@ -279,8 +281,6 @@ void cancel_all_RTSP_threads(void) { for (i = 0; i < nconns; i++) { debug(1, "Connection %d: joining.", conns[i]->connection_number); pthread_join(conns[i]->thread, NULL); - if (conns[i] == playing_conn) - playing_conn = NULL; free(conns[i]); } } @@ -295,8 +295,6 @@ void cleanup_threads(void) { conns[i]->connection_number); pthread_join(conns[i]->thread, &retval); debug(3, "RTSP connection thread %d deleted...", conns[i]->connection_number); - if (conns[i] == playing_conn) - playing_conn = NULL; free(conns[i]); nconns--; if (nconns) @@ -461,7 +459,7 @@ int msg_handle_line(rtsp_message **pmsg, char *line) { char *sp, *p; sp = NULL; // this is to quieten a compiler warning - // debug(1, "received request: %s", line); + debug(1, "received request: %s", line); p = strtok_r(line, " ", &sp); if (!p) @@ -898,8 +896,9 @@ void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { error: warn("Connection %d: SETUP -- Error in setup request -- unlocking play lock.", conn->connection_number); - playing_conn = NULL; - pthread_mutex_unlock(&play_lock); + debug_mutex_lock(&playing_conn_lock, 1000000 , 3); + playing_conn = NULL; // this connection definitely doesn't have the play lock + debug_mutex_unlock(&playing_conn_lock, 3); resp->respcode = 451; // invalid arguments } @@ -1523,62 +1522,76 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_m static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(2, "Connection %d: ANNOUNCE", conn->connection_number); + + int have_the_player = 0; - - // interrupt session if permitted - if (pthread_mutex_trylock(&play_lock) == 0) { + int should_wait = 0; + + // try to become the current playing_conn + + debug_mutex_lock(&playing_conn_lock, 1000000 , 3); //get it + + if (playing_conn == NULL) { + playing_conn = conn; have_the_player = 1; - } else if ((playing_conn) && - (playing_conn->connection_number == conn->connection_number)) { // duplicate ANNOUNCE - warn("Duplicate ANNOUNCE, by the look of it!"); - have_the_player = 1; - } else { - int should_wait = 0; - - if (!playing_conn) - die("Non existent playing_conn with play_lock enabled."); - debug(1, "Connection %d: ANNOUNCE: already playing when checked by connection %d.", - playing_conn->connection_number, conn->connection_number); - if (playing_conn->stop) { - debug(1, "Connetion %d: ANNOUNCE: already shutting down; waiting for it...",playing_conn->connection_number); + } else if (playing_conn == conn) { + have_the_player = 1; + warn("Duplicate ANNOUNCE, by the look of it!"); + } else if (playing_conn->stop) { + debug(1, "Connection %d: ANNOUNCE: already shutting down; waiting for it...",playing_conn->connection_number); should_wait = 1; - } else if (config.allow_session_interruption == 1) { - // some thread has the player ... ask it to relinquish the thread - debug(1, "Connection %d: ANNOUNCE: asking playing connection %d to shut down.", + } else if (config.allow_session_interruption == 1) { + debug(1, "Connection %d: ANNOUNCE: asking playing connection %d to shut down.", conn->connection_number, playing_conn->connection_number); - if (playing_conn == conn) { - debug(1, "Connection %d: ANNOUNCE: asking to stop itself! Nothing done.",conn->connection_number); - } else { - player_stop(playing_conn); - playing_conn->stop = 1; - pthread_cancel(playing_conn->thread); // asking the RTSP thread to exit - should_wait = 1; - } - } + playing_conn->stop = 1; + pthread_cancel(playing_conn->thread); // asking the RTSP thread to exit + should_wait = 1; + } + debug_mutex_unlock(&playing_conn_lock, 3); - if (should_wait) { - useconds_t time_remaining = 3000000; + if (should_wait) { + useconds_t time_remaining = 3000000; - while ((time_remaining > 0) && (have_the_player == 0)) { - if (pthread_mutex_trylock(&play_lock) == 0) - have_the_player = 1; - else { + while ((time_remaining > 0) && (have_the_player == 0)) { + debug_mutex_lock(&playing_conn_lock, 1000000 , 3); //get it + if (playing_conn == NULL) { + playing_conn = conn; + have_the_player = 1; + } + debug_mutex_unlock(&playing_conn_lock, 3); + + if (have_the_player == 0) { usleep(100000); time_remaining -= 100000; } } - if (have_the_player == 1) { - debug(1, "Connection %d: ANNOUNCE got the player", conn->connection_number); - usleep(1000000); // this is just to let everything settle a bit - } else - debug(1, "Connection %d: ANNOUNCE failed to get the player", conn->connection_number); + + if (have_the_player == 1) { + debug(1, "Connection %d: ANNOUNCE got the player", conn->connection_number); + usleep(2000000); // this is just to let everything settle a bit + } else { + debug(1, "Connection %d: ANNOUNCE failed to get the player", conn->connection_number); } } if (have_the_player) { - playing_conn = conn; // the present connection is now playing debug(3, "RTSP conversation thread %d has acquired play lock.", conn->connection_number); + + { + char *cp = req->content; + int cp_left = req->contentlength; + while (cp_left > 1) { + if (strlen(cp) != 0) + warn(" %s", cp); + cp += strlen(cp) + 1; + cp_left -= strlen(cp) + 1; + } + } + + + resp->respcode = 456; // 456 - Header Field Not Valid for Resource + char *pssid = NULL; char *paesiv = NULL; char *prsaaeskey = NULL; char *pfmtp = NULL; @@ -1601,6 +1614,9 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag goto out; } + if (!strncmp(cp, "o=iTunes", 7)) + pssid = cp + 8; + if (!strncmp(cp, "a=fmtp:", 7)) pfmtp = cp + 7; @@ -1618,6 +1634,11 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag cp = next; } + + if (pssid) { + uint32_t ssid = uatoi(pssid); + debug(1, "Synchronisation Source Identifier: %08X,%u", ssid,ssid); + } if (pminlatency) { conn->minimum_latency = atoi(pminlatency); @@ -1719,10 +1740,11 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag out: if (resp->respcode != 200 && resp->respcode != 453) { - debug(1, "Error in handling ANNOUNCE on conversation thread %d. Unlocking the play lock.", + debug(1, "Connection %d: Error in handling ANNOUNCE. Unlocking the play lock.", conn->connection_number); + debug_mutex_lock(&playing_conn_lock, 1000000 , 3); //get it playing_conn = NULL; - pthread_mutex_unlock(&play_lock); + debug_mutex_unlock(&playing_conn_lock, 3); } } @@ -1970,26 +1992,19 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { // debug(1, "Connection %d: closing fd %d.", // conn->connection_number,conn->fd); close(conn->fd); + usleep(1000000); } if (conn->auth_nonce) { free(conn->auth_nonce); conn->auth_nonce = NULL; } rtp_terminate(conn); - if (playing_conn == conn) { - debug(3, "Connection %d: Unlocking play lock.", conn->connection_number); - playing_conn = NULL; - pthread_mutex_unlock(&play_lock); - } - + if (conn->dacp_id) { free(conn->dacp_id); conn->dacp_id = NULL; } - debug(2, "Connection %d: RTSP thread terminated.", conn->connection_number); - conn->running = 0; - // remove flow control and mutexes int rc = pthread_cond_destroy(&conn->flowcontrol); if (rc) @@ -2001,6 +2016,16 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { rc = pthread_mutex_destroy(&conn->flush_mutex); if (rc) debug(1, "Connection %d: error %d destroying flush_mutex.", conn->connection_number, rc); + + debug_mutex_lock(&playing_conn_lock, 1000000 , 3); //get it + if (playing_conn == conn) { + debug(1, "Connection %d: Unlocking play lock.", conn->connection_number); + playing_conn = NULL; + } + debug_mutex_unlock(&playing_conn_lock, 3); + + debug(2, "Connection %d: RTSP thread terminated.", conn->connection_number); + conn->running = 0; } void msg_cleanup_function(void *arg) { @@ -2109,6 +2134,8 @@ static void *rtsp_conversation_thread_func(void *pconn) { int err = msg_write_response(conn->fd, resp); if (err) { debug(1, "Connection %d: Unable to write an RTSP message response. Terminating the connection.", conn->connection_number); + if (debuglev != 0) + debuglev = 3; // see what happens next conn->stop = 1; } } @@ -2122,9 +2149,11 @@ static void *rtsp_conversation_thread_func(void *pconn) { (reply == rtsp_read_request_response_read_error)) { if (conn->player_thread) { rtsp_read_request_attempt_count--; - if (rtsp_read_request_attempt_count == 0) + if (rtsp_read_request_attempt_count == 0) { tstop = 1; - else { + if ((reply == rtsp_read_request_response_read_error) && (debuglev != 0)) + debuglev = 3; // see what happens next + } else { if (reply == rtsp_read_request_response_channel_closed) debug(2, "Connection %d: RTSP channel unexpectedly closed -- will try again %d time(s).", conn->connection_number, rtsp_read_request_attempt_count); From 093bf1dd46970a140a9199302bccb606930b0cf8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 23 Nov 2018 13:29:09 +0000 Subject: [PATCH 220/569] Rotate usage of UDP ports if a session is interrupted. Avoids out-of-date packets landing in a new session. --- common.c | 17 +++++++++++++++++ common.h | 11 +++++++++++ player.c | 6 +++--- rtp.c | 25 ++++++++++++------------- rtsp.c | 22 ++++++++++++++-------- scripts/shairport-sync.conf | 2 +- shairport.c | 6 +++--- 7 files changed, 61 insertions(+), 28 deletions(-) diff --git a/common.c b/common.c index b0b34b3e..f735dba4 100644 --- a/common.c +++ b/common.c @@ -94,6 +94,23 @@ volatile int debuglev = 0; sigset_t pselect_sigset; +static uint16_t UDPPortIndex = 0; + +void resetFreeUDPPort() { + UDPPortIndex = 0; +} + +uint16_t nextFreeUDPPort() { + + if (UDPPortIndex == 0) + UDPPortIndex = config.udp_port_base; + else if (UDPPortIndex == (config.udp_port_base + config.udp_port_range)) + UDPPortIndex = config.udp_port_base; + else + UDPPortIndex++; + return UDPPortIndex; +} + int get_requested_connection_state_to_output() { return requested_connection_state_to_output; } void set_requested_connection_state_to_output(int v) { requested_connection_state_to_output = v; } diff --git a/common.h b/common.h index 0a84b725..a043d0f7 100644 --- a/common.h +++ b/common.h @@ -243,6 +243,17 @@ void r64arrayinit(); uint64_t ranarray64u(); int64_t ranarray64i(); +// if you are breaking in to a session, you need to avoid the ports of the current session +// if you are law-abiding, then you can reuse the ports. +// so, you can reset the free UDP ports minder when you're legit, and leave it otherwise + +// the downside of using different ports each time is that it might make the firewall +// rules a bit more complex, as they need to allow more than the minimum three ports. +// a range of 10 is suggested anyway + +void resetFreeUDPPort(); +uint16_t nextFreeUDPPort(); + volatile int debuglev; void die(const char *format, ...); void warn(const char *format, ...); diff --git a/player.c b/player.c index 751621e4..f1837573 100644 --- a/player.c +++ b/player.c @@ -457,7 +457,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in if ((conn->flush_rtp_timestamp != 0) && (actual_timestamp != conn->flush_rtp_timestamp) && (modulo_32_offset(actual_timestamp, conn->flush_rtp_timestamp) < conn->input_rate * 10)) { // if it's less than 10 seconds - debug(2, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %" PRIu32 + debug(3, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %" PRIu32 ", flushing to " "timestamp: %" PRIu32 ".", seqno, actual_timestamp, conn->flush_rtp_timestamp); @@ -859,7 +859,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { (curframe->given_timestamp != conn->flush_rtp_timestamp) && (modulo_32_offset(curframe->given_timestamp, conn->flush_rtp_timestamp) < conn->input_rate * 10)) { // if it's less than ten seconds - debug(2, "Dropping flushed packet in buffer_get_frame, seqno %u, timestamp %" PRIu32 + debug(3, "Dropping flushed packet in buffer_get_frame, seqno %u, timestamp %" PRIu32 ", flushing to " "timestamp: %" PRIu32 ".", curframe->sequence_number, curframe->given_timestamp, conn->flush_rtp_timestamp); @@ -874,7 +874,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { conn->input_rate / 5) && (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) < conn->input_rate * 10)) { - debug(2, "Dropping flush request in buffer_get_frame"); + debug(3, "Dropping flush request in buffer_get_frame"); conn->flush_rtp_timestamp = 0; } } diff --git a/rtp.c b/rtp.c index 92837e58..4b71c0ee 100644 --- a/rtp.c +++ b/rtp.c @@ -129,11 +129,8 @@ void *rtp_audio_receiver(void *arg) { ssize_t nread; while (1) { nread = recv(conn->audio_socket, packet, sizeof(packet), 0); - + frame_count++; - if (frame_count<10) - debug(1,"Recv'ed %d bytes. First two bytes are %02X,%02X ",nread,packet[0],packet[1]); - else { uint64_t local_time_now_fp = get_absolute_time_in_fp(); if (time_of_previous_packet_fp) { @@ -200,8 +197,8 @@ void *rtp_audio_receiver(void *arg) { uint32_t actual_timestamp = ntohl(*(uint32_t *)(pktp + 4)); - uint32_t ssid = ntohl(*(uint32_t *)(pktp + 8)); - debug(1, "Audio packet SSID: %08X,%u", ssid,ssid); + // uint32_t ssid = ntohl(*(uint32_t *)(pktp + 8)); + // debug(1, "Audio packet SSID: %08X,%u", ssid,ssid); // if (packet[1]&0x10) // debug(1,"Audio packet Extension bit set."); @@ -229,7 +226,6 @@ void *rtp_audio_receiver(void *arg) { } else { debug(1, "Error receiving an audio packet."); } - } } /* @@ -605,10 +601,10 @@ void *rtp_timing_receiver(void *arg) { (drand48() > config.diagnostic_drop_packet_fraction)) { arrival_time = get_absolute_time_in_fp(); - ssize_t plen = nread; + // ssize_t plen = nread; // debug(1,"Packet Received on Timing Port."); if (packet[1] == 0xd3) { // timing reply - + /* char obf[4096]; char *obfp = obf; int obfc; @@ -618,7 +614,7 @@ void *rtp_timing_receiver(void *arg) { }; *obfp=0; debug(1,"Timing Packet Received: \"%s\"",obf); - + */ // arrival_time = ((uint64_t)att.tv_sec<<32)+((uint64_t)att.tv_nsec<<32)/1000000000; // departure_time = ((uint64_t)dtt.tv_sec<<32)+((uint64_t)dtt.tv_nsec<<32)/1000000000; @@ -855,14 +851,17 @@ void *rtp_timing_receiver(void *arg) { static uint16_t bind_port(int ip_family, const char *self_ip_address, uint32_t scope_id, int *sock) { // look for a port in the range, if any was specified. - uint16_t desired_port = config.udp_port_base; int ret = 0; int local_socket = socket(ip_family, SOCK_DGRAM, IPPROTO_UDP); if (local_socket == -1) die("Could not allocate a socket."); SOCKADDR myaddr; + int tryCount = 0; + uint16_t desired_port; do { + tryCount++; + desired_port = nextFreeUDPPort(); memset(&myaddr, 0, sizeof(myaddr)); if (ip_family == AF_INET) { struct sockaddr_in *sa = (struct sockaddr_in *)&myaddr; @@ -883,13 +882,13 @@ static uint16_t bind_port(int ip_family, const char *self_ip_address, uint32_t s #endif } while ((ret < 0) && (errno == EADDRINUSE) && (desired_port != 0) && - (++desired_port < config.udp_port_base + config.udp_port_range)); + (tryCount < config.udp_port_range)); // debug(1,"UDP port chosen: %d.",desired_port); if (ret < 0) { close(local_socket); - die("error: could not bind a UDP port! Check the udp_port_range is large enough (>= 10) or " + die("error: could not bind a UDP port! Check the udp_port_range is large enough -- it must be at least 3, and 10 or more is suggested -- or " "check for restrictive firewall settings or a bad router!"); } diff --git a/rtsp.c b/rtsp.c index db6e99f4..50bda29a 100644 --- a/rtsp.c +++ b/rtsp.c @@ -459,7 +459,7 @@ int msg_handle_line(rtsp_message **pmsg, char *line) { char *sp, *p; sp = NULL; // this is to quieten a compiler warning - debug(1, "received request: %s", line); + debug(3, "RTSP Message Received: \"%s\".", line); p = strtok_r(line, " ", &sp); if (!p) @@ -1525,7 +1525,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag int have_the_player = 0; - int should_wait = 0; + int should_wait = 0; // this will be true if you're trying to break in to the current session // try to become the current playing_conn @@ -1568,7 +1568,6 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag if (have_the_player == 1) { debug(1, "Connection %d: ANNOUNCE got the player", conn->connection_number); - usleep(2000000); // this is just to let everything settle a bit } else { debug(1, "Connection %d: ANNOUNCE failed to get the player", conn->connection_number); } @@ -1577,17 +1576,25 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag if (have_the_player) { debug(3, "RTSP conversation thread %d has acquired play lock.", conn->connection_number); + // now, if this new session did not break in, then it's okay to reset the next UDP ports + // to the start of the range + + if (should_wait == 0) { // will be zero if it didn't need to wait to break in + resetFreeUDPPort(); + } + + /* { char *cp = req->content; int cp_left = req->contentlength; while (cp_left > 1) { if (strlen(cp) != 0) - warn(" %s", cp); + debug(1,">>>>>> %s", cp); cp += strlen(cp) + 1; cp_left -= strlen(cp) + 1; } } - +*/ resp->respcode = 456; // 456 - Header Field Not Valid for Resource @@ -1637,7 +1644,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag if (pssid) { uint32_t ssid = uatoi(pssid); - debug(1, "Synchronisation Source Identifier: %08X,%u", ssid,ssid); + debug(3, "Synchronisation Source Identifier: %08X,%u", ssid,ssid); } if (pminlatency) { @@ -1992,7 +1999,6 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { // debug(1, "Connection %d: closing fd %d.", // conn->connection_number,conn->fd); close(conn->fd); - usleep(1000000); } if (conn->auth_nonce) { free(conn->auth_nonce); @@ -2019,7 +2025,7 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { debug_mutex_lock(&playing_conn_lock, 1000000 , 3); //get it if (playing_conn == conn) { - debug(1, "Connection %d: Unlocking play lock.", conn->connection_number); + debug(3, "Connection %d: Unlocking play lock.", conn->connection_number); playing_conn = NULL; } debug_mutex_unlock(&playing_conn_lock, 3); diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 42c085b0..55ac4cbf 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -18,7 +18,7 @@ general = // mdns_backend = "avahi"; // Run "shairport-sync -h" to get a list of all mdns_backends. The default is the first one. // port = 5000; // Listen for service requests on this port // udp_port_base = 6001; // start allocating UDP ports from this port number when needed -// udp_port_range = 100; // look for free ports in this number of places, starting at the UDP port base. Allow at least 10, though only three are needed in a steady state. +// udp_port_range = 10; // look for free ports in this number of places, starting at the UDP port base. Allow at least 10, though only three are needed in a steady state. // drift_tolerance_in_seconds = 0.002; // allow a timing error of this number of seconds of drift away from exact synchronisation before attempting to correct it // resync_threshold_in_seconds = 0.050; // a synchronisation error greater than this number of seconds will cause resynchronisation; 0 disables it // ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. diff --git a/shairport.c b/shairport.c index 5e36e911..506f9feb 100644 --- a/shairport.c +++ b/shairport.c @@ -419,8 +419,8 @@ int parse_options(int argc, char **argv) { /* Get the udp port range setting. This is number of ports that will be tried for free ports , * starting at the port base. Only three ports are needed. */ if (config_lookup_int(config.cfg, "general.udp_port_range", &value)) { - if ((value < 0) || (value > 65535)) - die("Invalid port range \"%sd\". It should be between 0 and 65535, default is 100", + if ((value < 3) || (value > 65535)) + die("Invalid port range \"%sd\". It should be between 3 and 65535, default is 10", value); else config.udp_port_range = value; @@ -1234,7 +1234,7 @@ int main(int argc, char **argv) { 1); // we expect to be able to connect to the output device config.audio_backend_buffer_desired_length = 6615; // 0.15 seconds. config.udp_port_base = 6001; - config.udp_port_range = 100; + config.udp_port_range = 10; config.output_format = SPS_FORMAT_S16; // default config.output_rate = 44100; // default config.decoders_supported = From 4c5790de28b074376c628fd0acc70fd6bb38efe7 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 23 Nov 2018 13:39:23 +0000 Subject: [PATCH 221/569] Fix off-by-one error in UDP range counting --- common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.c b/common.c index f735dba4..9b1a5538 100644 --- a/common.c +++ b/common.c @@ -104,7 +104,7 @@ uint16_t nextFreeUDPPort() { if (UDPPortIndex == 0) UDPPortIndex = config.udp_port_base; - else if (UDPPortIndex == (config.udp_port_base + config.udp_port_range)) + else if (UDPPortIndex == (config.udp_port_base + config.udp_port_range - 1)) UDPPortIndex = config.udp_port_base; else UDPPortIndex++; From 1e5219faa9a11c3c39c3fd140770d74bbfac0a12 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 23 Nov 2018 13:40:42 +0000 Subject: [PATCH 222/569] Fix bugs in interrupting an existing session, and use fresh ports if necessary. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index efc335ce..786ed9c4 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.3d28], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d29], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From ae1453f8aa5e7567c2df549c6817a094204903da Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 23 Nov 2018 13:57:34 +0000 Subject: [PATCH 223/569] Stop going to verbosity level 3 on occasion. It's not so useful now. --- player.c | 8 ++++---- rtsp.c | 6 ++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/player.c b/player.c index f1837573..859e3d7f 100644 --- a/player.c +++ b/player.c @@ -2776,8 +2776,8 @@ int player_play(rtsp_conn_info *conn) { int player_stop(rtsp_conn_info *conn) { // note -- this may be called from another connection thread. - int dl = debuglev; - debuglev = 3; + //int dl = debuglev; + // debuglev = 3; debug(3, "player_stop"); if (conn->player_thread) { debug(2, "player_thread cancel..."); @@ -2791,12 +2791,12 @@ int player_stop(rtsp_conn_info *conn) { debug(2, "pend"); send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points #endif - debuglev = dl; + // debuglev = dl; command_stop(); return 0; } else { debug(3, "Connection %d: player thread already deleted.", conn->connection_number); - debuglev = dl; + // debuglev = dl; return -1; } } diff --git a/rtsp.c b/rtsp.c index 50bda29a..b4d9d4df 100644 --- a/rtsp.c +++ b/rtsp.c @@ -2140,8 +2140,6 @@ static void *rtsp_conversation_thread_func(void *pconn) { int err = msg_write_response(conn->fd, resp); if (err) { debug(1, "Connection %d: Unable to write an RTSP message response. Terminating the connection.", conn->connection_number); - if (debuglev != 0) - debuglev = 3; // see what happens next conn->stop = 1; } } @@ -2157,8 +2155,8 @@ static void *rtsp_conversation_thread_func(void *pconn) { rtsp_read_request_attempt_count--; if (rtsp_read_request_attempt_count == 0) { tstop = 1; - if ((reply == rtsp_read_request_response_read_error) && (debuglev != 0)) - debuglev = 3; // see what happens next + // if ((reply == rtsp_read_request_response_read_error) && (debuglev != 0)) + // debuglev = 3; // see what happens next } else { if (reply == rtsp_read_request_response_channel_closed) debug(2, "Connection %d: RTSP channel unexpectedly closed -- will try again %d time(s).", From d93aeec50aaf3ed79ac2e10bfdda6a171c3a2236 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 23 Nov 2018 15:11:04 +0000 Subject: [PATCH 224/569] Quieten a few debug messages. --- player.c | 8 ++++---- rtp.c | 12 ++++++------ rtsp.c | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/player.c b/player.c index 859e3d7f..dcd2a93e 100644 --- a/player.c +++ b/player.c @@ -954,7 +954,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { uint64_t lateness = local_time_now - conn->first_packet_time_to_play; lateness = (lateness * 1000000) >> 32; // microseconds debug( - 1, + 3, "First packet is %" PRIu64 " microseconds late! Flushing 0.5 seconds",lateness); do_flush(conn->first_packet_timestamp + 5 * 4410, conn); @@ -988,7 +988,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (local_time_now > conn->first_packet_time_to_play) { uint64_t lateness = local_time_now - conn->first_packet_time_to_play; lateness = (lateness * 1000000) >> 32; // microseconds - debug(1,"Gone past starting time by %" PRIu64 " microseconds.", lateness); + debug(3,"Gone past starting time by %" PRIu64 " microseconds.", lateness); have_sent_prefiller_silence = 1; conn->ab_buffering = 0; @@ -1642,11 +1642,11 @@ void *player_thread_func(void *arg) { debug(3, "Output bit depth is %d.", output_bit_depth); if (conn->input_bit_depth > output_bit_depth) { - debug(1, "Dithering will be enabled because the input bit depth is greater than the output bit " + debug(3, "Dithering will be enabled because the input bit depth is greater than the output bit " "depth"); } if (conn->fix_volume != 0x10000) { - debug(1, "Dithering will be enabled because the output volume is being altered in software"); + debug(3, "Dithering will be enabled because the output volume is being altered in software"); } // we need an intermediate "transition" buffer diff --git a/rtp.c b/rtp.c index 4b71c0ee..4e5a98d6 100644 --- a/rtp.c +++ b/rtp.c @@ -100,9 +100,9 @@ uint64_t local_to_remote_time_difference_now(rtsp_conn_info *conn) { void rtp_audio_receiver_cleanup_handler(void *arg) { debug(3, "Audio Receiver Cleanup."); rtsp_conn_info *conn = (rtsp_conn_info *)arg; - debug(1, "shutdown audio socket."); + debug(3, "shutdown audio socket."); shutdown(conn->audio_socket, SHUT_RDWR); - debug(1, "close audio socket."); + debug(3, "close audio socket."); close(conn->audio_socket); debug(3, "Audio Receiver Cleanup Successful."); @@ -242,9 +242,9 @@ void *rtp_audio_receiver(void *arg) { void rtp_control_handler_cleanup_handler(void *arg) { debug(3, "Control Receiver Cleanup."); rtsp_conn_info *conn = (rtsp_conn_info *)arg; - debug(1, "shutdown control socket."); + debug(3, "shutdown control socket."); shutdown(conn->control_socket, SHUT_RDWR); - debug(1, "close control socket."); + debug(3, "close control socket."); close(conn->control_socket); debug(3, "Control Receiver Cleanup Successful."); } @@ -561,9 +561,9 @@ void rtp_timing_receiver_cleanup_handler(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; pthread_cancel(conn->timer_requester); pthread_join(conn->timer_requester, NULL); - debug(1, "shutdown timing socket."); + debug(3, "shutdown timing socket."); shutdown(conn->timing_socket, SHUT_RDWR); - debug(1, "close timing socket."); + debug(3, "close timing socket."); close(conn->timing_socket); debug(3, "Timing Receiver Cleanup Successful."); } diff --git a/rtsp.c b/rtsp.c index b4d9d4df..6d783c73 100644 --- a/rtsp.c +++ b/rtsp.c @@ -543,7 +543,7 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes continue; char errorstring[1024]; strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(1, "rtsp_read_request_response_read_error %d: \"%s\".", errno, (char *)errorstring); + debug(1, "Connection %d: rtsp_read_request_response_read_error %d: \"%s\".", conn->connection_number, errno, (char *)errorstring); reply = rtsp_read_request_response_read_error; goto shutdown; } @@ -2106,7 +2106,7 @@ static void *rtsp_conversation_thread_func(void *pconn) { } } if (method_selected == 0) { - debug(1, "Connection %d: Unrecognised and unhandled rtsp request \"%s\".", + debug(3, "Connection %d: Unrecognised and unhandled rtsp request \"%s\".", conn->connection_number, req->method); int y = req->contentlength; @@ -2123,7 +2123,7 @@ static void *rtsp_conversation_thread_func(void *pconn) { obfp += 2; }; *obfp = 0; - debug(1, "Content: \"%s\".", obf); + debug(3, "Content: \"%s\".", obf); } } } From dcd8fbc1eca09eb0210ebaa643025eca600034b2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 23 Nov 2018 16:10:18 +0000 Subject: [PATCH 225/569] Remove some unnecessary test code from the debug messaging. --- common.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/common.c b/common.c index 9b1a5538..bf062edd 100644 --- a/common.c +++ b/common.c @@ -100,8 +100,7 @@ void resetFreeUDPPort() { UDPPortIndex = 0; } -uint16_t nextFreeUDPPort() { - +uint16_t nextFreeUDPPort() { if (UDPPortIndex == 0) UDPPortIndex = config.udp_port_base; else if (UDPPortIndex == (config.udp_port_base + config.udp_port_range - 1)) @@ -190,10 +189,6 @@ void debug(int level, const char *format, ...) { va_start(args, format); vsnprintf(s, sizeof(s), format, args); va_end(args); - int currentState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, ¤tState); - if (currentState == PTHREAD_CANCEL_ENABLE) - daemon_log(LOG_DEBUG, "Warning -- cancellation is enabled before logging"); if ((config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) daemon_log(LOG_DEBUG, "|% 20.9f|% 20.9f|%s", tss, tsl, s); else if (config.debugger_show_relative_time) @@ -202,9 +197,6 @@ void debug(int level, const char *format, ...) { daemon_log(LOG_DEBUG, "% 20.9f|%s", tss, s); else daemon_log(LOG_DEBUG, "%s", s); - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, ¤tState); - if (currentState == PTHREAD_CANCEL_ENABLE) - daemon_log(LOG_DEBUG, "Warning -- cancellation is enabled after logging"); pthread_setcancelstate(oldState, NULL); } From a932298d3a6deee479bf5e90d691843763f63fa9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 24 Nov 2018 11:06:20 +0000 Subject: [PATCH 226/569] Small correction to port selection logic. If a new sesion waits for an existing session to close that it hasn't interrupted, reset the ports base. Quieten some debug messages. --- rtsp.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/rtsp.c b/rtsp.c index 6d783c73..cd46598e 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1526,6 +1526,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag int have_the_player = 0; int should_wait = 0; // this will be true if you're trying to break in to the current session + int interrupting_current_session = 0; // try to become the current playing_conn @@ -1538,14 +1539,15 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag have_the_player = 1; warn("Duplicate ANNOUNCE, by the look of it!"); } else if (playing_conn->stop) { - debug(1, "Connection %d: ANNOUNCE: already shutting down; waiting for it...",playing_conn->connection_number); + debug(2, "Connection %d: ANNOUNCE: already shutting down; waiting for it...",playing_conn->connection_number); should_wait = 1; } else if (config.allow_session_interruption == 1) { - debug(1, "Connection %d: ANNOUNCE: asking playing connection %d to shut down.", + debug(2, "Connection %d: ANNOUNCE: asking playing connection %d to shut down.", conn->connection_number, playing_conn->connection_number); playing_conn->stop = 1; - pthread_cancel(playing_conn->thread); // asking the RTSP thread to exit + interrupting_current_session = 1; should_wait = 1; + pthread_cancel(playing_conn->thread); // asking the RTSP thread to exit } debug_mutex_unlock(&playing_conn_lock, 3); @@ -1566,10 +1568,10 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag } } - if (have_the_player == 1) { - debug(1, "Connection %d: ANNOUNCE got the player", conn->connection_number); + if ((have_the_player == 1) && (interrupting_current_session == 1)) { + debug(2, "Connection %d: ANNOUNCE got the player", conn->connection_number); } else { - debug(1, "Connection %d: ANNOUNCE failed to get the player", conn->connection_number); + debug(2, "Connection %d: ANNOUNCE failed to get the player", conn->connection_number); } } @@ -1579,7 +1581,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag // now, if this new session did not break in, then it's okay to reset the next UDP ports // to the start of the range - if (should_wait == 0) { // will be zero if it didn't need to wait to break in + if (interrupting_current_session == 0) { // will be zero if it wasn't waiting to break in resetFreeUDPPort(); } From 998e7cea2cbededebca3d1524e25b7b6a4d06cf8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 24 Nov 2018 11:16:57 +0000 Subject: [PATCH 227/569] clang-format --- common.c | 20 +++++++-------- player.c | 44 ++++++++++++++++----------------- rtp.c | 25 ++++++++++--------- rtsp.c | 74 +++++++++++++++++++++++++++++--------------------------- 4 files changed, 84 insertions(+), 79 deletions(-) diff --git a/common.c b/common.c index bf062edd..0d76a4f4 100644 --- a/common.c +++ b/common.c @@ -96,18 +96,16 @@ sigset_t pselect_sigset; static uint16_t UDPPortIndex = 0; -void resetFreeUDPPort() { - UDPPortIndex = 0; -} +void resetFreeUDPPort() { UDPPortIndex = 0; } -uint16_t nextFreeUDPPort() { - if (UDPPortIndex == 0) - UDPPortIndex = config.udp_port_base; - else if (UDPPortIndex == (config.udp_port_base + config.udp_port_range - 1)) - UDPPortIndex = config.udp_port_base; - else - UDPPortIndex++; - return UDPPortIndex; +uint16_t nextFreeUDPPort() { + if (UDPPortIndex == 0) + UDPPortIndex = config.udp_port_base; + else if (UDPPortIndex == (config.udp_port_base + config.udp_port_range - 1)) + UDPPortIndex = config.udp_port_base; + else + UDPPortIndex++; + return UDPPortIndex; } int get_requested_connection_state_to_output() { return requested_connection_state_to_output; } diff --git a/player.c b/player.c index dcd2a93e..8211c8dc 100644 --- a/player.c +++ b/player.c @@ -464,15 +464,16 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in conn->initial_reference_time = 0; conn->initial_reference_timestamp = 0; } else { - /* - if ((conn->flush_rtp_timestamp != 0) && - (modulo_32_offset(conn->flush_rtp_timestamp, actual_timestamp) > conn->input_rate / 5) && - (modulo_32_offset(conn->flush_rtp_timestamp, actual_timestamp) < conn->input_rate)) { - // between 0.2 and 1 second - debug(2, "Dropping flush request in player_put_packet"); - conn->flush_rtp_timestamp = 0; - } - */ + /* + if ((conn->flush_rtp_timestamp != 0) && + (modulo_32_offset(conn->flush_rtp_timestamp, actual_timestamp) > conn->input_rate / 5) + && + (modulo_32_offset(conn->flush_rtp_timestamp, actual_timestamp) < conn->input_rate)) { + // between 0.2 and 1 second + debug(2, "Dropping flush request in player_put_packet"); + conn->flush_rtp_timestamp = 0; + } + */ abuf_t *abuf = 0; @@ -851,10 +852,11 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { debug(1, "Inconsistent sequence numbers detected"); } } - - //if (conn->flush_rtp_timestamp != 0) - // debug(2,"flush_rtp_timestamp is %" PRIx32 " and curframe->given_timestamp is %" PRIx32 ".", conn->flush_rtp_timestamp , curframe->given_timestamp); - + + // if (conn->flush_rtp_timestamp != 0) + // debug(2,"flush_rtp_timestamp is %" PRIx32 " and curframe->given_timestamp is %" PRIx32 + // ".", conn->flush_rtp_timestamp , curframe->given_timestamp); + if ((conn->flush_rtp_timestamp != 0) && (curframe->given_timestamp != conn->flush_rtp_timestamp) && (modulo_32_offset(curframe->given_timestamp, conn->flush_rtp_timestamp) < @@ -953,11 +955,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (local_time_now > conn->first_packet_time_to_play) { uint64_t lateness = local_time_now - conn->first_packet_time_to_play; lateness = (lateness * 1000000) >> 32; // microseconds - debug( - 3, - "First packet is %" PRIu64 " microseconds late! Flushing 0.5 seconds",lateness); - do_flush(conn->first_packet_timestamp + 5 * 4410, - conn); + debug(3, "First packet is %" PRIu64 " microseconds late! Flushing 0.5 seconds", + lateness); + do_flush(conn->first_packet_timestamp + 5 * 4410, conn); } } } @@ -988,7 +988,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (local_time_now > conn->first_packet_time_to_play) { uint64_t lateness = local_time_now - conn->first_packet_time_to_play; lateness = (lateness * 1000000) >> 32; // microseconds - debug(3,"Gone past starting time by %" PRIu64 " microseconds.", lateness); + debug(3, "Gone past starting time by %" PRIu64 " microseconds.", lateness); have_sent_prefiller_silence = 1; conn->ab_buffering = 0; @@ -2476,8 +2476,8 @@ void *player_thread_func(void *arg) { debug(1, "This should never be called."); pthread_cleanup_pop(1); // pop the cleanup handler -// debug(1, "This should never be called either."); -// pthread_cleanup_pop(1); // pop the initial cleanup handler + // debug(1, "This should never be called either."); + // pthread_cleanup_pop(1); // pop the initial cleanup handler pthread_exit(NULL); } @@ -2776,7 +2776,7 @@ int player_play(rtsp_conn_info *conn) { int player_stop(rtsp_conn_info *conn) { // note -- this may be called from another connection thread. - //int dl = debuglev; + // int dl = debuglev; // debuglev = 3; debug(3, "player_stop"); if (conn->player_thread) { diff --git a/rtp.c b/rtp.c index 4e5a98d6..0c97fc31 100644 --- a/rtp.c +++ b/rtp.c @@ -104,7 +104,7 @@ void rtp_audio_receiver_cleanup_handler(void *arg) { shutdown(conn->audio_socket, SHUT_RDWR); debug(3, "close audio socket."); close(conn->audio_socket); - + debug(3, "Audio Receiver Cleanup Successful."); } @@ -170,8 +170,8 @@ void *rtp_audio_receiver(void *arg) { // increment last_seqno and see if it's the same as the incoming seqno if (type == 0x60) { // regular audio data - - /* + + /* char obf[4096]; char *obfp = obf; int obfc; @@ -181,8 +181,8 @@ void *rtp_audio_receiver(void *arg) { }; *obfp=0; debug(1,"Audio Packet Received: \"%s\"",obf); - */ - + */ + if (last_seqno == -1) last_seqno = seqno; else { @@ -196,7 +196,7 @@ void *rtp_audio_receiver(void *arg) { } uint32_t actual_timestamp = ntohl(*(uint32_t *)(pktp + 4)); - + // uint32_t ssid = ntohl(*(uint32_t *)(pktp + 8)); // debug(1, "Audio packet SSID: %08X,%u", ssid,ssid); @@ -308,8 +308,10 @@ void *rtp_control_receiver(void *arg) { 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 " respectively.",monotonic_timestamp(rt, conn),monotonic_timestamp(rtlt, conn)); + 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 " + respectively.",monotonic_timestamp(rt, conn),monotonic_timestamp(rtlt, conn)); } */ if (conn->local_to_remote_time_difference) { // need a time packet to be interchanged @@ -860,8 +862,8 @@ static uint16_t bind_port(int ip_family, const char *self_ip_address, uint32_t s int tryCount = 0; uint16_t desired_port; do { - tryCount++; - desired_port = nextFreeUDPPort(); + tryCount++; + desired_port = nextFreeUDPPort(); memset(&myaddr, 0, sizeof(myaddr)); if (ip_family == AF_INET) { struct sockaddr_in *sa = (struct sockaddr_in *)&myaddr; @@ -888,7 +890,8 @@ static uint16_t bind_port(int ip_family, const char *self_ip_address, uint32_t s if (ret < 0) { close(local_socket); - die("error: could not bind a UDP port! Check the udp_port_range is large enough -- it must be at least 3, and 10 or more is suggested -- or " + die("error: could not bind a UDP port! Check the udp_port_range is large enough -- it must be " + "at least 3, and 10 or more is suggested -- or " "check for restrictive firewall settings or a bad router!"); } diff --git a/rtsp.c b/rtsp.c index cd46598e..99000174 100644 --- a/rtsp.c +++ b/rtsp.c @@ -543,7 +543,8 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes continue; char errorstring[1024]; strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(1, "Connection %d: rtsp_read_request_response_read_error %d: \"%s\".", conn->connection_number, errno, (char *)errorstring); + debug(1, "Connection %d: rtsp_read_request_response_read_error %d: \"%s\".", + conn->connection_number, errno, (char *)errorstring); reply = rtsp_read_request_response_read_error; goto shutdown; } @@ -896,7 +897,7 @@ void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { error: warn("Connection %d: SETUP -- Error in setup request -- unlocking play lock.", conn->connection_number); - debug_mutex_lock(&playing_conn_lock, 1000000 , 3); + debug_mutex_lock(&playing_conn_lock, 1000000, 3); playing_conn = NULL; // this connection definitely doesn't have the play lock debug_mutex_unlock(&playing_conn_lock, 3); resp->respcode = 451; // invalid arguments @@ -1522,40 +1523,40 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_m static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(2, "Connection %d: ANNOUNCE", conn->connection_number); - - + int have_the_player = 0; int should_wait = 0; // this will be true if you're trying to break in to the current session int interrupting_current_session = 0; - + // try to become the current playing_conn - - debug_mutex_lock(&playing_conn_lock, 1000000 , 3); //get it - + + debug_mutex_lock(&playing_conn_lock, 1000000, 3); // get it + if (playing_conn == NULL) { playing_conn = conn; have_the_player = 1; } else if (playing_conn == conn) { - have_the_player = 1; - warn("Duplicate ANNOUNCE, by the look of it!"); + have_the_player = 1; + warn("Duplicate ANNOUNCE, by the look of it!"); } else if (playing_conn->stop) { - debug(2, "Connection %d: ANNOUNCE: already shutting down; waiting for it...",playing_conn->connection_number); - should_wait = 1; + debug(2, "Connection %d: ANNOUNCE: already shutting down; waiting for it...", + playing_conn->connection_number); + should_wait = 1; } else if (config.allow_session_interruption == 1) { debug(2, "Connection %d: ANNOUNCE: asking playing connection %d to shut down.", - conn->connection_number, playing_conn->connection_number); + conn->connection_number, playing_conn->connection_number); playing_conn->stop = 1; interrupting_current_session = 1; - should_wait = 1; + should_wait = 1; pthread_cancel(playing_conn->thread); // asking the RTSP thread to exit - } + } debug_mutex_unlock(&playing_conn_lock, 3); if (should_wait) { useconds_t time_remaining = 3000000; - + while ((time_remaining > 0) && (have_the_player == 0)) { - debug_mutex_lock(&playing_conn_lock, 1000000 , 3); //get it + debug_mutex_lock(&playing_conn_lock, 1000000, 3); // get it if (playing_conn == NULL) { playing_conn = conn; have_the_player = 1; @@ -1563,10 +1564,10 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag debug_mutex_unlock(&playing_conn_lock, 3); if (have_the_player == 0) { - usleep(100000); - time_remaining -= 100000; - } + usleep(100000); + time_remaining -= 100000; } + } if ((have_the_player == 1) && (interrupting_current_session == 1)) { debug(2, "Connection %d: ANNOUNCE got the player", conn->connection_number); @@ -1577,14 +1578,14 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag if (have_the_player) { debug(3, "RTSP conversation thread %d has acquired play lock.", conn->connection_number); - + // now, if this new session did not break in, then it's okay to reset the next UDP ports // to the start of the range - + if (interrupting_current_session == 0) { // will be zero if it wasn't waiting to break in - resetFreeUDPPort(); + resetFreeUDPPort(); } - + /* { char *cp = req->content; @@ -1597,8 +1598,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag } } */ - - + resp->respcode = 456; // 456 - Header Field Not Valid for Resource char *pssid = NULL; char *paesiv = NULL; @@ -1643,11 +1643,11 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag cp = next; } - + if (pssid) { uint32_t ssid = uatoi(pssid); - debug(3, "Synchronisation Source Identifier: %08X,%u", ssid,ssid); - } + debug(3, "Synchronisation Source Identifier: %08X,%u", ssid, ssid); + } if (pminlatency) { conn->minimum_latency = atoi(pminlatency); @@ -1751,7 +1751,7 @@ out: if (resp->respcode != 200 && resp->respcode != 453) { debug(1, "Connection %d: Error in handling ANNOUNCE. Unlocking the play lock.", conn->connection_number); - debug_mutex_lock(&playing_conn_lock, 1000000 , 3); //get it + debug_mutex_lock(&playing_conn_lock, 1000000, 3); // get it playing_conn = NULL; debug_mutex_unlock(&playing_conn_lock, 3); } @@ -2007,7 +2007,7 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { conn->auth_nonce = NULL; } rtp_terminate(conn); - + if (conn->dacp_id) { free(conn->dacp_id); conn->dacp_id = NULL; @@ -2025,7 +2025,7 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { if (rc) debug(1, "Connection %d: error %d destroying flush_mutex.", conn->connection_number, rc); - debug_mutex_lock(&playing_conn_lock, 1000000 , 3); //get it + debug_mutex_lock(&playing_conn_lock, 1000000, 3); // get it if (playing_conn == conn) { debug(3, "Connection %d: Unlocking play lock.", conn->connection_number); playing_conn = NULL; @@ -2141,7 +2141,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.", conn->connection_number); + debug(1, "Connection %d: Unable to write an RTSP message response. Terminating the " + "connection.", + conn->connection_number); conn->stop = 1; } } @@ -2161,7 +2163,8 @@ static void *rtsp_conversation_thread_func(void *pconn) { // debuglev = 3; // see what happens next } else { if (reply == rtsp_read_request_response_channel_closed) - debug(2, "Connection %d: RTSP channel unexpectedly closed -- will try again %d time(s).", + debug(2, + "Connection %d: RTSP channel unexpectedly closed -- will try again %d time(s).", conn->connection_number, rtsp_read_request_attempt_count); if (reply == rtsp_read_request_response_read_error) debug(2, "Connection %d: RTSP channel read error -- will try again %d time(s).", @@ -2172,7 +2175,8 @@ static void *rtsp_conversation_thread_func(void *pconn) { tstop = 1; } } else { - debug(1, "Connection %d: rtsp_read_request error %d, packet ignored.", conn->connection_number, (int)reply); + debug(1, "Connection %d: rtsp_read_request error %d, packet ignored.", + conn->connection_number, (int)reply); } if (tstop) { debug(3, "Connection %d: Terminate RTSP connection.", conn->connection_number); From 57ae8ad846c4a9b5713a269e1def2cb7382e598f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 24 Nov 2018 11:35:16 +0000 Subject: [PATCH 228/569] Change order of header files (again?) to avoid OpenBSD compilation errors. --- shairport.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shairport.c b/shairport.c index 506f9feb..c9212544 100644 --- a/shairport.c +++ b/shairport.c @@ -31,11 +31,11 @@ #include #include #include -#include #include #include #include #include +#include #include #include #include From 3b4654df7703c58d3d2fefa74c7543ed39851875 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 24 Nov 2018 22:28:52 +0000 Subject: [PATCH 229/569] Make some alsa operation sequences un-cancellable. Make some cleanup sequences un-cancellable. --- audio_alsa.c | 33 ++++++++++++++++++++++++++++++--- rtp.c | 23 +++++++++++++++++++---- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 5ca4836c..24132fe6 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -405,6 +405,8 @@ static int init(int argc, char **argv) { debug(1, "alsa output device name is \"%s\".", alsa_out_dev); if (hardware_mixer) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable if (alsa_mix_dev == NULL) alsa_mix_dev = alsa_out_dev; @@ -491,6 +493,7 @@ static int init(int argc, char **argv) { } debug_mutex_unlock(&alsa_mutex, 3); // release the mutex pthread_cleanup_pop(0); + pthread_setcancelstate(oldState, NULL); } else { // debug(1, "Has no mixer and thus no hardware mute."); } @@ -504,7 +507,7 @@ static void deinit(void) { stop(); } -int open_alsa_device(void) { +int actual_open_alsa_device(void) { // the alsa mutex is already acquired when this is called const snd_pcm_uframes_t minimal_buffer_headroom = 352 * 2; // we accept this much headroom in the hardware buffer, but we'll @@ -851,6 +854,15 @@ int open_alsa_device(void) { return 0; } +int open_alsa_device(void) { + int result; + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable + result = actual_open_alsa_device(); + pthread_setcancelstate(oldState, NULL); + return result; +} + static void start(int i_sample_rate, int i_sample_format) { // debug(2,"audio_alsa start called."); if (i_sample_rate == 0) @@ -874,6 +886,8 @@ int delay(long *the_delay) { if (alsa_handle == NULL) { return -ENODEV; } else { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); int derr; snd_pcm_state_t dac_state = snd_pcm_state(alsa_handle); @@ -926,6 +940,7 @@ int delay(long *the_delay) { // here, occasionally pretend there's a problem with pcm_get_delay() // if ((random() % 100000) < 3) // keep it pretty rare // reply = -EIO; // pretend something bad has happened + pthread_setcancelstate(oldState, NULL); return reply; } } @@ -945,10 +960,13 @@ int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) { static int play(void *buf, int samples) { // debug(3,"audio_alsa play called."); + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable int ret = 0; if (alsa_handle == NULL) { + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); - ret = open_alsa_device(); + ret = actual_open_alsa_device(); if (ret == 0) { if (audio_alsa.volume) do_volume(set_volume); @@ -1047,13 +1065,14 @@ static int play(void *buf, int samples) { debug_mutex_unlock(&alsa_mutex, 3); pthread_cleanup_pop(0); // release the mutex } + pthread_setcancelstate(oldState, NULL); return ret; } static void flush(void) { // debug(2,"audio_alsa flush called."); int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); int derr; do_mute(1); @@ -1094,6 +1113,8 @@ static void parameters(audio_parameters *info) { } void do_volume(double vol) { // caller is assumed to have the alsa_mutex when using this function + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable debug(3, "Setting volume db to %f.", vol); set_volume = vol; if (volume_set_request && (open_mixer() == 1)) { @@ -1125,6 +1146,7 @@ void do_volume(double vol) { // caller is assumed to have the alsa_mutex when us volume_set_request = 0; // any external request that has been made is now satisfied close_mixer(); } + pthread_setcancelstate(oldState, NULL); } void volume(double vol) { @@ -1165,6 +1187,9 @@ static void mute(int mute_state_requested) { void do_mute(int mute_state_requested) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable + // if a mute is requested now, then // if an external mute request is in place, leave everything muted // otherwise, if an external mute request is pending, action it @@ -1205,4 +1230,6 @@ void do_mute(int mute_state_requested) { } } mute_request_pending = 0; + + pthread_setcancelstate(oldState, NULL); } diff --git a/rtp.c b/rtp.c index 0c97fc31..afee1a4b 100644 --- a/rtp.c +++ b/rtp.c @@ -98,14 +98,16 @@ uint64_t local_to_remote_time_difference_now(rtsp_conn_info *conn) { } void rtp_audio_receiver_cleanup_handler(void *arg) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); debug(3, "Audio Receiver Cleanup."); rtsp_conn_info *conn = (rtsp_conn_info *)arg; debug(3, "shutdown audio socket."); shutdown(conn->audio_socket, SHUT_RDWR); debug(3, "close audio socket."); close(conn->audio_socket); - - debug(3, "Audio Receiver Cleanup Successful."); + debug(3, "Connection %d: Audio Receiver Cleanup.", conn->connection_number); + pthread_setcancelstate(oldState, NULL); } void *rtp_audio_receiver(void *arg) { @@ -240,13 +242,16 @@ void *rtp_audio_receiver(void *arg) { } void rtp_control_handler_cleanup_handler(void *arg) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); debug(3, "Control Receiver Cleanup."); rtsp_conn_info *conn = (rtsp_conn_info *)arg; debug(3, "shutdown control socket."); shutdown(conn->control_socket, SHUT_RDWR); debug(3, "close control socket."); close(conn->control_socket); - debug(3, "Control Receiver Cleanup Successful."); + debug(3, "Connection %d: Control Receiver Cleanup.", conn->connection_number); + pthread_setcancelstate(oldState, NULL); } void *rtp_control_receiver(void *arg) { @@ -495,7 +500,13 @@ void *rtp_control_receiver(void *arg) { pthread_exit(NULL); } +void rtp_timing_sender_cleanup_handler(void *arg) { + rtsp_conn_info *conn = (rtsp_conn_info *)arg; + debug(3, "Connection %d: Timing Sender Cleanup.", conn->connection_number); +} + void *rtp_timing_sender(void *arg) { + pthread_cleanup_push(rtp_timing_sender_cleanup_handler, arg); rtsp_conn_info *conn = (rtsp_conn_info *)arg; struct timing_request { char leader; @@ -555,10 +566,13 @@ void *rtp_timing_sender(void *arg) { usleep(3000000); } debug(3, "rtp_timing_sender thread interrupted. This should never happen."); + pthread_cleanup_pop(0); // don't execute anything here. pthread_exit(NULL); } void rtp_timing_receiver_cleanup_handler(void *arg) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); debug(3, "Timing Receiver Cleanup."); rtsp_conn_info *conn = (rtsp_conn_info *)arg; pthread_cancel(conn->timer_requester); @@ -567,7 +581,8 @@ void rtp_timing_receiver_cleanup_handler(void *arg) { shutdown(conn->timing_socket, SHUT_RDWR); debug(3, "close timing socket."); close(conn->timing_socket); - debug(3, "Timing Receiver Cleanup Successful."); + debug(3, "Connection %d: Timing Receiver Cleanup.", conn->connection_number); + pthread_setcancelstate(oldState, NULL); } void *rtp_timing_receiver(void *arg) { From 76ee6d5e4385cba38a5e10629a0113c00bc31635 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 26 Nov 2018 19:33:22 +0000 Subject: [PATCH 230/569] Add new watchdog thread for the player -- should be more reliable than the existing code. --- player.c | 72 +++++++++++++++++++++++++++++++++++++++++--------------- player.h | 5 +++- 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/player.c b/player.c index 8211c8dc..fe8a1447 100644 --- a/player.c +++ b/player.c @@ -114,6 +114,7 @@ uint64_t modulo_64_offset(uint64_t from, uint64_t to) { } void do_flush(uint32_t timestamp, rtsp_conn_info *conn); +void *player_watchdog_thread_code(void *arg); static void ab_resync(rtsp_conn_info *conn) { int i; @@ -781,25 +782,6 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { local_time_now = get_absolute_time_in_fp(); // type okay debug(3, "buffer_get_frame is iterating"); - // if config.timeout (default 120) seconds have elapsed since the last audio packet was - // received, then we should stop. - // config.timeout of zero means don't check..., but iTunes may be confused by a long gap - // followed by a resumption... - - if ((conn->time_of_last_audio_packet != 0) && (conn->stop == 0) && - (config.dont_check_timeout == 0)) { - uint64_t ct = config.timeout; // go from int to 64-bit int - // if (conn->packet_count>500) { //for testing -- about 4 seconds of play first - if ((local_time_now > conn->time_of_last_audio_packet) && - (local_time_now - conn->time_of_last_audio_packet >= ct << 32)) { - debug(1, "As Yeats almost said, \"Too long a silence / can make a stone of the heart\" " - "from RTSP conversation %d.", - conn->connection_number); - conn->stop = 1; - pthread_cancel(conn->thread); - // pthread_kill(conn->thread, SIGUSR1); - } - } int rco = get_requested_connection_state_to_output(); if (conn->connection_state_to_output != rco) { @@ -1449,6 +1431,12 @@ void player_thread_cleanup_handler(void *arg) { #endif debug(2, "Cancelling timing, control and audio threads..."); + debug(2, "Cancel watchdog thread."); + pthread_cancel(conn->player_watchdog_thread); + debug(2, "Join watchdog thread."); + pthread_join(conn->player_watchdog_thread, NULL); + debug(2, "Delete watchdog mutex."); + pthread_mutex_destroy(&conn->watchdog_mutex); debug(2, "Cancel timing thread."); pthread_cancel(conn->rtp_timing_thread); debug(2, "Join timing thread."); @@ -1742,6 +1730,10 @@ void *player_thread_func(void *arg) { pthread_create(&conn->rtp_control_thread, NULL, &rtp_control_receiver, (void *)conn); pthread_create(&conn->rtp_timing_thread, NULL, &rtp_timing_receiver, (void *)conn); + // create the watchdog mutex and start the watchdog thread; + pthread_mutex_init(&conn->watchdog_mutex, NULL); + pthread_create(&conn->player_watchdog_thread, NULL, &player_watchdog_thread_code, (void *)conn); + pthread_cleanup_push(player_thread_cleanup_handler, arg); // undo what's been done so far // stop looking elsewhere for DACP stuff @@ -2275,6 +2267,14 @@ void *player_thread_func(void *arg) { inframe->given_timestamp = 0; inframe->sequence_number = 0; + // update the watchdog + if ((config.dont_check_timeout == 0) && (config.timeout != 0)) { + uint64_t time_now = get_absolute_time_in_fp(); + debug_mutex_lock(&conn->watchdog_mutex, 1000, 1); + conn->watchdog_bark_time = time_now; + debug_mutex_unlock(&conn->watchdog_mutex, 3); + } + // debug(1,"Sync error %lld frames. Amount to stuff %d." ,sync_error,amount_to_stuff); // new stats calculation. We want a running average of sync error, drift, adjustment, @@ -2800,3 +2800,37 @@ int player_stop(rtsp_conn_info *conn) { return -1; } } + +void player_watchdog_thread_cleanup_handler(void *arg) { + rtsp_conn_info *conn = (rtsp_conn_info *)arg; + debug(2, "Connection %d: Watchdog Exit.", conn->connection_number); +} + +void *player_watchdog_thread_code(void *arg) { + pthread_cleanup_push(player_watchdog_thread_cleanup_handler, arg); + rtsp_conn_info *conn = (rtsp_conn_info *)arg; + do { + usleep(2000000); // check every two seconds + debug(3, "Connection %d: Check the player thread is doing something...", + conn->connection_number); + if ((config.dont_check_timeout == 0) && (config.timeout != 0)) { + debug_mutex_lock(&conn->watchdog_mutex, 1000, 1); + uint64_t last_watchdog_bark_time = conn->watchdog_bark_time; + debug_mutex_unlock(&conn->watchdog_mutex, 3); + if (last_watchdog_bark_time != 0) { + uint64_t time_since_last_bark = (get_absolute_time_in_fp() - last_watchdog_bark_time) >> 32; + uint64_t ct = config.timeout; // go from int to 64-bit int + + if (time_since_last_bark >= ct) { + 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); + } + } + } + } while (1); + pthread_cleanup_pop(0); // should never happen + pthread_exit(NULL); +} diff --git a/player.h b/player.h index 2f1a3284..deb787c6 100644 --- a/player.h +++ b/player.h @@ -85,7 +85,9 @@ typedef struct { int stop; int running; time_t playstart; - pthread_t thread, timer_requester, rtp_audio_thread, rtp_control_thread, rtp_timing_thread; + pthread_t thread, timer_requester, rtp_audio_thread, rtp_control_thread, rtp_timing_thread, + player_watchdog_thread; + int64_t watchdog_bark_time; // pthread_t *ptp; // buffers to delete on exit @@ -213,6 +215,7 @@ typedef struct { // request in flight at the same time pthread_mutex_t reference_time_mutex; + pthread_mutex_t watchdog_mutex; double local_to_remote_time_gradient; // if no drift, this would be exactly 1.0; likely it's // slightly above or below. From eaa98ea7d980384e09a3edfc3dab2872d915849a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 27 Nov 2018 17:40:07 +0000 Subject: [PATCH 231/569] Quieten a few debug messages. --- audio_alsa.c | 13 ++++++++----- player.c | 26 +++++++++++++------------- rtp.c | 12 ++++++------ 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 5ca4836c..2bd6efb8 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -874,7 +874,7 @@ int delay(long *the_delay) { if (alsa_handle == NULL) { return -ENODEV; } else { - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0); int derr; snd_pcm_state_t dac_state = snd_pcm_state(alsa_handle); if (dac_state == SND_PCM_STATE_RUNNING) { @@ -895,6 +895,8 @@ int delay(long *the_delay) { // there's nothing in the pipeline, so we can't measure frame rate. frame_index = 0; // we'll be starting over... measurement_data_is_valid = 0; + } else if (*the_delay > 10000) { + debug(1,"long delay: %ld.",*the_delay); } } } else { @@ -921,7 +923,7 @@ int delay(long *the_delay) { } } } - debug_mutex_unlock(&alsa_mutex, 3); + debug_mutex_unlock(&alsa_mutex, 0); pthread_cleanup_pop(0); // here, occasionally pretend there's a problem with pcm_get_delay() // if ((random() % 100000) < 3) // keep it pretty rare @@ -960,7 +962,7 @@ static int play(void *buf, int samples) { pthread_cleanup_pop(0); // release the mutex } if (ret == 0) { - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0); // snd_pcm_sframes_t current_delay = 0; int err, err2; if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) { @@ -980,6 +982,7 @@ static int play(void *buf, int samples) { if (samples == 0) debug(1, "empty buffer being passed to pcm_writei -- skipping it"); if ((samples != 0) && (buf != NULL)) { + debug(3,"write %d frames.",samples); err = alsa_pcm_write(alsa_handle, buf, samples); if (err < 0) { frame_index = 0; @@ -1044,7 +1047,7 @@ static int play(void *buf, int samples) { frame_index = 0; measurement_data_is_valid = 0; } - debug_mutex_unlock(&alsa_mutex, 3); + debug_mutex_unlock(&alsa_mutex, 0); pthread_cleanup_pop(0); // release the mutex } return ret; @@ -1065,7 +1068,7 @@ static void flush(void) { if ((derr = snd_pcm_hw_free(alsa_handle))) debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr)); - + // flush also closes the device if ((derr = snd_pcm_close(alsa_handle))) debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr)); diff --git a/player.c b/player.c index fe8a1447..3507db6b 100644 --- a/player.c +++ b/player.c @@ -443,7 +443,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in debug_mutex_unlock(&conn->flush_mutex, 3); } - debug_mutex_lock(&conn->ab_mutex, 30000, 1); + debug_mutex_lock(&conn->ab_mutex, 30000, 0); conn->packet_count++; conn->packet_count_since_flush++; conn->time_of_last_audio_packet = get_absolute_time_in_fp(); @@ -615,7 +615,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in } } } - debug_mutex_unlock(&conn->ab_mutex, 3); + debug_mutex_unlock(&conn->ab_mutex, 0); } int32_t rand_in_range(int32_t exclusive_range_limit) { @@ -759,7 +759,7 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t void buffer_get_frame_cleanup_handler(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; - debug_mutex_unlock(&conn->ab_mutex, 3); + debug_mutex_unlock(&conn->ab_mutex, 0); } // get the next frame, when available. return 0 if underrun/stream reset. @@ -770,7 +770,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { abuf_t *curframe = NULL; int notified_buffer_empty = 0; // diagnostic only - debug_mutex_lock(&conn->ab_mutex, 30000, 1); + debug_mutex_lock(&conn->ab_mutex, 30000, 0); int wait; long dac_delay = 0; // long because alsa returns a long @@ -780,7 +780,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { do { // get the time local_time_now = get_absolute_time_in_fp(); // type okay - debug(3, "buffer_get_frame is iterating"); + // debug(3, "buffer_get_frame is iterating"); int rco = get_requested_connection_state_to_output(); @@ -797,12 +797,12 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (config.output->is_running) if (config.output->is_running() != 0) { // if the back end isn't running for any reason debug(3, "not running"); - debug_mutex_lock(&conn->flush_mutex, 1000, 1); + debug_mutex_lock(&conn->flush_mutex, 1000, 0); conn->flush_requested = 1; - debug_mutex_unlock(&conn->flush_mutex, 3); + debug_mutex_unlock(&conn->flush_mutex, 0); } - debug_mutex_lock(&conn->flush_mutex, 1000, 1); + debug_mutex_lock(&conn->flush_mutex, 1000, 0); if (conn->flush_requested == 1) { if (config.output->flush) config.output->flush(); // no cancellation points @@ -812,7 +812,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { conn->time_since_play_started = 0; conn->flush_requested = 0; } - debug_mutex_unlock(&conn->flush_mutex, 3); + debug_mutex_unlock(&conn->flush_mutex, 0); if (conn->ab_synced) { curframe = conn->audio_buffer + BUFIDX(conn->ab_read); @@ -2270,9 +2270,9 @@ void *player_thread_func(void *arg) { // update the watchdog if ((config.dont_check_timeout == 0) && (config.timeout != 0)) { uint64_t time_now = get_absolute_time_in_fp(); - debug_mutex_lock(&conn->watchdog_mutex, 1000, 1); + debug_mutex_lock(&conn->watchdog_mutex, 1000, 0); conn->watchdog_bark_time = time_now; - debug_mutex_unlock(&conn->watchdog_mutex, 3); + debug_mutex_unlock(&conn->watchdog_mutex, 0); } // debug(1,"Sync error %lld frames. Amount to stuff %d." ,sync_error,amount_to_stuff); @@ -2814,9 +2814,9 @@ void *player_watchdog_thread_code(void *arg) { debug(3, "Connection %d: Check the player thread is doing something...", conn->connection_number); if ((config.dont_check_timeout == 0) && (config.timeout != 0)) { - debug_mutex_lock(&conn->watchdog_mutex, 1000, 1); + debug_mutex_lock(&conn->watchdog_mutex, 1000, 0); uint64_t last_watchdog_bark_time = conn->watchdog_bark_time; - debug_mutex_unlock(&conn->watchdog_mutex, 3); + debug_mutex_unlock(&conn->watchdog_mutex, 0); if (last_watchdog_bark_time != 0) { uint64_t time_since_last_bark = (get_absolute_time_in_fp() - last_watchdog_bark_time) >> 32; uint64_t ct = config.timeout; // go from int to 64-bit int diff --git a/rtp.c b/rtp.c index 0c97fc31..dba32390 100644 --- a/rtp.c +++ b/rtp.c @@ -1040,7 +1040,7 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, uint16_t cport, uint16_t tport void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time, uint64_t *remote_timestamp_time, rtsp_conn_info *conn) { // types okay - debug_mutex_lock(&conn->reference_time_mutex, 1000, 1); + debug_mutex_lock(&conn->reference_time_mutex, 1000, 0); *timestamp = conn->reference_timestamp; *remote_timestamp_time = conn->remote_reference_timestamp_time; *timestamp_time = @@ -1048,7 +1048,7 @@ void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time // if ((*timestamp == 0) && (*timestamp_time == 0)) { // debug(1,"Reference timestamp is invalid."); //} - debug_mutex_unlock(&conn->reference_time_mutex, 3); + debug_mutex_unlock(&conn->reference_time_mutex, 0); } void clear_reference_timestamp(rtsp_conn_info *conn) { @@ -1103,7 +1103,7 @@ int sanitised_source_rate_information(uint32_t *frames, uint64_t *time, rtsp_con // the reference timestamps are denominated in terms of the input rate int frame_to_local_time(uint32_t timestamp, uint64_t *time, rtsp_conn_info *conn) { - debug_mutex_lock(&conn->reference_time_mutex, 1000, 1); + debug_mutex_lock(&conn->reference_time_mutex, 1000, 0); int result = 0; uint64_t time_difference; uint32_t frame_difference; @@ -1135,12 +1135,12 @@ int frame_to_local_time(uint32_t timestamp, uint64_t *time, rtsp_conn_info *conn // on the specified fps. } *time = remote_time_of_timestamp - local_to_remote_time_difference_now(conn); - debug_mutex_unlock(&conn->reference_time_mutex, 3); + debug_mutex_unlock(&conn->reference_time_mutex, 0); return result; } int local_time_to_frame(uint64_t time, uint32_t *frame, rtsp_conn_info *conn) { - debug_mutex_lock(&conn->reference_time_mutex, 1000, 1); + debug_mutex_lock(&conn->reference_time_mutex, 1000, 0); int result = 0; uint64_t time_difference; @@ -1171,7 +1171,7 @@ int local_time_to_frame(uint64_t time, uint32_t *frame, rtsp_conn_info *conn) { // debug(1,"Frame interval is %" PRId64 " frames.",-frame_interval); *frame = (conn->reference_timestamp - frame_interval); } - debug_mutex_unlock(&conn->reference_time_mutex, 3); + debug_mutex_unlock(&conn->reference_time_mutex, 0); return result; } From b7ee743a0ee00df1a1c01b79cf3fc75c01f64dd0 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 27 Nov 2018 17:44:45 +0000 Subject: [PATCH 232/569] Quieten a few debug messages. --- audio_alsa.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 2bd6efb8..dc6f11d8 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -895,8 +895,6 @@ int delay(long *the_delay) { // there's nothing in the pipeline, so we can't measure frame rate. frame_index = 0; // we'll be starting over... measurement_data_is_valid = 0; - } else if (*the_delay > 10000) { - debug(1,"long delay: %ld.",*the_delay); } } } else { From 227c38a89c662d8659cbcb976cc26310b4396e3d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 27 Nov 2018 22:03:56 +0000 Subject: [PATCH 233/569] Try to capture what happens after a write error. --- rtsp.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/rtsp.c b/rtsp.c index 99000174..cb26c576 100644 --- a/rtsp.c +++ b/rtsp.c @@ -425,13 +425,9 @@ static void debug_print_msg_content(int level, rtsp_message *msg) { void msg_free(rtsp_message *msg) { if (msg) { - int rc = pthread_mutex_lock(&reference_counter_lock); - if (rc) - debug(1, "Error %d locking reference counter lock during msg_free()", rc); + debug_mutex_lock(&reference_counter_lock,1000,3); msg->referenceCount--; - rc = pthread_mutex_unlock(&reference_counter_lock); - if (rc) - debug(1, "Error %d unlocking reference counter lock during msg_free()", rc); + debug_mutex_unlock(&reference_counter_lock,3); if (msg->referenceCount == 0) { unsigned int i; for (i = 0; i < msg->nheaders; i++) { @@ -1998,9 +1994,9 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { if (conn->player_thread) player_stop(conn); if (conn->fd > 0) { - // debug(1, "Connection %d: closing fd %d.", - // conn->connection_number,conn->fd); + debug(3, "Connection %d: closing fd %d.", conn->connection_number,conn->fd); close(conn->fd); + debug(3, "Connection %d: closed fd %d.", conn->connection_number,conn->fd); } if (conn->auth_nonce) { free(conn->auth_nonce); @@ -2025,6 +2021,7 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { if (rc) debug(1, "Connection %d: error %d destroying flush_mutex.", conn->connection_number, rc); + debug(3, "Connection %d: Checking play lock.", conn->connection_number); debug_mutex_lock(&playing_conn_lock, 1000000, 3); // get it if (playing_conn == conn) { debug(3, "Connection %d: Unlocking play lock.", conn->connection_number); @@ -2037,7 +2034,7 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { } void msg_cleanup_function(void *arg) { - // debug(1, "msg_cleanup_function called."); + debug(3, "msg_cleanup_function called."); msg_free((rtsp_message *)arg); } @@ -2131,6 +2128,8 @@ static void *rtsp_conversation_thread_func(void *pconn) { } debug(debug_level, "Connection %d: RTSP Response:", conn->connection_number); debug_print_msg_headers(debug_level, resp); + + /* fd_set writefds; FD_ZERO(&writefds); FD_SET(conn->fd, &writefds); @@ -2138,6 +2137,8 @@ static void *rtsp_conversation_thread_func(void *pconn) { memory_barrier(); } while (conn->stop == 0 && pselect(conn->fd + 1, NULL, &writefds, NULL, NULL, &pselect_sigset) <= 0); + */ + if (conn->stop == 0) { int err = msg_write_response(conn->fd, resp); if (err) { @@ -2145,6 +2146,8 @@ static void *rtsp_conversation_thread_func(void *pconn) { "connection.", conn->connection_number); conn->stop = 1; + if (debuglev >= 1) + debuglev = 3; // see what happens next } } pthread_cleanup_pop(1); From 4ab81ce0b76db062d4ce910515291e740e64edf3 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 27 Nov 2018 22:53:27 +0000 Subject: [PATCH 234/569] Add extra diagnostic to capture a meg_write_response error. --- rtsp.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/rtsp.c b/rtsp.c index cb26c576..478f20e6 100644 --- a/rtsp.c +++ b/rtsp.c @@ -692,9 +692,17 @@ int msg_write_response(int fd, rtsp_message *resp) { debug(1, "Attempted to write overlong RTSP packet 3"); return -3; } - if (write(fd, pkt, p - pkt) != p - pkt) { - debug(1, "Error writing an RTSP packet -- requested bytes not fully written."); - return -4; + ssize_t reply = write(fd, pkt, p - pkt); + if (reply == -1) { + char errorstring[1024]; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + debug(1, "msg_write_response error %d: \"%s\".", + errno, (char *)errorstring); + return -4; + } + if (reply != p - pkt) { + debug(1, "msg_write_response error -- requested bytes not fully written."); + return -5; } return 0; } From 22f3e3e318d91d6e888f700bb2661dc3e32326cb Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 28 Nov 2018 17:18:03 +0000 Subject: [PATCH 235/569] Move watchdog to cover connection from start, not just from start of play. Start investigating UDP port allocation. Tidy up handling or RTSP write errors -- remove the pselect code from before a write. --- audio_alsa.c | 4 +-- common.c | 5 +++- player.c | 47 +--------------------------------- rtp.c | 9 +++++-- rtsp.c | 72 +++++++++++++++++++++++++++++++++++++++++++--------- shairport.c | 2 +- 6 files changed, 75 insertions(+), 64 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index dc6f11d8..00c33900 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -980,7 +980,7 @@ static int play(void *buf, int samples) { if (samples == 0) debug(1, "empty buffer being passed to pcm_writei -- skipping it"); if ((samples != 0) && (buf != NULL)) { - debug(3,"write %d frames.",samples); + debug(3, "write %d frames.", samples); err = alsa_pcm_write(alsa_handle, buf, samples); if (err < 0) { frame_index = 0; @@ -1066,7 +1066,7 @@ static void flush(void) { if ((derr = snd_pcm_hw_free(alsa_handle))) debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr)); - + // flush also closes the device if ((derr = snd_pcm_close(alsa_handle))) debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr)); diff --git a/common.c b/common.c index 0d76a4f4..76cf8204 100644 --- a/common.c +++ b/common.c @@ -96,7 +96,10 @@ sigset_t pselect_sigset; static uint16_t UDPPortIndex = 0; -void resetFreeUDPPort() { UDPPortIndex = 0; } +void resetFreeUDPPort() { + debug(1,"Resetting UDP Port Suggestion to %u",config.udp_port_base); + UDPPortIndex = 0; +} uint16_t nextFreeUDPPort() { if (UDPPortIndex == 0) diff --git a/player.c b/player.c index 3507db6b..4e2bfc4f 100644 --- a/player.c +++ b/player.c @@ -114,7 +114,6 @@ uint64_t modulo_64_offset(uint64_t from, uint64_t to) { } void do_flush(uint32_t timestamp, rtsp_conn_info *conn); -void *player_watchdog_thread_code(void *arg); static void ab_resync(rtsp_conn_info *conn) { int i; @@ -1431,12 +1430,6 @@ void player_thread_cleanup_handler(void *arg) { #endif debug(2, "Cancelling timing, control and audio threads..."); - debug(2, "Cancel watchdog thread."); - pthread_cancel(conn->player_watchdog_thread); - debug(2, "Join watchdog thread."); - pthread_join(conn->player_watchdog_thread, NULL); - debug(2, "Delete watchdog mutex."); - pthread_mutex_destroy(&conn->watchdog_mutex); debug(2, "Cancel timing thread."); pthread_cancel(conn->rtp_timing_thread); debug(2, "Join timing thread."); @@ -1730,10 +1723,6 @@ void *player_thread_func(void *arg) { pthread_create(&conn->rtp_control_thread, NULL, &rtp_control_receiver, (void *)conn); pthread_create(&conn->rtp_timing_thread, NULL, &rtp_timing_receiver, (void *)conn); - // create the watchdog mutex and start the watchdog thread; - pthread_mutex_init(&conn->watchdog_mutex, NULL); - pthread_create(&conn->player_watchdog_thread, NULL, &player_watchdog_thread_code, (void *)conn); - pthread_cleanup_push(player_thread_cleanup_handler, arg); // undo what's been done so far // stop looking elsewhere for DACP stuff @@ -2799,38 +2788,4 @@ int player_stop(rtsp_conn_info *conn) { // debuglev = dl; return -1; } -} - -void player_watchdog_thread_cleanup_handler(void *arg) { - rtsp_conn_info *conn = (rtsp_conn_info *)arg; - debug(2, "Connection %d: Watchdog Exit.", conn->connection_number); -} - -void *player_watchdog_thread_code(void *arg) { - pthread_cleanup_push(player_watchdog_thread_cleanup_handler, arg); - rtsp_conn_info *conn = (rtsp_conn_info *)arg; - do { - usleep(2000000); // check every two seconds - debug(3, "Connection %d: Check the player thread is doing something...", - conn->connection_number); - if ((config.dont_check_timeout == 0) && (config.timeout != 0)) { - debug_mutex_lock(&conn->watchdog_mutex, 1000, 0); - 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_fp() - last_watchdog_bark_time) >> 32; - uint64_t ct = config.timeout; // go from int to 64-bit int - - if (time_since_last_bark >= ct) { - 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); - } - } - } - } while (1); - pthread_cleanup_pop(0); // should never happen - pthread_exit(NULL); -} +} \ No newline at end of file diff --git a/rtp.c b/rtp.c index dba32390..1f908b0e 100644 --- a/rtp.c +++ b/rtp.c @@ -890,9 +890,14 @@ static uint16_t bind_port(int ip_family, const char *self_ip_address, uint32_t s if (ret < 0) { close(local_socket); - die("error: could not bind a UDP port! Check the udp_port_range is large enough -- it must be " + char errorstring[1024]; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + die("error %d: \"%s\". Could not bind a UDP port! Check the udp_port_range is large enough -- " + "it must be " "at least 3, and 10 or more is suggested -- or " - "check for restrictive firewall settings or a bad router!"); + "check for restrictive firewall settings or a bad router! UDP base is %u, range is %u and " + "current suggestion is %u.", + errno, errorstring, config.udp_port_base, config.udp_port_range, desired_port); } uint16_t sport; diff --git a/rtsp.c b/rtsp.c index 478f20e6..beaaf8f7 100644 --- a/rtsp.c +++ b/rtsp.c @@ -253,6 +253,39 @@ int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) { #endif +void player_watchdog_thread_cleanup_handler(void *arg) { + rtsp_conn_info *conn = (rtsp_conn_info *)arg; + debug(2, "Connection %d: Watchdog Exit.", conn->connection_number); +} + +void *player_watchdog_thread_code(void *arg) { + pthread_cleanup_push(player_watchdog_thread_cleanup_handler, arg); + rtsp_conn_info *conn = (rtsp_conn_info *)arg; + do { + usleep(2000000); // check every two seconds + debug(3, "Connection %d: Check the thread is doing something...", conn->connection_number); + if ((config.dont_check_timeout == 0) && (config.timeout != 0)) { + debug_mutex_lock(&conn->watchdog_mutex, 1000, 0); + 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_fp() - last_watchdog_bark_time) >> 32; + uint64_t ct = config.timeout; // go from int to 64-bit int + + if (time_since_last_bark >= ct) { + 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); + } + } + } + } while (1); + pthread_cleanup_pop(0); // should never happen + pthread_exit(NULL); +} + void ask_other_rtsp_conversation_threads_to_stop(pthread_t except_this_thread); void rtsp_request_shutdown_stream(void) { @@ -425,9 +458,9 @@ static void debug_print_msg_content(int level, rtsp_message *msg) { void msg_free(rtsp_message *msg) { if (msg) { - debug_mutex_lock(&reference_counter_lock,1000,3); + debug_mutex_lock(&reference_counter_lock, 1000, 3); msg->referenceCount--; - debug_mutex_unlock(&reference_counter_lock,3); + debug_mutex_unlock(&reference_counter_lock, 3); if (msg->referenceCount == 0) { unsigned int i; for (i = 0; i < msg->nheaders; i++) { @@ -695,10 +728,9 @@ int msg_write_response(int fd, rtsp_message *resp) { ssize_t reply = write(fd, pkt, p - pkt); if (reply == -1) { char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(1, "msg_write_response error %d: \"%s\".", - errno, (char *)errorstring); - return -4; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + debug(1, "msg_write_response error %d: \"%s\".", errno, (char *)errorstring); + return -4; } if (reply != p - pkt) { debug(1, "msg_write_response error -- requested bytes not fully written."); @@ -890,12 +922,14 @@ void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { "timing_port=%d;server_" "port=%d", conn->local_control_port, conn->local_timing_port, conn->local_audio_port); + msg_add_header(resp, "Transport", resphdr); msg_add_header(resp, "Session", "1"); resp->respcode = 200; + debug(1, "Connection %d: SETUP with UDP ports Control: %d, Timing: %d and Audio: %d.", conn->connection_number, conn->local_control_port, conn->local_timing_port, conn->local_audio_port); return; error: @@ -2002,9 +2036,9 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { if (conn->player_thread) player_stop(conn); if (conn->fd > 0) { - debug(3, "Connection %d: closing fd %d.", conn->connection_number,conn->fd); + debug(3, "Connection %d: closing fd %d.", conn->connection_number, conn->fd); close(conn->fd); - debug(3, "Connection %d: closed fd %d.", conn->connection_number,conn->fd); + debug(3, "Connection %d: closed fd %d.", conn->connection_number, conn->fd); } if (conn->auth_nonce) { free(conn->auth_nonce); @@ -2035,7 +2069,17 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { debug(3, "Connection %d: Unlocking play lock.", conn->connection_number); playing_conn = NULL; } + debug_mutex_unlock(&playing_conn_lock, 3); + debug(2, "Cancel watchdog thread."); + pthread_cancel(conn->player_watchdog_thread); + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); + debug(2, "Join watchdog thread."); + pthread_join(conn->player_watchdog_thread, NULL); + pthread_setcancelstate(oldState, NULL); + debug(2, "Delete watchdog mutex."); + pthread_mutex_destroy(&conn->watchdog_mutex); debug(2, "Connection %d: RTSP thread terminated.", conn->connection_number); conn->running = 0; @@ -2049,6 +2093,10 @@ void msg_cleanup_function(void *arg) { static void *rtsp_conversation_thread_func(void *pconn) { rtsp_conn_info *conn = pconn; + // create the watchdog mutex and start the watchdog thread; + pthread_mutex_init(&conn->watchdog_mutex, NULL); + pthread_create(&conn->player_watchdog_thread, NULL, &player_watchdog_thread_code, (void *)conn); + int rc = pthread_mutex_init(&conn->flush_mutex, NULL); if (rc) die("Connection %d: error %d initialising flush_mutex.", conn->connection_number, rc); @@ -2136,7 +2184,7 @@ static void *rtsp_conversation_thread_func(void *pconn) { } debug(debug_level, "Connection %d: RTSP Response:", conn->connection_number); debug_print_msg_headers(debug_level, resp); - + /* fd_set writefds; FD_ZERO(&writefds); @@ -2146,7 +2194,7 @@ static void *rtsp_conversation_thread_func(void *pconn) { } while (conn->stop == 0 && pselect(conn->fd + 1, NULL, &writefds, NULL, NULL, &pselect_sigset) <= 0); */ - + if (conn->stop == 0) { int err = msg_write_response(conn->fd, resp); if (err) { @@ -2154,8 +2202,8 @@ static void *rtsp_conversation_thread_func(void *pconn) { "connection.", conn->connection_number); conn->stop = 1; - if (debuglev >= 1) - debuglev = 3; // see what happens next + // if (debuglev >= 1) + // debuglev = 3; // see what happens next } } pthread_cleanup_pop(1); diff --git a/shairport.c b/shairport.c index c9212544..506f9feb 100644 --- a/shairport.c +++ b/shairport.c @@ -31,11 +31,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include #include From 1cf28be647f20370de7ddb5d8c60b2753d2e5869 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 28 Nov 2018 19:39:11 +0000 Subject: [PATCH 236/569] Add SO_REUSEADDR to the UDP sockets. See if it makes any difference. --- rtp.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rtp.c b/rtp.c index 1f908b0e..464bc303 100644 --- a/rtp.c +++ b/rtp.c @@ -858,6 +858,16 @@ static uint16_t bind_port(int ip_family, const char *self_ip_address, uint32_t s int local_socket = socket(ip_family, SOCK_DGRAM, IPPROTO_UDP); if (local_socket == -1) die("Could not allocate a socket."); + + int val = 1; + ret = setsockopt(local_socket, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); + if (ret < 0) { + char errorstring[1024]; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + debug(1,"Error %d: \"%s\". Couldn't set SO_REUSEADDR"); + } + + SOCKADDR myaddr; int tryCount = 0; uint16_t desired_port; From 7bf22b426584a2e3cc136784588bb6845e1ef5ec Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 28 Nov 2018 19:49:12 +0000 Subject: [PATCH 237/569] Add some debug messages to closing the UDP ports. --- rtp.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rtp.c b/rtp.c index 464bc303..c61be2ee 100644 --- a/rtp.c +++ b/rtp.c @@ -98,14 +98,14 @@ uint64_t local_to_remote_time_difference_now(rtsp_conn_info *conn) { } void rtp_audio_receiver_cleanup_handler(void *arg) { - debug(3, "Audio Receiver Cleanup."); + debug(1, "Audio Receiver Cleanup."); rtsp_conn_info *conn = (rtsp_conn_info *)arg; debug(3, "shutdown audio socket."); shutdown(conn->audio_socket, SHUT_RDWR); debug(3, "close audio socket."); close(conn->audio_socket); - debug(3, "Audio Receiver Cleanup Successful."); + debug(1, "Audio Receiver Cleanup Successful."); } void *rtp_audio_receiver(void *arg) { @@ -242,11 +242,11 @@ void *rtp_audio_receiver(void *arg) { void rtp_control_handler_cleanup_handler(void *arg) { debug(3, "Control Receiver Cleanup."); rtsp_conn_info *conn = (rtsp_conn_info *)arg; - debug(3, "shutdown control socket."); + debug(1, "shutdown control socket."); shutdown(conn->control_socket, SHUT_RDWR); debug(3, "close control socket."); close(conn->control_socket); - debug(3, "Control Receiver Cleanup Successful."); + debug(1, "Control Receiver Cleanup Successful."); } void *rtp_control_receiver(void *arg) { @@ -563,11 +563,11 @@ void rtp_timing_receiver_cleanup_handler(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; pthread_cancel(conn->timer_requester); pthread_join(conn->timer_requester, NULL); - debug(3, "shutdown timing socket."); + debug(1, "shutdown timing socket."); shutdown(conn->timing_socket, SHUT_RDWR); debug(3, "close timing socket."); close(conn->timing_socket); - debug(3, "Timing Receiver Cleanup Successful."); + debug(1, "Timing Receiver Cleanup Successful."); } void *rtp_timing_receiver(void *arg) { From c318796e5511686fdafbc0b445ada2eb30b38ae2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 29 Nov 2018 11:37:56 +0000 Subject: [PATCH 238/569] Add an uncancellable usleep timer. Make the player and UDP thread cleanups uncancellable. --- common.c | 10 ++++++++++ common.h | 2 ++ player.c | 3 +++ rtp.c | 35 ++++++++++++++++++++++------------- rtsp.c | 28 ++++++++++++++++------------ 5 files changed, 53 insertions(+), 25 deletions(-) diff --git a/common.c b/common.c index 76cf8204..f815af91 100644 --- a/common.c +++ b/common.c @@ -94,6 +94,16 @@ volatile int debuglev = 0; sigset_t pselect_sigset; + +int usleep_uncancellable(useconds_t usec) { + int response; + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); + response = usleep_uncancellable(usec); + pthread_setcancelstate(oldState, NULL); + return response; +} + static uint16_t UDPPortIndex = 0; void resetFreeUDPPort() { diff --git a/common.h b/common.h index a043d0f7..59492c5f 100644 --- a/common.h +++ b/common.h @@ -330,5 +330,7 @@ char *get_version_string(); // mallocs a string space -- remember to free it aft void sps_nanosleep(const time_t sec, const long nanosec); // waits for this time, even through interruptions + +int usleep_uncancellable(useconds_t usec); #endif // _COMMON_H diff --git a/player.c b/player.c index 4e2bfc4f..57d7527b 100644 --- a/player.c +++ b/player.c @@ -1396,6 +1396,8 @@ void player_thread_initial_cleanup_handler(__attribute__((unused)) void *arg) { void player_thread_cleanup_handler(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); debug(3, "Connection %d: player thread main loop exit via player_thread_cleanup_handler.", conn->connection_number); @@ -1465,6 +1467,7 @@ void player_thread_cleanup_handler(void *arg) { clear_reference_timestamp(conn); conn->rtp_running = 0; + pthread_setcancelstate(oldState, NULL); } void *player_thread_func(void *arg) { diff --git a/rtp.c b/rtp.c index c61be2ee..bfb32d75 100644 --- a/rtp.c +++ b/rtp.c @@ -98,14 +98,15 @@ uint64_t local_to_remote_time_difference_now(rtsp_conn_info *conn) { } void rtp_audio_receiver_cleanup_handler(void *arg) { - debug(1, "Audio Receiver Cleanup."); + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); + debug(3, "Audio Receiver Cleanup."); rtsp_conn_info *conn = (rtsp_conn_info *)arg; - debug(3, "shutdown audio socket."); - shutdown(conn->audio_socket, SHUT_RDWR); - debug(3, "close audio socket."); + debug(3, "Close Audio Socket."); close(conn->audio_socket); - - debug(1, "Audio Receiver Cleanup Successful."); + debug(3, "Audio Receiver Cleanup Successful."); + usleep(20000); // microseconds + pthread_setcancelstate(oldState, NULL); } void *rtp_audio_receiver(void *arg) { @@ -240,13 +241,17 @@ void *rtp_audio_receiver(void *arg) { } void rtp_control_handler_cleanup_handler(void *arg) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); debug(3, "Control Receiver Cleanup."); rtsp_conn_info *conn = (rtsp_conn_info *)arg; - debug(1, "shutdown control socket."); + debug(3, "Shut Down Control Socket."); shutdown(conn->control_socket, SHUT_RDWR); - debug(3, "close control socket."); + debug(3, "Close Control Socket."); close(conn->control_socket); - debug(1, "Control Receiver Cleanup Successful."); + debug(3, "Control Receiver Cleanup Successful."); + usleep(20000); // microseconds + pthread_setcancelstate(oldState, NULL); } void *rtp_control_receiver(void *arg) { @@ -559,15 +564,19 @@ void *rtp_timing_sender(void *arg) { } void rtp_timing_receiver_cleanup_handler(void *arg) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); debug(3, "Timing Receiver Cleanup."); rtsp_conn_info *conn = (rtsp_conn_info *)arg; + debug(3, "Cancel Timing Requester."); pthread_cancel(conn->timer_requester); + debug(3, "Join Timing Requester."); pthread_join(conn->timer_requester, NULL); - debug(1, "shutdown timing socket."); - shutdown(conn->timing_socket, SHUT_RDWR); - debug(3, "close timing socket."); + debug(3, "Close Timing Socket."); close(conn->timing_socket); - debug(1, "Timing Receiver Cleanup Successful."); + debug(3, "Timing Receiver Cleanup Successful."); + usleep(20000); // microseconds + pthread_setcancelstate(oldState, NULL); } void *rtp_timing_receiver(void *arg) { diff --git a/rtsp.c b/rtsp.c index beaaf8f7..2d1203d7 100644 --- a/rtsp.c +++ b/rtsp.c @@ -570,10 +570,12 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes if (nread < 0) { if (errno == EINTR) continue; - char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(1, "Connection %d: rtsp_read_request_response_read_error %d: \"%s\".", - conn->connection_number, errno, (char *)errorstring); + if (errno != ECONNRESET) { + char errorstring[1024]; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + debug(1, "Connection %d: rtsp_read_request_response_read_error %d: \"%s\".", + conn->connection_number, errno, (char *)errorstring); + } reply = rtsp_read_request_response_read_error; goto shutdown; } @@ -1577,7 +1579,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag have_the_player = 1; warn("Duplicate ANNOUNCE, by the look of it!"); } else if (playing_conn->stop) { - debug(2, "Connection %d: ANNOUNCE: already shutting down; waiting for it...", + debug(1, "Connection %d: ANNOUNCE: already shutting down; waiting for it...", playing_conn->connection_number); should_wait = 1; } else if (config.allow_session_interruption == 1) { @@ -2063,14 +2065,7 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { if (rc) debug(1, "Connection %d: error %d destroying flush_mutex.", conn->connection_number, rc); - debug(3, "Connection %d: Checking play lock.", conn->connection_number); - debug_mutex_lock(&playing_conn_lock, 1000000, 3); // get it - if (playing_conn == conn) { - debug(3, "Connection %d: Unlocking play lock.", conn->connection_number); - playing_conn = NULL; - } - debug_mutex_unlock(&playing_conn_lock, 3); debug(2, "Cancel watchdog thread."); pthread_cancel(conn->player_watchdog_thread); int oldState; @@ -2081,6 +2076,15 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { debug(2, "Delete watchdog mutex."); pthread_mutex_destroy(&conn->watchdog_mutex); + + debug(3, "Connection %d: Checking play lock.", conn->connection_number); + debug_mutex_lock(&playing_conn_lock, 1000000, 3); // get it + if (playing_conn == conn) { + debug(1, "Connection %d: Unlocking play lock.", conn->connection_number); + playing_conn = NULL; + } + debug_mutex_unlock(&playing_conn_lock, 3); + debug(2, "Connection %d: RTSP thread terminated.", conn->connection_number); conn->running = 0; } From ca45d2ecbeea1eac2496bea6cff312f7c2ede571 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 29 Nov 2018 12:18:07 +0000 Subject: [PATCH 239/569] Clang formatted. --- common.c | 9 ++++----- common.h | 2 +- rtp.c | 11 +++++------ rtsp.c | 17 ++++++++--------- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/common.c b/common.c index f815af91..d8fcbd44 100644 --- a/common.c +++ b/common.c @@ -94,10 +94,9 @@ volatile int debuglev = 0; sigset_t pselect_sigset; - int usleep_uncancellable(useconds_t usec) { - int response; - int oldState; + int response; + int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); response = usleep_uncancellable(usec); pthread_setcancelstate(oldState, NULL); @@ -107,8 +106,8 @@ int usleep_uncancellable(useconds_t usec) { static uint16_t UDPPortIndex = 0; void resetFreeUDPPort() { - debug(1,"Resetting UDP Port Suggestion to %u",config.udp_port_base); - UDPPortIndex = 0; + debug(1, "Resetting UDP Port Suggestion to %u", config.udp_port_base); + UDPPortIndex = 0; } uint16_t nextFreeUDPPort() { diff --git a/common.h b/common.h index 59492c5f..ece21b1d 100644 --- a/common.h +++ b/common.h @@ -330,7 +330,7 @@ char *get_version_string(); // mallocs a string space -- remember to free it aft void sps_nanosleep(const time_t sec, const long nanosec); // waits for this time, even through interruptions - + int usleep_uncancellable(useconds_t usec); #endif // _COMMON_H diff --git a/rtp.c b/rtp.c index bfb32d75..61c69dd2 100644 --- a/rtp.c +++ b/rtp.c @@ -98,7 +98,7 @@ uint64_t local_to_remote_time_difference_now(rtsp_conn_info *conn) { } void rtp_audio_receiver_cleanup_handler(void *arg) { - int oldState; + int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); debug(3, "Audio Receiver Cleanup."); rtsp_conn_info *conn = (rtsp_conn_info *)arg; @@ -564,13 +564,13 @@ void *rtp_timing_sender(void *arg) { } void rtp_timing_receiver_cleanup_handler(void *arg) { - int oldState; + int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); debug(3, "Timing Receiver Cleanup."); rtsp_conn_info *conn = (rtsp_conn_info *)arg; debug(3, "Cancel Timing Requester."); pthread_cancel(conn->timer_requester); - debug(3, "Join Timing Requester."); + debug(3, "Join Timing Requester."); pthread_join(conn->timer_requester, NULL); debug(3, "Close Timing Socket."); close(conn->timing_socket); @@ -867,16 +867,15 @@ static uint16_t bind_port(int ip_family, const char *self_ip_address, uint32_t s int local_socket = socket(ip_family, SOCK_DGRAM, IPPROTO_UDP); if (local_socket == -1) die("Could not allocate a socket."); - + int val = 1; ret = setsockopt(local_socket, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); if (ret < 0) { char errorstring[1024]; strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(1,"Error %d: \"%s\". Couldn't set SO_REUSEADDR"); + debug(1, "Error %d: \"%s\". Couldn't set SO_REUSEADDR"); } - SOCKADDR myaddr; int tryCount = 0; uint16_t desired_port; diff --git a/rtsp.c b/rtsp.c index 2d1203d7..31af30f9 100644 --- a/rtsp.c +++ b/rtsp.c @@ -571,10 +571,10 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes if (errno == EINTR) continue; if (errno != ECONNRESET) { - char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(1, "Connection %d: rtsp_read_request_response_read_error %d: \"%s\".", - conn->connection_number, errno, (char *)errorstring); + char errorstring[1024]; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + debug(1, "Connection %d: rtsp_read_request_response_read_error %d: \"%s\".", + conn->connection_number, errno, (char *)errorstring); } reply = rtsp_read_request_response_read_error; goto shutdown; @@ -924,14 +924,15 @@ void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { "timing_port=%d;server_" "port=%d", conn->local_control_port, conn->local_timing_port, conn->local_audio_port); - msg_add_header(resp, "Transport", resphdr); msg_add_header(resp, "Session", "1"); resp->respcode = 200; - debug(1, "Connection %d: SETUP with UDP ports Control: %d, Timing: %d and Audio: %d.", conn->connection_number, conn->local_control_port, conn->local_timing_port, conn->local_audio_port); + debug(1, "Connection %d: SETUP with UDP ports Control: %d, Timing: %d and Audio: %d.", + conn->connection_number, conn->local_control_port, conn->local_timing_port, + conn->local_audio_port); return; error: @@ -2065,7 +2066,6 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { if (rc) debug(1, "Connection %d: error %d destroying flush_mutex.", conn->connection_number, rc); - debug(2, "Cancel watchdog thread."); pthread_cancel(conn->player_watchdog_thread); int oldState; @@ -2076,7 +2076,6 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { debug(2, "Delete watchdog mutex."); pthread_mutex_destroy(&conn->watchdog_mutex); - debug(3, "Connection %d: Checking play lock.", conn->connection_number); debug_mutex_lock(&playing_conn_lock, 1000000, 3); // get it if (playing_conn == conn) { @@ -2084,7 +2083,7 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { playing_conn = NULL; } debug_mutex_unlock(&playing_conn_lock, 3); - + debug(2, "Connection %d: RTSP thread terminated.", conn->connection_number); conn->running = 0; } From bcf9b1a84341e5e5d96cd6daca489518b387377a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 29 Nov 2018 13:12:53 +0000 Subject: [PATCH 240/569] Quieten a debug message --- common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.c b/common.c index d8fcbd44..8cbcd0d4 100644 --- a/common.c +++ b/common.c @@ -106,7 +106,7 @@ int usleep_uncancellable(useconds_t usec) { static uint16_t UDPPortIndex = 0; void resetFreeUDPPort() { - debug(1, "Resetting UDP Port Suggestion to %u", config.udp_port_base); + debug(3, "Resetting UDP Port Suggestion to %u", config.udp_port_base); UDPPortIndex = 0; } From fdd5ce430dd54801cfad32f343cc68a8c64452f0 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 29 Nov 2018 13:13:51 +0000 Subject: [PATCH 241/569] Check in SETUP and RECORD that the player is available, clean up setup code by removing gotos. --- rtsp.c | 272 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 149 insertions(+), 123 deletions(-) diff --git a/rtsp.c b/rtsp.c index 31af30f9..637938ef 100644 --- a/rtsp.c +++ b/rtsp.c @@ -253,6 +253,15 @@ int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) { #endif +int have_player(rtsp_conn_info *conn) { + int response = 0; + debug_mutex_lock(&playing_conn_lock, 1000000, 3); + if (playing_conn == conn) // this connection definitely doesn't have the play lock + response = 1; + debug_mutex_unlock(&playing_conn_lock, 3); + return response; +} + void player_watchdog_thread_cleanup_handler(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; debug(2, "Connection %d: Watchdog Exit.", conn->connection_number); @@ -744,40 +753,47 @@ int msg_write_response(int fd, rtsp_message *resp) { void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(2, "Connection %d: RECORD", conn->connection_number); - if (conn->player_thread) - warn("Duplicate RECORD message -- ignored"); - else - player_play(conn); // the thread better be 0 + if (have_player(conn)) { - resp->respcode = 200; - // I think this is for telling the client what the absolute minimum latency - // actually is, - // and when the client specifies a latency, it should be added to this figure. + if (conn->player_thread) + warn("Duplicate RECORD message -- ignored"); + else + player_play(conn); // the thread better be 0 - // Thus, [the old version of] AirPlay's latency figure of 77175, when added to 11025 gives you - // exactly 88200 - // and iTunes' latency figure of 88553, when added to 11025 gives you 99578, - // pretty close to the 99400 we guessed. + resp->respcode = 200; + // I think this is for telling the client what the absolute minimum latency + // actually is, + // and when the client specifies a latency, it should be added to this figure. - msg_add_header(resp, "Audio-Latency", "11025"); + // Thus, [the old version of] AirPlay's latency figure of 77175, when added to 11025 gives you + // exactly 88200 + // and iTunes' latency figure of 88553, when added to 11025 gives you 99578, + // pretty close to the 99400 we guessed. - char *p; - uint32_t rtptime = 0; - char *hdr = msg_get_header(req, "RTP-Info"); + msg_add_header(resp, "Audio-Latency", "11025"); - if (hdr) { - // debug(1,"FLUSH message received: \"%s\".",hdr); - // get the rtp timestamp - p = strstr(hdr, "rtptime="); - if (p) { - p = strchr(p, '='); + char *p; + uint32_t rtptime = 0; + char *hdr = msg_get_header(req, "RTP-Info"); + + if (hdr) { + // debug(1,"FLUSH message received: \"%s\".",hdr); + // get the rtp timestamp + p = strstr(hdr, "rtptime="); if (p) { - rtptime = uatoi(p + 1); // unsigned integer -- up to 2^32-1 - // rtptime--; - // debug(1,"RTSP Flush Requested by handle_record: %u.",rtptime); - player_flush(rtptime, conn); + p = strchr(p, '='); + if (p) { + rtptime = uatoi(p + 1); // unsigned integer -- up to 2^32-1 + // rtptime--; + // debug(1,"RTSP Flush Requested by handle_record: %u.",rtptime); + player_flush(rtptime, conn); + } } } + } else { + warn("Connection %d RECORD received without having the player (no ANNOUNCE?)", + conn->connection_number); + resp->respcode = 451; } } @@ -839,109 +855,119 @@ void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(3, "Connection %d: SETUP", conn->connection_number); - uint16_t cport, tport; - char *ar = msg_get_header(req, "Active-Remote"); - if (ar) { - 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); + resp->respcode = 451; // invalid arguments -- expect them + + if (have_player(conn)) { + + uint16_t cport, tport; + + char *ar = msg_get_header(req, "Active-Remote"); + if (ar) { + 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); #ifdef CONFIG_METADATA - send_metadata('ssnc', 'acre', ar, strlen(ar), req, 1); + 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; - } - - ar = msg_get_header(req, "DACP-ID"); - if (ar) { - debug(2, "Connection %d: SETUP -- DACP-ID string seen: \"%s\".", conn->connection_number, ar); - if (conn->dacp_id) // this is in case SETUP was previously called - free(conn->dacp_id); - conn->dacp_id = strdup(ar); -#ifdef CONFIG_METADATA - send_metadata('ssnc', 'daid', ar, strlen(ar), req, 1); -#endif - } 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 - free(conn->dacp_id); - conn->dacp_id = NULL; - } - - char *hdr = msg_get_header(req, "Transport"); - if (!hdr) { - debug(1, "Connection %d: SETUP doesn't contain a Transport header.", conn->connection_number); - goto error; - } - - char *p; - p = strstr(hdr, "control_port="); - if (!p) { - debug(1, "Connection %d: SETUP doesn't specify a control_port.", conn->connection_number); - goto error; - } - p = strchr(p, '=') + 1; - cport = atoi(p); - - p = strstr(hdr, "timing_port="); - if (!p) { - debug(1, "Connection %d: SETUP doesn't specify a timing_port.", conn->connection_number); - goto error; - } - p = strchr(p, '=') + 1; - tport = atoi(p); - - if (conn->rtp_running) { - if ((conn->remote_control_port != cport) || (conn->remote_timing_port != tport)) { - warn("Connection %d: Duplicate SETUP message with different control (old %u, new %u) or " - "timing (old %u, new " - "%u) ports! This is probably fatal!", - conn->connection_number, conn->remote_control_port, cport, conn->remote_timing_port, - tport); } else { - warn("Connection %d: Duplicate SETUP message with the same control (%u) and timing (%u) " - "ports. This is " - "probably not fatal.", - conn->connection_number, conn->remote_control_port, conn->remote_timing_port); + debug(2, "Connection %d: SETUP -- Note: no Active-Remote information the SETUP Record.", + conn->connection_number); + conn->dacp_active_remote = 0; } + + ar = msg_get_header(req, "DACP-ID"); + if (ar) { + debug(2, "Connection %d: SETUP -- DACP-ID string seen: \"%s\".", conn->connection_number, ar); + if (conn->dacp_id) // this is in case SETUP was previously called + free(conn->dacp_id); + conn->dacp_id = strdup(ar); +#ifdef CONFIG_METADATA + send_metadata('ssnc', 'daid', ar, strlen(ar), req, 1); +#endif + } 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 + free(conn->dacp_id); + conn->dacp_id = NULL; + } + + char *hdr = msg_get_header(req, "Transport"); + if (hdr) { + char *p; + p = strstr(hdr, "control_port="); + if (p) { + p = strchr(p, '=') + 1; + cport = atoi(p); + + p = strstr(hdr, "timing_port="); + if (p) { + p = strchr(p, '=') + 1; + tport = atoi(p); + + if (conn->rtp_running) { + if ((conn->remote_control_port != cport) || (conn->remote_timing_port != tport)) { + warn("Connection %d: Duplicate SETUP message with different control (old %u, new %u) " + "or " + "timing (old %u, new " + "%u) ports! This is probably fatal!", + conn->connection_number, conn->remote_control_port, cport, + conn->remote_timing_port, tport); + } else { + warn("Connection %d: Duplicate SETUP message with the same control (%u) and timing " + "(%u) " + "ports. This is " + "probably not fatal.", + conn->connection_number, conn->remote_control_port, conn->remote_timing_port); + } + } else { + rtp_setup(&conn->local, &conn->remote, cport, tport, conn); + } + if (conn->local_audio_port != 0) { + + char resphdr[256] = ""; + snprintf(resphdr, sizeof(resphdr), + "RTP/AVP/" + "UDP;unicast;interleaved=0-1;mode=record;control_port=%d;" + "timing_port=%d;server_" + "port=%d", + conn->local_control_port, conn->local_timing_port, conn->local_audio_port); + + msg_add_header(resp, "Transport", resphdr); + + msg_add_header(resp, "Session", "1"); + + resp->respcode = 200; // it all worked out okay + debug(1, "Connection %d: SETUP with UDP ports Control: %d, Timing: %d and Audio: %d.", + conn->connection_number, conn->local_control_port, conn->local_timing_port, + conn->local_audio_port); + + } else { + debug(1, "Connection %d: SETUP seems to specify a null audio port.", + conn->connection_number); + } + } else { + debug(1, "Connection %d: SETUP doesn't specify a timing_port.", conn->connection_number); + } + } else { + debug(1, "Connection %d: SETUP doesn't specify a control_port.", conn->connection_number); + } + } else { + debug(1, "Connection %d: SETUP doesn't contain a Transport header.", conn->connection_number); + } + if (resp->respcode != 200) { + debug_mutex_lock(&playing_conn_lock, 1000000, 3); + playing_conn = NULL; // this connection definitely doesn't have the play lock + debug_mutex_unlock(&playing_conn_lock, 3); + } + } else { - rtp_setup(&conn->local, &conn->remote, cport, tport, conn); + warn("Connection %d SETUP received without having the player (no ANNOUNCE?)", + conn->connection_number); } - if (conn->local_audio_port == 0) { - debug(1, "Connection %d: SETUP seems to specify a null audio port.", conn->connection_number); - goto error; - } - - char resphdr[256] = ""; - snprintf(resphdr, sizeof(resphdr), "RTP/AVP/" - "UDP;unicast;interleaved=0-1;mode=record;control_port=%d;" - "timing_port=%d;server_" - "port=%d", - conn->local_control_port, conn->local_timing_port, conn->local_audio_port); - - msg_add_header(resp, "Transport", resphdr); - - msg_add_header(resp, "Session", "1"); - - resp->respcode = 200; - debug(1, "Connection %d: SETUP with UDP ports Control: %d, Timing: %d and Audio: %d.", - conn->connection_number, conn->local_control_port, conn->local_timing_port, - conn->local_audio_port); - return; - -error: - warn("Connection %d: SETUP -- Error in setup request -- unlocking play lock.", - conn->connection_number); - debug_mutex_lock(&playing_conn_lock, 1000000, 3); - playing_conn = NULL; // this connection definitely doesn't have the play lock - debug_mutex_unlock(&playing_conn_lock, 3); - resp->respcode = 451; // invalid arguments } /* @@ -2079,7 +2105,7 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { debug(3, "Connection %d: Checking play lock.", conn->connection_number); debug_mutex_lock(&playing_conn_lock, 1000000, 3); // get it if (playing_conn == conn) { - debug(1, "Connection %d: Unlocking play lock.", conn->connection_number); + debug(2, "Connection %d: Unlocking play lock.", conn->connection_number); playing_conn = NULL; } debug_mutex_unlock(&playing_conn_lock, 3); From 1240c6efab7488ce6f7ced6e5be4025f3032d6fe Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 29 Nov 2018 15:32:06 +0000 Subject: [PATCH 242/569] Adds checks for posession of the player in order to handle flush, teardown requests. --- rtsp.c | 78 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/rtsp.c b/rtsp.c index 637938ef..b643c7fe 100644 --- a/rtsp.c +++ b/rtsp.c @@ -752,11 +752,9 @@ int msg_write_response(int fd, rtsp_message *resp) { void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(2, "Connection %d: RECORD", conn->connection_number); - if (have_player(conn)) { - if (conn->player_thread) - warn("Duplicate RECORD message -- ignored"); + warn("Connection %d: RECORD: Duplicate RECORD message -- ignored", conn->connection_number); else player_play(conn); // the thread better be 0 @@ -809,59 +807,62 @@ void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message * void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, rtsp_message *resp) { debug(2, "Connection %d: TEARDOWN", conn->connection_number); - // if (!rtsp_playing()) - // debug(1, "This RTSP connection thread (%d) doesn't think it's playing, but " - // "it's sending a response to teardown anyway",conn->connection_number); - resp->respcode = 200; - msg_add_header(resp, "Connection", "close"); - - debug(3, + if (have_player(conn)) { + resp->respcode = 200; + msg_add_header(resp, "Connection", "close"); + debug( + 3, "TEARDOWN: synchronously terminating the player thread of RTSP conversation thread %d (2).", conn->connection_number); - player_stop(conn); - debug(3, "TEARDOWN: successful termination of playing thread of RTSP conversation thread %d.", - conn->connection_number); + player_stop(conn); + debug(3, "TEARDOWN: successful termination of playing thread of RTSP conversation thread %d.", + conn->connection_number); + } else { + warn("Connection %d TEARDOWN received without having the player (no ANNOUNCE?)", + conn->connection_number); + resp->respcode = 451; + } } void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(3, "Connection %d: FLUSH", conn->connection_number); - // if (!rtsp_playing()) - // debug(1, "This RTSP conversation thread (%d) doesn't think it's playing, but " - // "it's sending a response to flush anyway",conn->connection_number); - char *p = NULL; - uint32_t rtptime = 0; - char *hdr = msg_get_header(req, "RTP-Info"); + if (have_player(conn)) { + char *p = NULL; + uint32_t rtptime = 0; + char *hdr = msg_get_header(req, "RTP-Info"); - if (hdr) { - // debug(1,"FLUSH message received: \"%s\".",hdr); - // get the rtp timestamp - p = strstr(hdr, "rtptime="); - if (p) { - p = strchr(p, '='); - if (p) - rtptime = uatoi(p + 1); // unsigned integer -- up to 2^32-1 + if (hdr) { + // debug(1,"FLUSH message received: \"%s\".",hdr); + // get the rtp timestamp + p = strstr(hdr, "rtptime="); + if (p) { + p = strchr(p, '='); + if (p) + rtptime = uatoi(p + 1); // unsigned integer -- up to 2^32-1 + } } - } // debug(1,"RTSP Flush Requested: %u.",rtptime); #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); + 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; + player_flush(rtptime, conn); // will not crash even it there is no player thread. + resp->respcode = 200; + + } else { + warn("Connection %d FLUSH received without having the player (no ANNOUNCE?)", + conn->connection_number); + resp->respcode = 451; + } } void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(3, "Connection %d: SETUP", conn->connection_number); - resp->respcode = 451; // invalid arguments -- expect them - if (have_player(conn)) { - uint16_t cport, tport; - char *ar = msg_get_header(req, "Active-Remote"); if (ar) { debug(2, "Connection %d: SETUP -- Active-Remote string seen: \"%s\".", @@ -959,6 +960,7 @@ void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(1, "Connection %d: SETUP doesn't contain a Transport header.", conn->connection_number); } if (resp->respcode != 200) { + debug(1, "Connection %d: SETUP error -- releasing the player lock.", conn->connection_number); debug_mutex_lock(&playing_conn_lock, 1000000, 3); playing_conn = NULL; // this connection definitely doesn't have the play lock debug_mutex_unlock(&playing_conn_lock, 3); From 4d4272c8baf4e2da8ca351ecd54a70999b362498 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 29 Nov 2018 15:36:18 +0000 Subject: [PATCH 243/569] Improve player watchdog, improve clearing down of ports, improve handling of write- and read-erroes on the RTSP channel. Improve code for holding and releasing the player. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 786ed9c4..5c2874e1 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.3d29], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d30], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 4a5b0009a4b03b8db3b3fab44e3a80ffc2123c1e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 29 Nov 2018 17:13:12 +0000 Subject: [PATCH 244/569] Quieten a debug message --- rtp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtp.c b/rtp.c index 61c69dd2..b8b1ea5b 100644 --- a/rtp.c +++ b/rtp.c @@ -1110,7 +1110,7 @@ int sanitised_source_rate_information(uint32_t *frames, uint64_t *time, rtsp_con double calculated_frame_rate = ((1.0 * local_frames) / local_time) * one_fp; if (((calculated_frame_rate / conn->input_rate) > 1.002) || ((calculated_frame_rate / conn->input_rate) < 0.998)) { - debug(1, "input frame rate out of bounds at %.2f fps.", calculated_frame_rate); + debug(2, "input frame rate out of bounds at %.2f fps.", calculated_frame_rate); result = 1; } else { *frames = local_frames; From f2a3ba00cc9b18a6cf7f73f9eea9972a26d86395 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 29 Nov 2018 17:14:40 +0000 Subject: [PATCH 245/569] Move cleanup push to start of RTSP initialisation. Take more care at startup and closedown to make stuff uncancellable. --- rtsp.c | 48 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/rtsp.c b/rtsp.c index b643c7fe..f3831683 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1591,7 +1591,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_m } static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - debug(2, "Connection %d: ANNOUNCE", conn->connection_number); + debug(3, "Connection %d: ANNOUNCE", conn->connection_number); int have_the_player = 0; int should_wait = 0; // this will be true if you're trying to break in to the current session @@ -1608,8 +1608,8 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag have_the_player = 1; warn("Duplicate ANNOUNCE, by the look of it!"); } else if (playing_conn->stop) { - debug(1, "Connection %d: ANNOUNCE: already shutting down; waiting for it...", - playing_conn->connection_number); + debug(1, "Connection %d ANNOUNCE is waiting for connection %d to shut down.", + conn->connection_number, playing_conn->connection_number); should_wait = 1; } else if (config.allow_session_interruption == 1) { debug(2, "Connection %d: ANNOUNCE: asking playing connection %d to shut down.", @@ -2062,10 +2062,14 @@ authenticate: void rtsp_conversation_thread_cleanup_function(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; - // debug(1, "Connection %d: rtsp_conversation_thread_func_cleanup_function called.", - // conn->connection_number); + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); + + debug(3, "Connection %d: rtsp_conversation_thread_func_cleanup_function called.", + conn->connection_number); if (conn->player_thread) player_stop(conn); + if (conn->fd > 0) { debug(3, "Connection %d: closing fd %d.", conn->connection_number, conn->fd); close(conn->fd); @@ -2096,11 +2100,8 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { debug(2, "Cancel watchdog thread."); pthread_cancel(conn->player_watchdog_thread); - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); debug(2, "Join watchdog thread."); pthread_join(conn->player_watchdog_thread, NULL); - pthread_setcancelstate(oldState, NULL); debug(2, "Delete watchdog mutex."); pthread_mutex_destroy(&conn->watchdog_mutex); @@ -2114,6 +2115,7 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { debug(2, "Connection %d: RTSP thread terminated.", conn->connection_number); conn->running = 0; + pthread_setcancelstate(oldState, NULL); } void msg_cleanup_function(void *arg) { @@ -2148,6 +2150,9 @@ static void *rtsp_conversation_thread_func(void *pconn) { die("Connection %d: error %d initialising flow control condition variable.", conn->connection_number, rc); + // nothing before this is cancellable + pthread_cleanup_push(rtsp_conversation_thread_cleanup_function, (void *)conn); + rtp_initialise(conn); char *hdr = NULL; @@ -2156,7 +2161,6 @@ static void *rtsp_conversation_thread_func(void *pconn) { int rtsp_read_request_attempt_count = 1; // 1 means exit immediately rtsp_message *req, *resp; - pthread_cleanup_push(rtsp_conversation_thread_cleanup_function, (void *)conn); while (conn->stop == 0) { int debug_level = 3; // for printing the request and response reply = rtsp_read_request(conn, &req); @@ -2232,6 +2236,12 @@ static void *rtsp_conversation_thread_func(void *pconn) { 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" + so_linger.l_linger = 0; + err = setsockopt(conn->fd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger); + if (err) + debug(1, "Could not set the RTSP socket to abort due to a write error on closing."); conn->stop = 1; // if (debuglev >= 1) // debuglev = 3; // see what happens next @@ -2249,7 +2259,14 @@ static void *rtsp_conversation_thread_func(void *pconn) { rtsp_read_request_attempt_count--; if (rtsp_read_request_attempt_count == 0) { tstop = 1; - // if ((reply == rtsp_read_request_response_read_error) && (debuglev != 0)) + if (reply == rtsp_read_request_response_read_error) { + struct linger so_linger; + so_linger.l_onoff = 1; // "true" + so_linger.l_linger = 0; + int err = setsockopt(conn->fd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger); + if (err) + debug(1, "Could not set the RTSP socket to abort due to a read error on closing."); + } // debuglev = 3; // see what happens next } else { if (reply == rtsp_read_request_response_channel_closed) @@ -2298,15 +2315,20 @@ static const char *format_address(struct sockaddr *fsa) { */ void rtsp_listen_loop_cleanup_handler(__attribute__((unused)) void *arg) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); debug(1, "rtsp_listen_loop_cleanup_handler called."); cancel_all_RTSP_threads(); int *sockfd = (int *)arg; mdns_unregister(); if (sockfd) free(sockfd); + pthread_setcancelstate(oldState, NULL); } void rtsp_listen_loop(void) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); struct addrinfo hints, *info, *p; char portstr[6]; int *sockfd = NULL; @@ -2405,9 +2427,7 @@ void rtsp_listen_loop(void) { mdns_register(); - // printf("Listening for connections."); - // shairport_startup_complete(); - + pthread_setcancelstate(oldState, NULL); int acceptfd; struct timeval tv; pthread_cleanup_push(rtsp_listen_loop_cleanup_handler, (void *)sockfd); @@ -2509,6 +2529,6 @@ void rtsp_listen_loop(void) { } } while (1); - pthread_cleanup_pop(0); + pthread_cleanup_pop(1); // should never happen debug(1, "Oops -- fell out of the RTSP select loop"); } From 0eac2b9cb3664235f8fb30481d48983553a14f77 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 29 Nov 2018 22:03:49 +0000 Subject: [PATCH 246/569] Move closing of ports to the player thread cleanup. They can be bound without the UDP threads ever having been called, so they must be closed when the player thread exits. --- player.c | 8 ++++++++ rtp.c | 33 ++++++--------------------------- 2 files changed, 14 insertions(+), 27 deletions(-) diff --git a/player.c b/player.c index 57d7527b..9c0482c7 100644 --- a/player.c +++ b/player.c @@ -1448,6 +1448,14 @@ void player_thread_cleanup_handler(void *arg) { pthread_join(conn->rtp_audio_thread, NULL); debug(2, "Audio thread terminated."); + debug(2, "Closing timing, control and audio sockets..."); + if (conn->control_socket) + close(conn->control_socket); + if (conn->timing_socket) + close(conn->timing_socket); + if (conn->audio_socket) + close(conn->audio_socket); + if (conn->outbuf) { free(conn->outbuf); conn->outbuf = NULL; diff --git a/rtp.c b/rtp.c index b8b1ea5b..0de094c6 100644 --- a/rtp.c +++ b/rtp.c @@ -97,16 +97,8 @@ uint64_t local_to_remote_time_difference_now(rtsp_conn_info *conn) { return conn->local_to_remote_time_difference + (uint64_t)(drift * (uint64_t)0x100000000); } -void rtp_audio_receiver_cleanup_handler(void *arg) { - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); - debug(3, "Audio Receiver Cleanup."); - rtsp_conn_info *conn = (rtsp_conn_info *)arg; - debug(3, "Close Audio Socket."); - close(conn->audio_socket); - debug(3, "Audio Receiver Cleanup Successful."); - usleep(20000); // microseconds - pthread_setcancelstate(oldState, NULL); +void rtp_audio_receiver_cleanup_handler(__attribute__((unused)) void *arg) { + debug(3, "Audio Receiver Cleanup Done."); } void *rtp_audio_receiver(void *arg) { @@ -240,18 +232,8 @@ void *rtp_audio_receiver(void *arg) { pthread_exit(NULL); } -void rtp_control_handler_cleanup_handler(void *arg) { - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); - debug(3, "Control Receiver Cleanup."); - rtsp_conn_info *conn = (rtsp_conn_info *)arg; - debug(3, "Shut Down Control Socket."); - shutdown(conn->control_socket, SHUT_RDWR); - debug(3, "Close Control Socket."); - close(conn->control_socket); - debug(3, "Control Receiver Cleanup Successful."); - usleep(20000); // microseconds - pthread_setcancelstate(oldState, NULL); +void rtp_control_handler_cleanup_handler(__attribute__((unused)) void *arg) { + debug(3, "Control Receiver Cleanup Done."); } void *rtp_control_receiver(void *arg) { @@ -564,18 +546,15 @@ void *rtp_timing_sender(void *arg) { } void rtp_timing_receiver_cleanup_handler(void *arg) { - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); debug(3, "Timing Receiver Cleanup."); rtsp_conn_info *conn = (rtsp_conn_info *)arg; debug(3, "Cancel Timing Requester."); pthread_cancel(conn->timer_requester); + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); debug(3, "Join Timing Requester."); pthread_join(conn->timer_requester, NULL); - debug(3, "Close Timing Socket."); - close(conn->timing_socket); debug(3, "Timing Receiver Cleanup Successful."); - usleep(20000); // microseconds pthread_setcancelstate(oldState, NULL); } From a5c65ceced5671f3134ef378cd3202e0660eae17 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 30 Nov 2018 14:55:04 +0000 Subject: [PATCH 247/569] Move UDP port cloure to the RTSP thread, don't allow calls to mdns_* to be cancelled. Remove usage of (p)select in rtsp thread handling. --- player.c | 13 ++++--------- rtp.c | 16 +++++++++------- rtsp.c | 31 +++++++++++++++++++++++-------- 3 files changed, 36 insertions(+), 24 deletions(-) diff --git a/player.c b/player.c index 9c0482c7..cd9a9b8d 100644 --- a/player.c +++ b/player.c @@ -1448,14 +1448,6 @@ void player_thread_cleanup_handler(void *arg) { pthread_join(conn->rtp_audio_thread, NULL); debug(2, "Audio thread terminated."); - debug(2, "Closing timing, control and audio sockets..."); - if (conn->control_socket) - close(conn->control_socket); - if (conn->timing_socket) - close(conn->timing_socket); - if (conn->audio_socket) - close(conn->audio_socket); - if (conn->outbuf) { free(conn->outbuf); conn->outbuf = NULL; @@ -1736,7 +1728,9 @@ void *player_thread_func(void *arg) { pthread_cleanup_push(player_thread_cleanup_handler, arg); // undo what's been done so far -// stop looking elsewhere for DACP stuff + // stop looking elsewhere for DACP stuff + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); #ifdef CONFIG_DACP_CLIENT // debug(1, "Set dacp server info"); @@ -1753,6 +1747,7 @@ void *player_thread_func(void *arg) { // almost certainly, this has pthread cancellation points in it -- beware conn->dapo_private_storage = mdns_dacp_monitor(conn->dacp_id); #endif + pthread_setcancelstate(oldState, NULL); // set the default volume to whaterver it was before, as stored in the config airplay_volume debug(2, "Set initial volume to %f.", config.airplay_volume); diff --git a/rtp.c b/rtp.c index 0de094c6..1da6097f 100644 --- a/rtp.c +++ b/rtp.c @@ -847,13 +847,15 @@ static uint16_t bind_port(int ip_family, const char *self_ip_address, uint32_t s if (local_socket == -1) die("Could not allocate a socket."); - int val = 1; - ret = setsockopt(local_socket, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); - if (ret < 0) { - char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(1, "Error %d: \"%s\". Couldn't set SO_REUSEADDR"); - } + /* + int val = 1; + ret = setsockopt(local_socket, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); + if (ret < 0) { + char errorstring[1024]; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + debug(1, "Error %d: \"%s\". Couldn't set SO_REUSEADDR"); + } + */ SOCKADDR myaddr; int tryCount = 0; diff --git a/rtsp.c b/rtsp.c index f3831683..0b3625bd 100644 --- a/rtsp.c +++ b/rtsp.c @@ -555,18 +555,21 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes int msg_size = -1; while (msg_size < 0) { - fd_set readfds; - FD_ZERO(&readfds); - FD_SET(conn->fd, &readfds); - do { - memory_barrier(); - } while (conn->stop == 0 && - pselect(conn->fd + 1, &readfds, NULL, NULL, NULL, &pselect_sigset) <= 0); + /* +fd_set readfds; +FD_ZERO(&readfds); +FD_SET(conn->fd, &readfds); +do { + memory_barrier(); +} while (conn->stop == 0 && + pselect(conn->fd + 1, &readfds, NULL, NULL, NULL, &pselect_sigset) <= 0); +*/ if (conn->stop != 0) { debug(3, "RTSP conversation thread %d shutdown requested.", conn->connection_number); reply = rtsp_read_request_response_immediate_shutdown_requested; goto shutdown; } + nread = read(conn->fd, buf + inbuf, buflen - inbuf); if (nread == 0) { @@ -640,6 +643,8 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes warning_message_sent = 1; } } + + /* fd_set readfds; FD_ZERO(&readfds); FD_SET(conn->fd, &readfds); @@ -647,6 +652,8 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes memory_barrier(); } while (conn->stop == 0 && pselect(conn->fd + 1, &readfds, NULL, NULL, NULL, &pselect_sigset) <= 0); + */ + if (conn->stop != 0) { debug(1, "RTSP shutdown requested."); reply = rtsp_read_request_response_immediate_shutdown_requested; @@ -2070,6 +2077,14 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { if (conn->player_thread) player_stop(conn); + debug(3, "Closing timing, control and audio sockets..."); + if (conn->control_socket) + close(conn->control_socket); + if (conn->timing_socket) + close(conn->timing_socket); + if (conn->audio_socket) + close(conn->audio_socket); + if (conn->fd > 0) { debug(3, "Connection %d: closing fd %d.", conn->connection_number, conn->fd); close(conn->fd); @@ -2517,7 +2532,7 @@ void rtsp_listen_loop(void) { // conn->thread = rtsp_conversation_thread; // conn->stop = 0; // record's memory has been zeroed // conn->authorized = 0; // record's memory has been zeroed - fcntl(conn->fd, F_SETFL, O_NONBLOCK); + // fcntl(conn->fd, F_SETFL, O_NONBLOCK); ret = pthread_create(&conn->thread, NULL, rtsp_conversation_thread_func, conn); // also acts as a memory barrier From 2737222b0b5bb363e837821e89e47760655dae56 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 1 Dec 2018 16:52:23 +0000 Subject: [PATCH 248/569] Fix a non-initialisation of parameters if no h/w mixer used. Add a few diagnostics, tidy up initial diagnostic message, add verbosity level and whether mixer used. --- audio_alsa.c | 10 +++++----- player.c | 12 +++++++++--- rtsp.c | 1 + shairport.c | 13 +++++++------ 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 00c33900..372f84d1 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -70,7 +70,7 @@ audio_output audio_alsa = { .rate_info = &get_rate_information, .mute = NULL, // a function will be provided if it can, and is allowed to, do hardware mute .volume = NULL, // a function will be provided if it can do hardware volume - .parameters = ¶meters}; + .parameters = NULL}; // a function will be provided if it can do hardware volume static pthread_mutex_t alsa_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -402,7 +402,7 @@ static int init(int argc, char **argv) { warn("Invalid audio argument: \"%s\" -- ignored", argv[optind]); } - debug(1, "alsa output device name is \"%s\".", alsa_out_dev); + debug(1, "alsa: output device name is \"%s\".", alsa_out_dev); if (hardware_mixer) { @@ -453,8 +453,8 @@ static int init(int argc, char **argv) { snd_ctl_elem_id_set_name(elem_id, alsa_mix_ctrl); if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) { - debug(1, "Volume control \"%s\" has dB volume from %f to %f.", alsa_mix_ctrl, - (1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0); + debug(1, "alsa: hardware mixer \"%s\" selected, with dB volume from %f to %f.", + alsa_mix_ctrl, (1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0); has_softvol = 1; audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff @@ -492,7 +492,7 @@ static int init(int argc, char **argv) { debug_mutex_unlock(&alsa_mutex, 3); // release the mutex pthread_cleanup_pop(0); } else { - // debug(1, "Has no mixer and thus no hardware mute."); + debug(1, "alsa: no hardware mixer selected."); } alsa_mix_handle = NULL; diff --git a/player.c b/player.c index cd9a9b8d..429a218d 100644 --- a/player.c +++ b/player.c @@ -1749,7 +1749,7 @@ void *player_thread_func(void *arg) { #endif pthread_setcancelstate(oldState, NULL); - // set the default volume to whaterver it was before, as stored in the config airplay_volume + // set the default volume to whatever it was before, as stored in the config airplay_volume debug(2, "Set initial volume to %f.", config.airplay_volume); player_volume(config.airplay_volume, conn); // will contain a cancellation point if asked to wait @@ -2778,8 +2778,14 @@ int player_stop(rtsp_conn_info *conn) { debug(2, "player_thread cancel..."); pthread_cancel(*conn->player_thread); debug(2, "player_thread join..."); - pthread_join(*conn->player_thread, NULL); - debug(2, "player_thread joined."); + if (pthread_join(*conn->player_thread, NULL) == -1) { + char errorstring[1024]; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + debug(1, "Connection %d: error %d joining player thread: \"%s\".", conn->connection_number, + errno, (char *)errorstring); + } else { + debug(2, "player_thread joined."); + } free(conn->player_thread); conn->player_thread = NULL; #ifdef CONFIG_METADATA diff --git a/rtsp.c b/rtsp.c index 0b3625bd..a5079616 100644 --- a/rtsp.c +++ b/rtsp.c @@ -170,6 +170,7 @@ int send_ssnc_metadata(uint32_t code, char *data, uint32_t length, int block) { } void pc_queue_cleanup_handler(void *arg) { + // debug(1, "pc_queue_cleanup_handler called."); pc_queue *the_queue = (pc_queue *)arg; int rc = pthread_mutex_unlock(&the_queue->pc_queue_lock); if (rc) diff --git a/shairport.c b/shairport.c index 506f9feb..11f085b1 100644 --- a/shairport.c +++ b/shairport.c @@ -1483,13 +1483,18 @@ int main(int argc, char **argv) { char *version_dbs = get_version_string(); if (version_dbs) { - debug(1, "Version: \"%s\"", version_dbs); + debug(1, "software version: \"%s\"", version_dbs); free(version_dbs); } else { - debug(1, "Can't print the version information!"); + debug(1, "can't print the version information!"); } /* Print out options */ + debug(1, "log verbosity is %d.", debuglev); + debug(1, "disable resend requests is %s.", config.disable_resend_requests ? "on" : "off"); + debug(1, "diagnostic_drop_packet_fraction is %f. A value of 0.0 means no packets will be dropped " + "deliberately.", + config.diagnostic_drop_packet_fraction); debug(1, "statistics_requester status is %d.", config.statistics_requested); debug(1, "daemon status is %d.", config.daemonise); debug(1, "deamon pid file path is \"%s\".", pid_file_proc()); @@ -1563,10 +1568,6 @@ int main(int argc, char **argv) { #endif debug(1, "loudness is %d.", config.loudness); debug(1, "loudness reference level is %f", config.loudness_reference_volume_db); - debug(1, "disable resend requests is %s.", config.disable_resend_requests ? "on" : "off"); - debug(1, "diagnostic_drop_packet_fraction is %f. A value of 0.0 means no packets will be dropped " - "deliberately.", - config.diagnostic_drop_packet_fraction); uint8_t ap_md5[16]; From 3644300db15292ad4cee3d2d6a85e2fb2e93259c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 1 Dec 2018 17:13:20 +0000 Subject: [PATCH 249/569] Fix a non-initialisation of parameters if no h/w mixer used. Add a few diagnostics, tidy up initial diagnostic message, add verbosity level and whether mixer used. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 5c2874e1..ca23c4e4 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.3d30], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d31], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 8c9b1590112e86054c9b68cd9643860ef85c24f0 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 2 Dec 2018 18:13:20 +0000 Subject: [PATCH 250/569] Stop using an unsigned integer for time when we want to watch if it goes negative. Only release the playing_conn if we already own it! --- common.c | 4 ++-- rtsp.c | 14 ++++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/common.c b/common.c index 8cbcd0d4..b2c920ad 100644 --- a/common.c +++ b/common.c @@ -1142,10 +1142,10 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, // this is not pthread_cancellation safe because is contains a cancellation point int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); - useconds_t time_to_wait = dally_time; + int time_to_wait = dally_time; int r = pthread_mutex_trylock(mutex); while ((r == EBUSY) && (time_to_wait > 0)) { - useconds_t st = time_to_wait; + int st = time_to_wait; if (st > 1000) st = 1000; sps_nanosleep(0, st * 1000); // this contains a cancellation point diff --git a/rtsp.c b/rtsp.c index a5079616..11266c7d 100644 --- a/rtsp.c +++ b/rtsp.c @@ -257,7 +257,7 @@ int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) { int have_player(rtsp_conn_info *conn) { int response = 0; debug_mutex_lock(&playing_conn_lock, 1000000, 3); - if (playing_conn == conn) // this connection definitely doesn't have the play lock + if (playing_conn == conn) // this connection definitely has the play lock response = 1; debug_mutex_unlock(&playing_conn_lock, 3); return response; @@ -970,7 +970,8 @@ void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { if (resp->respcode != 200) { debug(1, "Connection %d: SETUP error -- releasing the player lock.", conn->connection_number); debug_mutex_lock(&playing_conn_lock, 1000000, 3); - playing_conn = NULL; // this connection definitely doesn't have the play lock + if (playing_conn == conn) // if we have the player + playing_conn = NULL; // let it go debug_mutex_unlock(&playing_conn_lock, 3); } @@ -1630,7 +1631,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag debug_mutex_unlock(&playing_conn_lock, 3); if (should_wait) { - useconds_t time_remaining = 3000000; + int time_remaining = 3000000; // must be signed, as it could go negative... while ((time_remaining > 0) && (have_the_player == 0)) { debug_mutex_lock(&playing_conn_lock, 1000000, 3); // get it @@ -1829,7 +1830,8 @@ out: debug(1, "Connection %d: Error in handling ANNOUNCE. Unlocking the play lock.", conn->connection_number); debug_mutex_lock(&playing_conn_lock, 1000000, 3); // get it - playing_conn = NULL; + if (playing_conn == conn) // if we managed to acquire it + playing_conn = NULL; // let it go debug_mutex_unlock(&playing_conn_lock, 3); } } @@ -2123,9 +2125,9 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { debug(3, "Connection %d: Checking play lock.", conn->connection_number); debug_mutex_lock(&playing_conn_lock, 1000000, 3); // get it - if (playing_conn == conn) { + if (playing_conn == conn) { // if it's ours debug(2, "Connection %d: Unlocking play lock.", conn->connection_number); - playing_conn = NULL; + playing_conn = NULL; // let it go } debug_mutex_unlock(&playing_conn_lock, 3); From 038dee01ba14f2f721f5c6e44796a601ece01870 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 3 Dec 2018 10:33:29 +0000 Subject: [PATCH 251/569] Fix an error caused by using an unsigned integer as a signed number. It may have been causing lots of conversation threads to wait for the (blocked) current playing thread to exit. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index ca23c4e4..1fda52d5 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.3d31], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d32], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 4bb29bc198541d5dacf7cb1ba9a2fcd82f2afe86 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 3 Dec 2018 16:52:32 +0000 Subject: [PATCH 252/569] Make sure that shared watchdog count is correctly updated in the rtsp_conn_info structure! --- player.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player.h b/player.h index deb787c6..e2c6a4c8 100644 --- a/player.h +++ b/player.h @@ -87,7 +87,7 @@ typedef struct { time_t playstart; pthread_t thread, timer_requester, rtp_audio_thread, rtp_control_thread, rtp_timing_thread, player_watchdog_thread; - int64_t watchdog_bark_time; + volatile int64_t watchdog_bark_time; // pthread_t *ptp; // buffers to delete on exit From 60315b63c90944fb4d9da78e5608cfb92d3f01b7 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 3 Dec 2018 17:00:12 +0000 Subject: [PATCH 253/569] Try to ensure that any alsa command sequences are uncancellable. Leave a little time afterwards where possible. --- audio_alsa.c | 7 +++++-- rtsp.c | 16 ++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 98182989..c233a67b 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -506,7 +506,8 @@ static int init(int argc, char **argv) { } else { debug(1, "alsa: no hardware mixer selected."); } - + + usleep(200000); // see if it makes a difference alsa_mix_handle = NULL; return response; } @@ -883,7 +884,7 @@ int actual_open_alsa_device(void) { break; } } - + usleep(200000); // see if it makes a difference return 0; } @@ -910,6 +911,7 @@ static void start(int i_sample_rate, int i_sample_format) { frame_index = 0; measurement_data_is_valid = 0; + usleep(200000); // see if it makes a difference } //assuming pthread cancellation is disabled @@ -1163,6 +1165,7 @@ static void flush(void) { measurement_data_is_valid = 0; alsa_handle = NULL; } + usleep(200000); // see if it makes a difference debug_mutex_unlock(&alsa_mutex, 3); pthread_cleanup_pop(0); // release the mutex pthread_setcancelstate(oldState, NULL); diff --git a/rtsp.c b/rtsp.c index 11266c7d..81b380f0 100644 --- a/rtsp.c +++ b/rtsp.c @@ -265,7 +265,7 @@ int have_player(rtsp_conn_info *conn) { void player_watchdog_thread_cleanup_handler(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; - debug(2, "Connection %d: Watchdog Exit.", conn->connection_number); + debug(1, "Connection %d: Watchdog Exit.", conn->connection_number); } void *player_watchdog_thread_code(void *arg) { @@ -2116,13 +2116,6 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { if (rc) debug(1, "Connection %d: error %d destroying flush_mutex.", conn->connection_number, rc); - debug(2, "Cancel watchdog thread."); - pthread_cancel(conn->player_watchdog_thread); - debug(2, "Join watchdog thread."); - pthread_join(conn->player_watchdog_thread, NULL); - debug(2, "Delete watchdog mutex."); - pthread_mutex_destroy(&conn->watchdog_mutex); - debug(3, "Connection %d: Checking play lock.", conn->connection_number); debug_mutex_lock(&playing_conn_lock, 1000000, 3); // get it if (playing_conn == conn) { // if it's ours @@ -2131,6 +2124,13 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { } debug_mutex_unlock(&playing_conn_lock, 3); + debug(2, "Cancel watchdog thread."); + pthread_cancel(conn->player_watchdog_thread); + debug(2, "Join watchdog thread."); + pthread_join(conn->player_watchdog_thread, NULL); + debug(2, "Delete watchdog mutex."); + pthread_mutex_destroy(&conn->watchdog_mutex); + debug(2, "Connection %d: RTSP thread terminated.", conn->connection_number); conn->running = 0; pthread_setcancelstate(oldState, NULL); From 78da7c453c8048b9b560d6d997072b7810cec48a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 3 Dec 2018 18:01:54 +0000 Subject: [PATCH 254/569] Make the stop requester volatile, duh. --- player.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/player.h b/player.h index e2c6a4c8..7fea41fc 100644 --- a/player.h +++ b/player.h @@ -82,8 +82,8 @@ typedef struct { char *auth_nonce; // the session nonce, if needed stream_cfg stream; SOCKADDR remote, local; - int stop; - int running; + volatile int stop; + volatile int running; time_t playstart; pthread_t thread, timer_requester, rtp_audio_thread, rtp_control_thread, rtp_timing_thread, player_watchdog_thread; From 67d2a4b5d9455382519743dd747523ea3dc5f4c9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 3 Dec 2018 18:42:49 +0000 Subject: [PATCH 255/569] Add a 30 second timout on the read function on an RTSP thread. --- rtsp.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/rtsp.c b/rtsp.c index 81b380f0..8f0ec8c7 100644 --- a/rtsp.c +++ b/rtsp.c @@ -265,7 +265,7 @@ int have_player(rtsp_conn_info *conn) { void player_watchdog_thread_cleanup_handler(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; - debug(1, "Connection %d: Watchdog Exit.", conn->connection_number); + debug(2, "Connection %d: Watchdog Exit.", conn->connection_number); } void *player_watchdog_thread_code(void *arg) { @@ -2116,6 +2116,13 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { if (rc) debug(1, "Connection %d: error %d destroying flush_mutex.", conn->connection_number, rc); + debug(2, "Cancel watchdog thread."); + pthread_cancel(conn->player_watchdog_thread); + debug(2, "Join watchdog thread."); + pthread_join(conn->player_watchdog_thread, NULL); + debug(2, "Delete watchdog mutex."); + pthread_mutex_destroy(&conn->watchdog_mutex); + debug(3, "Connection %d: Checking play lock.", conn->connection_number); debug_mutex_lock(&playing_conn_lock, 1000000, 3); // get it if (playing_conn == conn) { // if it's ours @@ -2124,13 +2131,6 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { } debug_mutex_unlock(&playing_conn_lock, 3); - debug(2, "Cancel watchdog thread."); - pthread_cancel(conn->player_watchdog_thread); - debug(2, "Join watchdog thread."); - pthread_join(conn->player_watchdog_thread, NULL); - debug(2, "Delete watchdog mutex."); - pthread_mutex_destroy(&conn->watchdog_mutex); - debug(2, "Connection %d: RTSP thread terminated.", conn->connection_number); conn->running = 0; pthread_setcancelstate(oldState, NULL); @@ -2393,6 +2393,12 @@ void rtsp_listen_loop(void) { tv.tv_usec = 0; if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1) debug(1, "Error %d setting send timeout for rtsp writeback.", errno); + + tv.tv_sec = 30; // 30 seconds read timeout + tv.tv_usec = 0; + if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1) + debug(1, "Error %d setting send timeout for rtsp writeback.", errno); + #ifdef IPV6_V6ONLY // some systems don't support v4 access on v6 sockets, but some do. From 9ddef563ad8c7d5c603be5a31bc98ea101dccbec Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 4 Dec 2018 08:56:29 +0000 Subject: [PATCH 256/569] Shorten the delays to 100 ms --- audio_alsa.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index c233a67b..73f324a5 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -507,7 +507,7 @@ static int init(int argc, char **argv) { debug(1, "alsa: no hardware mixer selected."); } - usleep(200000); // see if it makes a difference + usleep(100000); // see if it makes a difference alsa_mix_handle = NULL; return response; } @@ -884,7 +884,7 @@ int actual_open_alsa_device(void) { break; } } - usleep(200000); // see if it makes a difference + usleep(100000); // see if it makes a difference return 0; } @@ -911,7 +911,7 @@ static void start(int i_sample_rate, int i_sample_format) { frame_index = 0; measurement_data_is_valid = 0; - usleep(200000); // see if it makes a difference + usleep(100000); // see if it makes a difference } //assuming pthread cancellation is disabled @@ -1165,7 +1165,7 @@ static void flush(void) { measurement_data_is_valid = 0; alsa_handle = NULL; } - usleep(200000); // see if it makes a difference + usleep(100000); // see if it makes a difference debug_mutex_unlock(&alsa_mutex, 3); pthread_cleanup_pop(0); // release the mutex pthread_setcancelstate(oldState, NULL); From 277d401d24438c563deef6aa870987c93ef58961 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 4 Dec 2018 17:25:15 +0000 Subject: [PATCH 257/569] Ensure watchdog always starts with the starting time of the thread. Add watchdog bark count. On the third bark, execute an unfixable error script if present. --- audio_alsa.c | 23 +++++++++-------------- common.c | 32 ++++++++++++++++++++++++++++++++ common.h | 3 ++- player.h | 5 +++-- rtsp.c | 32 +++++++++++++++++++++++--------- scripts/shairport-sync.conf | 1 + shairport.c | 5 +++++ 7 files changed, 75 insertions(+), 26 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 73f324a5..a93ec1ae 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -136,8 +136,7 @@ static void help(void) { void set_alsa_out_dev(char *dev) { alsa_out_dev = dev; } - -//assuming pthread cancellation is disabled +// assuming pthread cancellation is disabled int open_mixer() { int response = 0; if (hardware_mixer) { @@ -181,7 +180,7 @@ int open_mixer() { return response; } -//assuming pthread cancellation is disabled +// assuming pthread cancellation is disabled void close_mixer() { if (alsa_mix_handle) { snd_mixer_close(alsa_mix_handle); @@ -189,7 +188,7 @@ void close_mixer() { } } -//assuming pthread cancellation is disabled +// assuming pthread cancellation is disabled void do_snd_mixer_selem_set_playback_dB_all(snd_mixer_elem_t *mix_elem, double vol) { if (snd_mixer_selem_set_playback_dB_all(mix_elem, vol, 0) != 0) { debug(1, "Can't set playback volume accurately to %f dB.", vol); @@ -200,7 +199,7 @@ void do_snd_mixer_selem_set_playback_dB_all(snd_mixer_elem_t *mix_elem, double v } static int init(int argc, char **argv) { - // for debugging + // for debugging snd_output_stdio_attach(&output, stdout, 0); // debug(2,"audio_alsa init called."); @@ -506,8 +505,7 @@ static int init(int argc, char **argv) { } else { debug(1, "alsa: no hardware mixer selected."); } - - usleep(100000); // see if it makes a difference + alsa_mix_handle = NULL; return response; } @@ -517,7 +515,7 @@ static void deinit(void) { stop(); } -//assuming pthread cancellation is disabled +// assuming pthread cancellation is disabled int actual_open_alsa_device(void) { // the alsa mutex is already acquired when this is called const snd_pcm_uframes_t minimal_buffer_headroom = @@ -884,11 +882,10 @@ int actual_open_alsa_device(void) { break; } } - usleep(100000); // see if it makes a difference return 0; } -int open_alsa_device(void) { +int open_alsa_device(void) { int result; int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable @@ -911,10 +908,9 @@ static void start(int i_sample_rate, int i_sample_format) { frame_index = 0; measurement_data_is_valid = 0; - usleep(100000); // see if it makes a difference } -//assuming pthread cancellation is disabled +// assuming pthread cancellation is disabled int my_snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { int ret; snd_pcm_status_t *alsa_snd_pcm_status; @@ -1165,7 +1161,6 @@ static void flush(void) { measurement_data_is_valid = 0; alsa_handle = NULL; } - usleep(100000); // see if it makes a difference debug_mutex_unlock(&alsa_mutex, 3); pthread_cleanup_pop(0); // release the mutex pthread_setcancelstate(oldState, NULL); @@ -1303,6 +1298,6 @@ void do_mute(int mute_state_requested) { close_mixer(); } } - mute_request_pending = 0; + mute_request_pending = 0; pthread_setcancelstate(oldState, NULL); } diff --git a/common.c b/common.c index b2c920ad..264ef499 100644 --- a/common.c +++ b/common.c @@ -739,6 +739,38 @@ void command_start(void) { } } } +void command_execute(const char *command) { + // this has a cancellation point if waiting is enabled + if (command) { + /*Spawn a child to run the program.*/ + pid_t pid = fork(); + if (pid == 0) { /* child process */ + int argC; + char **argV; + if (poptParseArgvString(command, &argC, (const char ***)&argV) != + 0) // note that argV should be free()'d after use, but we expect this fork to exit + // eventually. + debug(1, "Can't decipher command arguments in \"%s\".", command); + else { + // debug(1,"Executing command %s",command); + execv(argV[0], argV); + warn("Execution of command \"%s\" failed to start", command); + debug(1, "Error executing command \"%s\".", command); + exit(127); /* only if execv fails */ + } + } else { + if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for + process to finish */ + pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */ + if (rc != pid) { + warn("Execution of command \"%s\" returned an error.", command); + debug(1, "Command \"%s\" finished with error %d", command, errno); + } + } + // debug(1,"Continue after on-unfixable command"); + } + } +} void command_stop(void) { // this has a cancellation point if waiting is enabled diff --git a/common.h b/common.h index ece21b1d..a9f4ab7d 100644 --- a/common.h +++ b/common.h @@ -148,7 +148,7 @@ typedef struct { int debugger_show_relative_time; // in the debug message, display the time since the last one int statistics_requested, use_negotiated_latencies; enum playback_mode_type playback_mode; - char *cmd_start, *cmd_stop, *cmd_set_volume; + char *cmd_start, *cmd_stop, *cmd_set_volume, *cmd_unfixable; int cmd_blocking, cmd_start_returns_output; double tolerance; // allow this much drift before attempting to correct it enum stuffing_type packet_stuffing; @@ -296,6 +296,7 @@ int config_set_lookup_bool(config_t *cfg, char *where, int *dst); void command_start(void); void command_stop(void); +void command_execute(const char *command); void command_set_volume(double volume); int mkpath(const char *path, mode_t mode); diff --git a/player.h b/player.h index 7fea41fc..9346ef2b 100644 --- a/player.h +++ b/player.h @@ -84,11 +84,12 @@ typedef struct { SOCKADDR remote, local; volatile int stop; volatile int running; + volatile uint64_t watchdog_bark_time; + volatile int watchdog_barks; // number of times the watchdog has timed out and done something + time_t playstart; pthread_t thread, timer_requester, rtp_audio_thread, rtp_control_thread, rtp_timing_thread, player_watchdog_thread; - volatile int64_t watchdog_bark_time; - // pthread_t *ptp; // buffers to delete on exit signed short *tbuf; diff --git a/rtsp.c b/rtsp.c index 8f0ec8c7..158b3263 100644 --- a/rtsp.c +++ b/rtsp.c @@ -283,11 +283,25 @@ void *player_watchdog_thread_code(void *arg) { uint64_t ct = config.timeout; // go from int to 64-bit int if (time_since_last_bark >= ct) { - 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); + conn->watchdog_barks++; + if (conn->watchdog_barks == 1) { + 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); + } else if (conn->watchdog_barks == 3) { + if (config.cmd_unfixable) { + warn("Connection %d: An unfixable error has been detected. Executing the " + "\"run_this_if_an_unfixable_error_is_detected\" command.", + conn->connection_number); + command_execute(config.cmd_unfixable); + } else { + warn("Connection %d: An unfixable error has been detected. \"No " + "run_this_if_an_unfixable_error_is_detected\" command provided -- nothing done.", + conn->connection_number); + } + } } } } @@ -2144,7 +2158,8 @@ void msg_cleanup_function(void *arg) { static void *rtsp_conversation_thread_func(void *pconn) { rtsp_conn_info *conn = pconn; - // create the watchdog mutex and start the watchdog thread; + // create the watchdog mutex, initialise the watchdog time and start the watchdog thread; + conn->watchdog_bark_time = get_absolute_time_in_fp(); pthread_mutex_init(&conn->watchdog_mutex, NULL); pthread_create(&conn->player_watchdog_thread, NULL, &player_watchdog_thread_code, (void *)conn); @@ -2393,12 +2408,11 @@ void rtsp_listen_loop(void) { tv.tv_usec = 0; if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1) debug(1, "Error %d setting send timeout for rtsp writeback.", errno); - + tv.tv_sec = 30; // 30 seconds read timeout tv.tv_usec = 0; - if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1) + if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1) debug(1, "Error %d setting send timeout for rtsp writeback.", errno); - #ifdef IPV6_V6ONLY // some systems don't support v4 access on v6 sockets, but some do. diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 55ac4cbf..97431d79 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -53,6 +53,7 @@ sessioncontrol = { // run_this_before_play_begins = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line // run_this_after_play_ends = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line +// run_this_if_an_unfixable_error_is_detected = "/full/path/to/application and args"; // if a problem occurs that can't be cleared. It might restart the whole device. // wait_for_completion = "no"; // set to "yes" to get Shairport Sync to wait until the "run_this..." applications have terminated before continuing // allow_session_interruption = "no"; // set to "yes" to allow another device to interrupt Shairport Sync while it's playing from an existing audio source // session_timeout = 120; // wait for this number of seconds after a source disappears before terminating the session and becoming available again. diff --git a/shairport.c b/shairport.c index 11f085b1..65c0bd68 100644 --- a/shairport.c +++ b/shairport.c @@ -721,6 +721,11 @@ int parse_options(int argc, char **argv) { config.cmd_stop = (char *)str; } + if (config_lookup_string(config.cfg, + "sessioncontrol.run_this_if_an_unfixable_error_is_detected", &str)) { + config.cmd_unfixable = (char *)str; + } + if (config_lookup_string(config.cfg, "sessioncontrol.wait_for_completion", &str)) { if (strcasecmp(str, "no") == 0) config.cmd_blocking = 0; From b071d3983407e398320778fb709b023dbbc4b915 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 4 Dec 2018 17:25:40 +0000 Subject: [PATCH 258/569] Ensure watchdog always starts with the starting time of the thread. Add watchdog bark count. On the third bark, execute an unfixable error script if present. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 1fda52d5..82dc13fb 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.3d32], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d33], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From a2e7cb34fb973d95e6f28763af12db7a1a2bb035 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 6 Dec 2018 14:46:57 +0000 Subject: [PATCH 259/569] The run_this_if_an_unfixable_error_is_detected script is definitely working and can restart the device. A sudoers entry is necessary to allow the shairport-sync user to 'sudo reboot --no-wall' without needing a password. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 82dc13fb..8f3248ab 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.3d33], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d34], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 53c17276dbde069e8413935dc986fe5ee6c4c3f5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 6 Dec 2018 15:39:19 +0000 Subject: [PATCH 260/569] Continue to use the ordinary snd_pcm_delay call. --- audio_alsa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_alsa.c b/audio_alsa.c index a93ec1ae..5705380e 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -961,7 +961,7 @@ int delay(long *the_delay) { snd_pcm_state_t dac_state = snd_pcm_state(alsa_handle); if (dac_state == SND_PCM_STATE_RUNNING) { *the_delay = 0; // just to see what happens - reply = my_snd_pcm_delay(alsa_handle, the_delay); + reply = snd_pcm_delay(alsa_handle, the_delay); if (reply != 0) { debug(1, "Error %d in delay(): \"%s\". Delay reported is %d frames.", reply, snd_strerror(reply), *the_delay); From e3dcae7514fb3806b3bc826cc3f5dc4948d10d01 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 7 Dec 2018 12:25:50 +0000 Subject: [PATCH 261/569] Update RELEASENOTES.md --- RELEASENOTES.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 927b7f65..4bdfd2ea 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,8 +1,7 @@ - Version 3.3d21 ==== **Enhancements** -* Use `/dev/urandom` rather than `/dev/random` as a source of some kind of randomness for the crtptographic nonce used in AirPlay password exchange, as `/dev/random` blocks occasionally. Please see [here](https://unix.stackexchange.com/questions/324209/when-to-use-dev-random-vs-dev-urandom) for a discussion of the merits of both. The effect of `/dev/random`'s blocking on Shairport Sync was to make the source of randomness somewhat less random. (By the way, you should never use an important password as an AirPlay password for Shairport Sync -- it is stored in Shairport Sync's configuration file in plain text.) +* Use `/dev/urandom` rather than `/dev/random` as a source of some kind of randomness for the cryptographic "nonce" used in AirPlay password exchange, as `/dev/random` blocks occasionally. Please see [here](https://unix.stackexchange.com/questions/324209/when-to-use-dev-random-vs-dev-urandom) for a discussion of the merits of both. The effect of `/dev/random`'s blocking on Shairport Sync was to make the source of randomness somewhat less random. (By the way, you should never use an important password as an AirPlay password for Shairport Sync -- it is stored in Shairport Sync's configuration file in plain text.) Version 3.3d20 ==== From 9d2571724b20f185b1695c4269e4e4f2c8465ccc Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 7 Dec 2018 12:28:20 +0000 Subject: [PATCH 262/569] Update RELEASENOTES.md --- RELEASENOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 4bdfd2ea..8bec1fde 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -476,6 +476,7 @@ Version 3.0.2 Version 3.0.1 ==== This update fixes one alarming and potentially very noisy bug and restores the identification of Shairport Sync as "ShairportSync" so that TuneBlade recognises it as an open source application. + **Bug Fixes** * Fixed a bug that was causing Shairport Sync to possibly make a very loud and alarming noise whenever an audio frame was missing. * In 2.8.6, a change was made to the way Shairport Sync identified itself, so that it could be recognised by TuneBlade as an open source application and treated preferentially. That change was inadventently lost in the transition from 2.8.6 to 3.0. Now it's restored. From f1dc4fece66f216b441dec1b69e9f69f2bccdfdc Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 7 Dec 2018 12:38:12 +0000 Subject: [PATCH 263/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 8bec1fde..5599a8b0 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -150,7 +150,7 @@ Version 3.2.d64 Version 3.2.d63 ==== **Enhancement** -* An [MQTT](https://en.wikipedia.org/wiki/MQTT) client interface is introduced, with support for metadata and for some remote control commands, thanks to the work [Till Zimmermann](https://github.com/tillz). MQTT is a fairly often used protocol in FOSS home-automation projects (as well as in commercial ones), and as Shairport Sync is often used in these setups, this adds client support for this protocol. +* An [MQTT](https://en.wikipedia.org/wiki/MQTT) client interface is introduced, with support for metadata and for some remote control commands, thanks to the work of [Till Zimmermann](https://github.com/tillz). MQTT is a fairly often used protocol in FOSS home-automation projects (as well as in commercial ones), and as Shairport Sync is often used in these setups, this adds client support for this protocol. Version 3.2d62 ==== From 50700b46a58cb08a527763905746175891d05dc1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 8 Dec 2018 12:58:01 +0000 Subject: [PATCH 264/569] Add timing to writing to the DAC --- audio_alsa.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/audio_alsa.c b/audio_alsa.c index 5705380e..ca869f1b 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1067,7 +1067,12 @@ static int play(void *buf, int samples) { debug(1, "empty buffer being passed to pcm_writei -- skipping it"); if ((samples != 0) && (buf != NULL)) { debug(3, "write %d frames.", samples); + uint64_t nominal_playing_time = samples; + nominal_playing_time = (nominal_playing_time << 32) / desired_sample_rate; + uint64_t time_before = get_absolute_time_in_fp(); err = alsa_pcm_write(alsa_handle, buf, samples); + uint64_t writing_time = get_absolute_time_in_fp() - time_before; + if (err < 0) { frame_index = 0; measurement_data_is_valid = 0; @@ -1079,6 +1084,11 @@ static int play(void *buf, int samples) { "play(): \"%s\".", err, samples, snd_strerror(err)); } + + if (writing_time > nominal_playing_time) { + debug(1,"Taking too long to write %d samples. Playing time: %8.2f us, writing time: %8.2f us.", samples,(1000000.0 * nominal_playing_time)/(uint64_t)0x100000000, (1000000.0 * writing_time)/(uint64_t)0x100000000); + } + if (frame_index == 0) { frames_sent_for_playing = samples; } else { From e5452e51725fbc28d6dbe4b112ddf94eb4c4f223 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 8 Dec 2018 14:12:33 +0000 Subject: [PATCH 265/569] Make the idle timeout on the RTSP link the same as the overall timeout -- 30 seconds is a bit short. Add checks to the alsa interface timing -- 15 ms to write a packet of frames, 1 ms to ask for the delay. --- audio_alsa.c | 62 ++++++++++++++++++++++++++++++++++++++++++++-------- common.h | 4 ++++ rtp.c | 4 ++-- rtsp.c | 19 ++++++++-------- 4 files changed, 69 insertions(+), 20 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index ca869f1b..acc4693e 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -216,6 +216,10 @@ static int init(int argc, char **argv) { config.audio_backend_latency_offset = 0; config.audio_backend_buffer_desired_length = 0.15; + config.alsa_maximum_write_time = 0.016; // 16 milliseconds -- if it takes longer, we need to know + config.alsa_maximum_interface_response_time = + 0.001; // one millisecond -- if it takes longer, we need to know + // get settings from settings file first, allow them to be overridden by // command line options @@ -223,6 +227,7 @@ static int init(int argc, char **argv) { parse_general_audio_options(); if (config.cfg != NULL) { + double dvalue; /* Get the Output Device Name. */ if (config_lookup_string(config.cfg, "alsa.output_device", &str)) { @@ -371,6 +376,28 @@ static int init(int argc, char **argv) { buffer_size_requested = value; } } + + /* Get the optional alsa_maximum_interface_response_time setting. */ + if (config_lookup_float(config.cfg, "alsa.maximum_interface_response_time", &dvalue)) { + if (dvalue < 0.0) { + warn("Invalid alsa maximum interface response time setting \"%f\". It " + "must be greater than 0. Default is \"%f\". No setting is made.", + dvalue, config.alsa_maximum_interface_response_time); + } else { + config.alsa_maximum_interface_response_time = dvalue; + } + } + + /* Get the optional alsa_maximum_write_time setting. */ + if (config_lookup_float(config.cfg, "alsa.maximum_write_time", &dvalue)) { + if (dvalue < 0.0) { + warn("Invalid alsa maximum write time setting \"%f\". It " + "must be greater than 0. Default is \"%f\". No setting is made.", + dvalue, config.alsa_maximum_write_time); + } else { + config.alsa_maximum_write_time = dvalue; + } + } } optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour @@ -956,12 +983,15 @@ int delay(long *the_delay) { } else { int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0); int derr; snd_pcm_state_t dac_state = snd_pcm_state(alsa_handle); if (dac_state == SND_PCM_STATE_RUNNING) { *the_delay = 0; // just to see what happens + uint64_t time_before = get_absolute_time_in_fp(); reply = snd_pcm_delay(alsa_handle, the_delay); + uint64_t response_time = get_absolute_time_in_fp() - time_before; + if (reply != 0) { debug(1, "Error %d in delay(): \"%s\". Delay reported is %d frames.", reply, snd_strerror(reply), *the_delay); @@ -979,6 +1009,15 @@ int delay(long *the_delay) { measurement_data_is_valid = 0; } } + uint64_t maximum_permitted_response_time = + (uint64_t)(config.alsa_maximum_interface_response_time * 1000000); // microseconds + maximum_permitted_response_time = (maximum_permitted_response_time << 32) / 1000000; + if (response_time > maximum_permitted_response_time) { + debug(3, "Maximum response time of %8.2f us exceeded reading delay. Response time was " + "%12.2f us.", + (1000000.0 * maximum_permitted_response_time) / (uint64_t)0x100000000, + (1000000.0 * response_time) / (uint64_t)0x100000000); + } } else { reply = -EIO; // shomething is wrong frame_index = 0; // we'll be starting over... @@ -1066,13 +1105,15 @@ static int play(void *buf, int samples) { if (samples == 0) debug(1, "empty buffer being passed to pcm_writei -- skipping it"); if ((samples != 0) && (buf != NULL)) { - debug(3, "write %d frames.", samples); - uint64_t nominal_playing_time = samples; - nominal_playing_time = (nominal_playing_time << 32) / desired_sample_rate; + // debug(3, "write %d frames.", samples); + + uint64_t maximum_permitted_writing_time = (config.alsa_maximum_write_time * 1000000000); + maximum_permitted_writing_time = (maximum_permitted_writing_time << 32) / 1000000000; + uint64_t time_before = get_absolute_time_in_fp(); err = alsa_pcm_write(alsa_handle, buf, samples); uint64_t writing_time = get_absolute_time_in_fp() - time_before; - + if (err < 0) { frame_index = 0; measurement_data_is_valid = 0; @@ -1084,11 +1125,14 @@ static int play(void *buf, int samples) { "play(): \"%s\".", err, samples, snd_strerror(err)); } - - if (writing_time > nominal_playing_time) { - debug(1,"Taking too long to write %d samples. Playing time: %8.2f us, writing time: %8.2f us.", samples,(1000000.0 * nominal_playing_time)/(uint64_t)0x100000000, (1000000.0 * writing_time)/(uint64_t)0x100000000); + + if (writing_time > maximum_permitted_writing_time) { + debug(3, "Taking too long to write %d samples. Playing time: %8.2f us, writing time: " + "%8.2f us.", + samples, (1000000.0 * maximum_permitted_writing_time) / (uint64_t)0x100000000, + (1000000.0 * writing_time) / (uint64_t)0x100000000); } - + if (frame_index == 0) { frames_sent_for_playing = samples; } else { diff --git a/common.h b/common.h index a9f4ab7d..abf19e71 100644 --- a/common.h +++ b/common.h @@ -184,6 +184,10 @@ typedef struct { int loudness; float loudness_reference_volume_db; int alsa_use_hardware_mute; + + double alsa_maximum_interface_response_time; + double alsa_maximum_write_time; + #if defined(CONFIG_DBUS_INTERFACE) enum dbus_session_type dbus_service_bus_type; #endif diff --git a/rtp.c b/rtp.c index ac1a43b6..07f19169 100644 --- a/rtp.c +++ b/rtp.c @@ -377,7 +377,7 @@ void *rtp_control_receiver(void *arg) { } } - debug_mutex_lock(&conn->reference_time_mutex, 1000, 1); + debug_mutex_lock(&conn->reference_time_mutex, 1000, 0); if (conn->packet_stream_established) { if (conn->initial_reference_time == 0) { @@ -414,7 +414,7 @@ void *rtp_control_receiver(void *arg) { // remote_time_of_sync - local_to_remote_time_difference_now(conn); conn->reference_timestamp = sync_rtp_timestamp; conn->latency_delayed_timestamp = rtp_timestamp_less_latency; - debug_mutex_unlock(&conn->reference_time_mutex, 3); + debug_mutex_unlock(&conn->reference_time_mutex, 0); conn->reference_to_previous_time_difference = remote_time_of_sync - old_remote_reference_time; diff --git a/rtsp.c b/rtsp.c index 158b3263..7921f70b 100644 --- a/rtsp.c +++ b/rtsp.c @@ -273,7 +273,7 @@ void *player_watchdog_thread_code(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; do { usleep(2000000); // check every two seconds - debug(3, "Connection %d: Check the thread is doing something...", conn->connection_number); + // debug(3, "Connection %d: Check the thread is doing something...", conn->connection_number); if ((config.dont_check_timeout == 0) && (config.timeout != 0)) { debug_mutex_lock(&conn->watchdog_mutex, 1000, 0); uint64_t last_watchdog_bark_time = conn->watchdog_bark_time; @@ -482,9 +482,9 @@ static void debug_print_msg_content(int level, rtsp_message *msg) { void msg_free(rtsp_message *msg) { if (msg) { - debug_mutex_lock(&reference_counter_lock, 1000, 3); + debug_mutex_lock(&reference_counter_lock, 1000, 0); msg->referenceCount--; - debug_mutex_unlock(&reference_counter_lock, 3); + debug_mutex_unlock(&reference_counter_lock, 0); if (msg->referenceCount == 0) { unsigned int i; for (i = 0; i < msg->nheaders; i++) { @@ -2151,7 +2151,7 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { } void msg_cleanup_function(void *arg) { - debug(3, "msg_cleanup_function called."); + // debug(3, "msg_cleanup_function called."); msg_free((rtsp_message *)arg); } @@ -2409,11 +2409,12 @@ void rtsp_listen_loop(void) { if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1) debug(1, "Error %d setting send timeout for rtsp writeback.", errno); - tv.tv_sec = 30; // 30 seconds read timeout - tv.tv_usec = 0; - if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1) - debug(1, "Error %d setting send timeout for rtsp writeback.", errno); - + if ((config.dont_check_timeout == 0) && (config.timeout != 0)) { + tv.tv_sec = config.timeout; // 120 seconds read timeout by default. + tv.tv_usec = 0; + if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1) + debug(1, "Error %d setting read timeout for rtsp connection.", errno); + } #ifdef IPV6_V6ONLY // some systems don't support v4 access on v6 sockets, but some do. // since we need to account for two sockets we might as well From 697d654b4144da78eef40341103471f78a936a58 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 8 Dec 2018 14:13:04 +0000 Subject: [PATCH 266/569] Make the idle timeout on the RTSP link the same as the overall timeout -- 30 seconds is a bit short. Add checks to the alsa interface timing -- 15 ms to write a packet of frames, 1 ms to ask for the delay. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 8f3248ab..7d209028 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.3d34], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d35], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From f20fa4f1c2f259929b096613e150dd05f84eaa7c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 8 Dec 2018 14:37:36 +0000 Subject: [PATCH 267/569] Fix RTSP timeout and add timing checks for interfacing with the ALSA subsystem. Add entries to the configuration file. --- scripts/shairport-sync.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 97431d79..689f55ea 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -74,6 +74,8 @@ alsa = // buffer_size = ; // Use this optional advanced setting to set the alsa buffer size near to this value // use_mmap_if_available = "yes"; // Use this optional advanced setting to control whether MMAP-based output is used to communicate with the DAC. Default is "yes" // use_hardware_mute_if_available = "no"; // Use this optional advanced setting to control whether the hardware in the DAC is used for muting. Default is "no", for compatibility with other audio players. +// maximum_interface_response_time = 0.001; // Use this optional advanced setting to control how long a response should be waited for on the interface between Shairport Sync and the ALSA subsystem. +// maximum_write_time = 0.015; // Use this optional advanced setting to control how long it should take to write audio data to the ALSA subsystem. }; // Parameters for the "sndio" audio back end. All are optional. From 12611b5521b60300ca0ec2752932759e940f791d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 8 Dec 2018 17:48:47 +0000 Subject: [PATCH 268/569] Fix small error in uncancellable sleep -- it was calling itself! --- common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.c b/common.c index 264ef499..7cadf231 100644 --- a/common.c +++ b/common.c @@ -98,7 +98,7 @@ int usleep_uncancellable(useconds_t usec) { int response; int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); - response = usleep_uncancellable(usec); + response = usleep(usec); pthread_setcancelstate(oldState, NULL); return response; } From 72079adaf4dcdf77338eb2485af9e8d90e4e9d37 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 8 Dec 2018 21:55:49 +0000 Subject: [PATCH 269/569] Initial shot at tidying up the avahi monitoring for DACP Id port information to make it survive abnormal temrination of play sessions. --- dacp.c | 17 +++--- mdns.c | 36 ++++++------ mdns.h | 10 ++-- mdns_avahi.c | 134 +++++++++++++++++++++++---------------------- mdns_dns_sd.c | 5 +- mdns_external.c | 10 ++-- mdns_tinysvcmdns.c | 5 +- player.c | 33 +++-------- 8 files changed, 122 insertions(+), 128 deletions(-) diff --git a/dacp.c b/dacp.c index 31497915..4c502cfa 100644 --- a/dacp.c +++ b/dacp.c @@ -60,6 +60,7 @@ typedef struct { pthread_t dacp_monitor_thread; dacp_server_record dacp_server; +void *mdns_dacp_monitor_private_storage_pointer; // HTTP Response data/funcs (See the tinyhttp example.cpp file for more on this.) struct HttpResponse { @@ -356,14 +357,10 @@ void set_dacp_server_information(rtsp_conn_info *conn) { dacp_server.connection_family = conn->connection_ip_family; dacp_server.scope_id = conn->self_scope_id; strncpy(dacp_server.ip_string, conn->client_ip_string, INET6_ADDRSTRLEN); - debug(2, "set_dacp_server_information set IP to \"%s\" and DACP id to \"%s\".", + debug(3, "set_dacp_server_information set IP to \"%s\" and DACP id to \"%s\".", dacp_server.ip_string, dacp_server.dacp_id); - if (dacp_server.port_monitor_private_storage) // if there's is a monitor already active... - mdns_dacp_dont_monitor(dacp_server.port_monitor_private_storage); // let it go. - dacp_server.port_monitor_private_storage = - mdns_dacp_monitor(dacp_server.dacp_id); // create a new one for us if a DACP-ID is provided, - // otherwise will return a NULL + mdns_dacp_monitor_set_id(dacp_server.dacp_id); metadata_hub_modify_prolog(); int ch = metadata_store.dacp_server_active != dacp_server.scan_enable; @@ -389,16 +386,17 @@ void set_dacp_server_information(rtsp_conn_info *conn) { } dacp_server.active_remote_id = conn->dacp_active_remote; // even if the dacp_id remains the same, // the active remote will change. - debug(2, "set_dacp_server_information set active-remote id to %" PRIu32 ".", + debug(3, "set_dacp_server_information set active-remote id to %" PRIu32 ".", dacp_server.active_remote_id); pthread_cond_signal(&dacp_server_information_cv); debug_mutex_unlock(&dacp_server_information_lock, 3); } void dacp_monitor_port_update_callback(char *dacp_id, uint16_t port) { - debug(2, "dacp_monitor_port_update_callback with Remote ID \"%s\" and port number %d.", dacp_id, - port); debug_mutex_lock(&dacp_server_information_lock, 500000, 2); + debug(3, "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) @@ -856,6 +854,7 @@ void dacp_monitor_start() { debug(1, "Error creating the DACP Server Information Lock Attr Destroy"); memset(&dacp_server, 0, sizeof(dacp_server_record)); + pthread_create(&dacp_monitor_thread, NULL, dacp_monitor_thread_code, NULL); } diff --git a/mdns.c b/mdns.c index d25335b1..de703975 100644 --- a/mdns.c +++ b/mdns.c @@ -96,34 +96,38 @@ void mdns_register(void) { if (config.mdns == NULL) die("Could not establish mDNS advertisement!"); + + mdns_dacp_monitor_start(); // create a dacp monitor thread } void mdns_unregister(void) { + mdns_dacp_monitor_stop(); if (config.mdns) { config.mdns->mdns_unregister(); } } -void *mdns_dacp_monitor(char *dacp_id) { - void *reply = NULL; - if ((dacp_id != NULL) && (*dacp_id != '\0')) { - if ((config.mdns) && (config.mdns->mdns_dacp_monitor)) { - reply = config.mdns->mdns_dacp_monitor(dacp_id); - if (reply == NULL) { - debug(1, "Error starting a DACP monitor."); - } - } else - debug(3, "Can't start a DACP monitor -- none registered."); - } - return reply; +void mdns_dacp_monitor_start(void) { + if ((config.mdns) && (config.mdns->mdns_dacp_monitor_start)) { + config.mdns->mdns_dacp_monitor_start(); + } else + debug(3, "Can't start a DACP monitor -- no mdns_dacp_monitor start registered."); } -void mdns_dacp_dont_monitor(void *userdata) { - if ((config.mdns) && (config.mdns->mdns_dacp_dont_monitor)) { - config.mdns->mdns_dacp_dont_monitor(userdata); +void mdns_dacp_monitor_stop() { + if ((config.mdns) && (config.mdns->mdns_dacp_monitor_stop)) { + config.mdns->mdns_dacp_monitor_stop(); } else - debug(3, "Can't stop a DACP monitor -- none registered."); + debug(3, "Can't stop a DACP monitor -- no mdns_dacp_monitor_stop registered."); } + +void mdns_dacp_monitor_set_id(const char *dacp_id) { + if ((config.mdns) && (config.mdns->mdns_dacp_monitor_set_id)) { + config.mdns->mdns_dacp_monitor_set_id(dacp_id); + } else + debug(3, "Can't set dacp_id -- no mdns_dacp_set_id registered."); +} + void mdns_ls_backends(void) { mdns_backend **b = NULL; printf("Available mDNS backends: \n"); diff --git a/mdns.h b/mdns.h index 9f96a9f7..8dc919f2 100644 --- a/mdns.h +++ b/mdns.h @@ -9,8 +9,9 @@ extern int mdns_pid; void mdns_unregister(void); void mdns_register(void); -void *mdns_dacp_monitor(char *dacp_id); -void mdns_dacp_dont_monitor(void *userdata); +void mdns_dacp_monitor_start(); +void mdns_dacp_monitor_stop(void); +void mdns_dacp_monitor_set_id(const char *dacp_id); void mdns_ls_backends(void); @@ -18,8 +19,9 @@ typedef struct { char *name; int (*mdns_register)(char *apname, int port); void (*mdns_unregister)(void); - void *(*mdns_dacp_monitor)(char *); - void (*mdns_dacp_dont_monitor)(void *); + void (*mdns_dacp_monitor_start)(); + void (*mdns_dacp_monitor_set_id)(const char *); + void (*mdns_dacp_monitor_stop)(); } mdns_backend; #ifdef CONFIG_METADATA diff --git a/mdns_avahi.c b/mdns_avahi.c index b739e716..e1abe286 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -61,6 +61,8 @@ typedef struct { char *dacp_id; } dacp_browser_struct; +dacp_browser_struct private_dbs; + // static AvahiServiceBrowser *sb = NULL; static AvahiClient *client = NULL; // static AvahiClient *service_client = NULL; @@ -90,24 +92,26 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn break; case AVAHI_RESOLVER_FOUND: { // char a[AVAHI_ADDRESS_STR_MAX], *t; - // debug(1, "Resolve callback: Service '%s' of type '%s' in domain '%s':", name, type, domain); - char *dacpid = strstr(name, "iTunes_Ctrl_"); - if (dacpid) { - dacpid += strlen("iTunes_Ctrl_"); - if (strcmp(dacpid, dbs->dacp_id) == 0) { - debug(3, "Client's DACP port: %u.", port); + debug(3, "Resolve callback: Service '%s' of type '%s' in domain '%s':", name, type, domain); + if (dbs->dacp_id) { + char *dacpid = strstr(name, "iTunes_Ctrl_"); + if (dacpid) { + dacpid += strlen("iTunes_Ctrl_"); + if (strcmp(dacpid, dbs->dacp_id) == 0) { + debug(1, "Client \"%s\"'s DACP port: %u.", dbs->dacp_id, port); #ifdef CONFIG_DACP_CLIENT - dacp_monitor_port_update_callback(dacpid, port); + dacp_monitor_port_update_callback(dacpid, port); #endif #ifdef CONFIG_METADATA - char portstring[20]; - memset(portstring, 0, sizeof(portstring)); - snprintf(portstring, sizeof(portstring), "%u", port); - send_ssnc_metadata('dapo', strdup(portstring), strlen(portstring), 0); + char portstring[20]; + memset(portstring, 0, sizeof(portstring)); + snprintf(portstring, sizeof(portstring), "%u", port); + send_ssnc_metadata('dapo', strdup(portstring), strlen(portstring), 0); #endif + } + } else { + debug(1, "Resolve callback: Can't see a DACP string in a DACP Record!"); } - } else { - debug(1, "Resolve callback: Can't see a DACP string in a DACP Record!"); } } } @@ -128,7 +132,7 @@ static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, Avah avahi_threaded_poll_quit(tpoll); break; case AVAHI_BROWSER_NEW: - debug(3, "(Browser) NEW: service '%s' of type '%s' in domain '%s'.", name, type, domain); + // debug(1, "(Browser) NEW: service '%s' of type '%s' in domain '%s'.", name, type, domain); /* We ignore the returned resolver object. In the callback function we free it. If the server is terminated before the callback function is called the server will free @@ -144,7 +148,7 @@ static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, Avah char *dacpid = strstr(name, "iTunes_Ctrl_"); if (dacpid) { dacpid += strlen("iTunes_Ctrl_"); - if (strcmp(dacpid, dbs->dacp_id) == 0) + if ((dbs->dacp_id) && (strcmp(dacpid, dbs->dacp_id) == 0)) dacp_monitor_port_update_callback(dbs->dacp_id, 0); // say the port is withdrawn } else { debug(1, "Browse callback: Can't see a DACP string in a DACP Record!"); @@ -410,26 +414,15 @@ static void avahi_unregister(void) { service_name = NULL; } -void *avahi_dacp_monitor(char *dacp_id) { - dacp_browser_struct *dbs = (dacp_browser_struct *)malloc(sizeof(dacp_browser_struct)); - - if (dbs == NULL) - die("can not allocate a dacp_browser_struct."); - - char *t = strdup(dacp_id); - if (t) - dbs->dacp_id = t; - else { - die("can not allocate a dacp_id string in dacp_browser_struct."); - } +void avahi_dacp_monitor_start(void) { + dacp_browser_struct *dbs = &private_dbs; + memset((void *)&private_dbs, 0, sizeof(dacp_browser_struct)); // create the threaded poll code int err; if (!(dbs->service_poll = avahi_threaded_poll_new())) { warn("couldn't create avahi threaded service_poll!"); - free(dbs->dacp_id); - free((char *)dbs); - return NULL; + return; } // create the service client @@ -438,63 +431,72 @@ void *avahi_dacp_monitor(char *dacp_id) { service_client_callback, (void *)dbs, &err))) { warn("couldn't create avahi service client: %s!", avahi_strerror(err)); avahi_threaded_poll_free(dbs->service_poll); - free(dbs->dacp_id); - free((char *)dbs); - return NULL; + return; } - /* Create the service browser */ - if (!(dbs->service_browser = - avahi_service_browser_new(dbs->service_client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, - "_dacp._tcp", NULL, 0, browse_callback, (void *)dbs))) { - warn("failed to create avahi service browser: %s\n", - avahi_strerror(avahi_client_errno(dbs->service_client))); - avahi_client_free(dbs->service_client); - avahi_threaded_poll_free(dbs->service_poll); - free(dbs->dacp_id); - free((char *)dbs); - return NULL; - } // start the polling thread if (avahi_threaded_poll_start(dbs->service_poll) < 0) { warn("couldn't start avahi service_poll thread"); avahi_service_browser_free(dbs->service_browser); avahi_client_free(dbs->service_client); avahi_threaded_poll_free(dbs->service_poll); - free(dbs->dacp_id); - free((char *)dbs); - return NULL; + return; } debug(3, "Avahi DACP monitor successfully started"); - return (void *)dbs; + return; } -void avahi_dacp_dont_monitor(void *userdata) { +void avahi_dacp_monitor_set_id(const char *dacp_id) { debug(3, "avahi_dacp_dont_monitor"); - if (userdata) { - dacp_browser_struct *dbs = (dacp_browser_struct *)userdata; - // stop and dispose of everything - if (dbs->service_poll) { - avahi_threaded_poll_stop(dbs->service_poll); + dacp_browser_struct *dbs = &private_dbs; + + if (dbs->dacp_id) + free(dbs->dacp_id); + if (dacp_id == NULL) + dbs->dacp_id = NULL; + else { + char *t = strdup(dacp_id); + if (t) { + dbs->dacp_id = t; avahi_threaded_poll_lock(dbs->service_poll); if (dbs->service_browser) avahi_service_browser_free(dbs->service_browser); - if (dbs->service_client) - avahi_client_free(dbs->service_client); + + if (!(dbs->service_browser = + avahi_service_browser_new(dbs->service_client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + "_dacp._tcp", NULL, 0, browse_callback, (void *)dbs))) { + warn("failed to create avahi service browser: %s\n", + avahi_strerror(avahi_client_errno(dbs->service_client))); + } avahi_threaded_poll_unlock(dbs->service_poll); - avahi_threaded_poll_free(dbs->service_poll); + } else { + warn("avahi_dacp_set_id: can not allocate a dacp_id string in dacp_browser_struct."); } - free(dbs->dacp_id); - free(userdata); - debug(3, "Avahi DACP monitor successfully stopped"); - } else { - debug(1, "Avahi DACP Monitor is not running."); + debug(3, "Search for DACP ID \"%s\".", t); } - debug(3, "avahi_dacp_dont_monitor exit"); +} + +void avahi_dacp_monitor_stop() { + debug(3, "avahi_dacp_dont_monitor"); + dacp_browser_struct *dbs = &private_dbs; + // stop and dispose of everything + if (dbs->service_poll) { + avahi_threaded_poll_stop(dbs->service_poll); + avahi_threaded_poll_lock(dbs->service_poll); + if (dbs->service_browser) + avahi_service_browser_free(dbs->service_browser); + if (dbs->service_client) + avahi_client_free(dbs->service_client); + avahi_threaded_poll_unlock(dbs->service_poll); + avahi_threaded_poll_free(dbs->service_poll); + } + free(dbs->dacp_id); + debug(3, "Avahi DACP monitor successfully stopped"); } mdns_backend mdns_avahi = {.name = "avahi", .mdns_register = avahi_register, .mdns_unregister = avahi_unregister, - .mdns_dacp_monitor = avahi_dacp_monitor, - .mdns_dacp_dont_monitor = avahi_dacp_dont_monitor}; + .mdns_dacp_monitor_start = avahi_dacp_monitor_start, + .mdns_dacp_monitor_set_id = avahi_dacp_monitor_set_id, + .mdns_dacp_monitor_stop = avahi_dacp_monitor_stop}; diff --git a/mdns_dns_sd.c b/mdns_dns_sd.c index 722aadcf..95bae443 100644 --- a/mdns_dns_sd.c +++ b/mdns_dns_sd.c @@ -93,5 +93,6 @@ static void mdns_dns_sd_unregister(void) { mdns_backend mdns_dns_sd = {.name = "dns-sd", .mdns_register = mdns_dns_sd_register, .mdns_unregister = mdns_dns_sd_unregister, - .mdns_dacp_monitor = NULL, - .mdns_dacp_dont_monitor = NULL}; + .mdns_dacp_monitor_start = NULL, + .mdns_dacp_monitor_set_id = NULL, + .mdns_dacp_monitor_stop = NULL}; diff --git a/mdns_external.c b/mdns_external.c index e3a241fd..d3879ff5 100644 --- a/mdns_external.c +++ b/mdns_external.c @@ -162,11 +162,13 @@ static void kill_mdns_child(void) { mdns_backend mdns_external_avahi = {.name = "external-avahi", .mdns_register = mdns_external_avahi_register, .mdns_unregister = kill_mdns_child, - .mdns_dacp_monitor = NULL, - .mdns_dacp_dont_monitor = NULL}; + .mdns_dacp_monitor_start = NULL, + .mdns_dacp_monitor_set_id = NULL, + .mdns_dacp_monitor_stop = NULL}; mdns_backend mdns_external_dns_sd = {.name = "external-dns-sd", .mdns_register = mdns_external_dns_sd_register, .mdns_unregister = kill_mdns_child, - .mdns_dacp_monitor = NULL, - .mdns_dacp_dont_monitor = NULL}; + .mdns_dacp_monitor_start = NULL, + .mdns_dacp_monitor_set_id = NULL, + .mdns_dacp_monitor_stop = NULL}; diff --git a/mdns_tinysvcmdns.c b/mdns_tinysvcmdns.c index 238b68b2..ad6be65a 100644 --- a/mdns_tinysvcmdns.c +++ b/mdns_tinysvcmdns.c @@ -164,5 +164,6 @@ static void mdns_tinysvcmdns_unregister(void) { mdns_backend mdns_tinysvcmdns = {.name = "tinysvcmdns", .mdns_register = mdns_tinysvcmdns_register, .mdns_unregister = mdns_tinysvcmdns_unregister, - .mdns_dacp_monitor = NULL, - .mdns_dacp_dont_monitor = NULL}; + .mdns_dacp_monitor_start = NULL, + .mdns_dacp_monitor_set_id = NULL, + .mdns_dacp_monitor_stop = NULL}; diff --git a/player.c b/player.c index 429a218d..3d3e8daa 100644 --- a/player.c +++ b/player.c @@ -76,6 +76,7 @@ #endif #include "common.h" +#include "mdns.h" #include "player.h" #include "rtp.h" #include "rtsp.h" @@ -452,7 +453,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in // debug(1,"Flush_rtp_timestamp is %u",flush_rtp_timestamp); // now, if a flush_rtp_timestamp has been defined and the incoming timestamp is "before" it, - // drop it… + // drop itÉ if ((conn->flush_rtp_timestamp != 0) && (actual_timestamp != conn->flush_rtp_timestamp) && (modulo_32_offset(actual_timestamp, conn->flush_rtp_timestamp) < @@ -478,7 +479,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in abuf_t *abuf = 0; if (!conn->ab_synced) { - // if this is the first packet… + // if this is the first packetÉ debug(3, "syncing to seqno %u.", seqno); conn->ab_write = seqno; conn->ab_read = seqno; @@ -500,7 +501,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in } if (conn->ab_write == - seqno) { // if this is the expected packet (which could be the first packet…) + seqno) { // if this is the expected packet (which could be the first packetÉ) uint64_t reception_time = get_absolute_time_in_fp(); if (conn->input_frame_rate_starting_point_is_valid == 0) { if ((conn->packet_count_since_flush >= 500) && (conn->packet_count_since_flush <= 510)) { @@ -1416,19 +1417,10 @@ void player_thread_cleanup_handler(void *arg) { } #ifdef CONFIG_DACP_CLIENT - relinquish_dacp_server_information( conn); // say it doesn't belong to this conversation thread any more... - #else - // stop watching for DACP port number stuff - // this is only used for compatability, if dacp stuff isn't enabled. - if (conn->dapo_private_storage) { - mdns_dacp_dont_monitor(conn->dapo_private_storage); - conn->dapo_private_storage = NULL; - } else { - debug(2, "DACP Monitor already stopped"); - } + mdns_dacp_monitor_set_id(NULL); // say we're not interested in following that DACP id any more #endif debug(2, "Cancelling timing, control and audio threads..."); @@ -1733,20 +1725,11 @@ void *player_thread_func(void *arg) { pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); #ifdef CONFIG_DACP_CLIENT - // debug(1, "Set dacp server info"); - // may have pthread cancellation points in it -- beware - set_dacp_server_information(conn); // this will start scanning when a port is registered by the - // code initiated by the mdns_dacp_monitor + set_dacp_server_information(conn); #else - // this is only used for compatability, if dacp stuff isn't enabled. - // start an mdns/zeroconf thread to look for DACP messages containing our DACP_ID and getting the - // port number - if (conn->dapo_private_storage) - debug(1, "DACP monitor already initialised?"); - else - // almost certainly, this has pthread cancellation points in it -- beware - conn->dapo_private_storage = mdns_dacp_monitor(conn->dacp_id); + mdns_dacp_monitor_set_id(conn->dacp_id); #endif + pthread_setcancelstate(oldState, NULL); // set the default volume to whatever it was before, as stored in the config airplay_volume From df61f806076a94c2d2ec2121e9e3661fd14af8bc Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 8 Dec 2018 22:30:34 +0000 Subject: [PATCH 270/569] Time opening of the device --- audio_alsa.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/audio_alsa.c b/audio_alsa.c index acc4693e..5a60b2d8 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1066,6 +1066,7 @@ int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) { } static int play(void *buf, int samples) { + uint64_t time_before; // debug(3,"audio_alsa play called."); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable @@ -1073,7 +1074,9 @@ static int play(void *buf, int samples) { if (alsa_handle == NULL) { pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); + time_before = get_absolute_time_in_fp(); ret = actual_open_alsa_device(); + debug(1, "Opening time: %8.2f us.", (1000000.0 * (get_absolute_time_in_fp() - time_before ) / (uint64_t)0x100000000)); if (ret == 0) { if (audio_alsa.volume) do_volume(set_volume); @@ -1110,7 +1113,7 @@ static int play(void *buf, int samples) { uint64_t maximum_permitted_writing_time = (config.alsa_maximum_write_time * 1000000000); maximum_permitted_writing_time = (maximum_permitted_writing_time << 32) / 1000000000; - uint64_t time_before = get_absolute_time_in_fp(); + time_before = get_absolute_time_in_fp(); err = alsa_pcm_write(alsa_handle, buf, samples); uint64_t writing_time = get_absolute_time_in_fp() - time_before; From 7c42050847aeefe96e0bcaaff634969a3ee333b2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 9 Dec 2018 08:45:10 +0000 Subject: [PATCH 271/569] Get timings of play / flush / delay calls. --- audio_alsa.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 5a60b2d8..bc9a76aa 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -975,11 +975,12 @@ int my_snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { } int delay(long *the_delay) { + uint64_t overall_time_before = get_absolute_time_in_fp(); // snd_pcm_sframes_t is a signed long -- hence the return of a "long" int reply = 0; // debug(3,"audio_alsa delay called."); if (alsa_handle == NULL) { - return -ENODEV; + reply = -ENODEV; } else { int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable @@ -1048,8 +1049,10 @@ int delay(long *the_delay) { // if ((random() % 100000) < 3) // keep it pretty rare // reply = -EIO; // pretend something bad has happened pthread_setcancelstate(oldState, NULL); - return reply; } + debug(1, "Total playing time to get delay: %8.2f us.", + (1000000.0 * (get_absolute_time_in_fp() - overall_time_before) / (uint64_t)0x100000000)); + return reply; } int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) { @@ -1066,7 +1069,7 @@ int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) { } static int play(void *buf, int samples) { - uint64_t time_before; + uint64_t overall_time_before = get_absolute_time_in_fp(); // debug(3,"audio_alsa play called."); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable @@ -1074,9 +1077,9 @@ static int play(void *buf, int samples) { if (alsa_handle == NULL) { pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); - time_before = get_absolute_time_in_fp(); + overall_time_before = get_absolute_time_in_fp(); ret = actual_open_alsa_device(); - debug(1, "Opening time: %8.2f us.", (1000000.0 * (get_absolute_time_in_fp() - time_before ) / (uint64_t)0x100000000)); + debug(1, "Opening time: %8.2f us.", (1000000.0 * (get_absolute_time_in_fp() - overall_time_before ) / (uint64_t)0x100000000)); if (ret == 0) { if (audio_alsa.volume) do_volume(set_volume); @@ -1113,9 +1116,9 @@ static int play(void *buf, int samples) { uint64_t maximum_permitted_writing_time = (config.alsa_maximum_write_time * 1000000000); maximum_permitted_writing_time = (maximum_permitted_writing_time << 32) / 1000000000; - time_before = get_absolute_time_in_fp(); + overall_time_before = get_absolute_time_in_fp(); err = alsa_pcm_write(alsa_handle, buf, samples); - uint64_t writing_time = get_absolute_time_in_fp() - time_before; + uint64_t writing_time = get_absolute_time_in_fp() - overall_time_before; if (err < 0) { frame_index = 0; @@ -1192,10 +1195,14 @@ static int play(void *buf, int samples) { pthread_cleanup_pop(0); // release the mutex } pthread_setcancelstate(oldState, NULL); + debug(1, "Total playing time to write %d samples: %8.2f us.", + samples, (1000000.0 * (get_absolute_time_in_fp() - overall_time_before) / (uint64_t)0x100000000)); + return ret; } static void flush(void) { + uint64_t overall_time_before = get_absolute_time_in_fp(); // debug(2,"audio_alsa flush called."); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable @@ -1221,6 +1228,8 @@ static void flush(void) { debug_mutex_unlock(&alsa_mutex, 3); pthread_cleanup_pop(0); // release the mutex pthread_setcancelstate(oldState, NULL); + debug(1, "Total playing time to flush: %8.2f us.", + (1000000.0 * (get_absolute_time_in_fp() - overall_time_before) / (uint64_t)0x100000000)); } static void stop(void) { From ea11547095385d1be9e4702a1cf19e5e994eb27f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 9 Dec 2018 08:58:47 +0000 Subject: [PATCH 272/569] add delay to debug output. --- audio_alsa.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/audio_alsa.c b/audio_alsa.c index bc9a76aa..07ee0cab 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -976,6 +976,7 @@ int my_snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { int delay(long *the_delay) { uint64_t overall_time_before = get_absolute_time_in_fp(); + *the_delay = 0; // snd_pcm_sframes_t is a signed long -- hence the return of a "long" int reply = 0; // debug(3,"audio_alsa delay called."); @@ -1050,7 +1051,7 @@ int delay(long *the_delay) { // reply = -EIO; // pretend something bad has happened pthread_setcancelstate(oldState, NULL); } - debug(1, "Total playing time to get delay: %8.2f us.", + debug(1, "Total playing time to get delay of %d frames: %8.2f us.", *the_delay, (1000000.0 * (get_absolute_time_in_fp() - overall_time_before) / (uint64_t)0x100000000)); return reply; } From 46a13dce409e88a655aae2a778417fde18c98193 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 10 Dec 2018 10:50:08 +0000 Subject: [PATCH 273/569] Add stall detection to the alsa subsystem. --- audio_alsa.c | 45 ++++++++++++++++++++++++++++++++++++++++----- player.c | 8 +------- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 07ee0cab..3eff6fbe 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -74,6 +74,11 @@ audio_output audio_alsa = { static pthread_mutex_t alsa_mutex = PTHREAD_MUTEX_INITIALIZER; +// for tracking how long the output device has stalled +uint64_t stall_monitor_start_time; // zero if not initialised / not started / zeroed by flush +long stall_monitor_frame_count; // set to delay at start of time, incremented by any writes +int stall_monitor_error_notified; + static snd_output_t *output = NULL; static unsigned int desired_sample_rate; static enum sps_format_t sample_format; @@ -935,6 +940,11 @@ static void start(int i_sample_rate, int i_sample_format) { frame_index = 0; measurement_data_is_valid = 0; + + stall_monitor_start_time = 0; + stall_monitor_frame_count = 0; + stall_monitor_error_notified = 0; + } // assuming pthread cancellation is disabled @@ -1051,8 +1061,32 @@ int delay(long *the_delay) { // reply = -EIO; // pretend something bad has happened pthread_setcancelstate(oldState, NULL); } - debug(1, "Total playing time to get delay of %d frames: %8.2f us.", *the_delay, + debug(3, "Total playing time to get delay of %d frames: %8.2f us.", *the_delay, (1000000.0 * (get_absolute_time_in_fp() - overall_time_before) / (uint64_t)0x100000000)); + + if ((reply == 0) && (*the_delay != 0)) { + uint64_t stall_monitor_time_now = get_absolute_time_in_fp(); + if ((stall_monitor_start_time != 0) && (stall_monitor_frame_count == *the_delay)) { + // hasn't outputted anything since the last call to delay() + int64_t time_stalled = stall_monitor_time_now - stall_monitor_start_time; + int64_t stall_monitor_error_threshold = 1000; // microseconds; + stall_monitor_error_threshold = (stall_monitor_error_threshold << 32) / 1000000; // now in fp form + if (time_stalled > stall_monitor_error_threshold) { + if (stall_monitor_error_notified == 0) { + debug(1, "alsa output device appears to be stalled -- no output in %8.2 us.",(1000000.0 * stall_monitor_error_threshold) / (uint64_t)0x100000000); + stall_monitor_error_notified = 1; + } + } + } else { + // is outputting stuff, so restart the monitoring here + stall_monitor_start_time = stall_monitor_time_now; + stall_monitor_frame_count = *the_delay; + } + } else { + // if there is an error or the delay is zero (from which it is assumed there is no output) + stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush + stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes + } return reply; } @@ -1080,7 +1114,7 @@ static int play(void *buf, int samples) { pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); overall_time_before = get_absolute_time_in_fp(); ret = actual_open_alsa_device(); - debug(1, "Opening time: %8.2f us.", (1000000.0 * (get_absolute_time_in_fp() - overall_time_before ) / (uint64_t)0x100000000)); + debug(3, "Opening time: %8.2f us.", (1000000.0 * (get_absolute_time_in_fp() - overall_time_before ) / (uint64_t)0x100000000)); if (ret == 0) { if (audio_alsa.volume) do_volume(set_volume); @@ -1120,6 +1154,7 @@ static int play(void *buf, int samples) { overall_time_before = get_absolute_time_in_fp(); err = alsa_pcm_write(alsa_handle, buf, samples); uint64_t writing_time = get_absolute_time_in_fp() - overall_time_before; + stall_monitor_frame_count += samples; if (err < 0) { frame_index = 0; @@ -1196,7 +1231,7 @@ static int play(void *buf, int samples) { pthread_cleanup_pop(0); // release the mutex } pthread_setcancelstate(oldState, NULL); - debug(1, "Total playing time to write %d samples: %8.2f us.", + debug(3, "Total playing time to write %d samples: %8.2f us.", samples, (1000000.0 * (get_absolute_time_in_fp() - overall_time_before) / (uint64_t)0x100000000)); return ret; @@ -1212,7 +1247,7 @@ static void flush(void) { do_mute(1); if (alsa_handle) { - + stall_monitor_start_time = 0; if ((derr = snd_pcm_drop(alsa_handle))) debug(1, "Error %d (\"%s\") dropping output device.", derr, snd_strerror(derr)); @@ -1229,7 +1264,7 @@ static void flush(void) { debug_mutex_unlock(&alsa_mutex, 3); pthread_cleanup_pop(0); // release the mutex pthread_setcancelstate(oldState, NULL); - debug(1, "Total playing time to flush: %8.2f us.", + debug(3, "Total playing time to flush: %8.2f us.", (1000000.0 * (get_absolute_time_in_fp() - overall_time_before) / (uint64_t)0x100000000)); } diff --git a/player.c b/player.c index 3d3e8daa..5d60687b 100644 --- a/player.c +++ b/player.c @@ -449,12 +449,6 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in conn->time_of_last_audio_packet = get_absolute_time_in_fp(); if (conn->connection_state_to_output) { // if we are supposed to be processing these packets - // if (flush_rtp_timestamp != 0) - // debug(1,"Flush_rtp_timestamp is %u",flush_rtp_timestamp); - - // now, if a flush_rtp_timestamp has been defined and the incoming timestamp is "before" it, - // drop itÉ - if ((conn->flush_rtp_timestamp != 0) && (actual_timestamp != conn->flush_rtp_timestamp) && (modulo_32_offset(actual_timestamp, conn->flush_rtp_timestamp) < conn->input_rate * 10)) { // if it's less than 10 seconds @@ -2035,7 +2029,7 @@ void *player_thread_func(void *arg) { debug(2, "Large positive sync error: %" PRId64 ".", sync_error); int64_t local_frames_to_drop = sync_error / conn->output_sample_ratio; uint32_t frames_to_drop_sized = local_frames_to_drop; - do_flush(inframe->given_timestamp + frames_to_drop_sized, conn); + do_flush(inframe->given_timestamp + frames_to_drop_sized, conn); // this will reset_input_flow_metrics anyway } else if ((sync_error < 0) && ((-sync_error) > filler_length)) { debug(2, "Large negative sync error: %" PRId64 " with should_be_frame_32 of %" PRIu32 From 079db2eba885e708eb32dd6121f601365fe553a7 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 10 Dec 2018 12:38:48 +0000 Subject: [PATCH 274/569] Set stall threshold to 200 ms. --- audio_alsa.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 3eff6fbe..d1fe42a1 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1069,11 +1069,11 @@ int delay(long *the_delay) { if ((stall_monitor_start_time != 0) && (stall_monitor_frame_count == *the_delay)) { // hasn't outputted anything since the last call to delay() int64_t time_stalled = stall_monitor_time_now - stall_monitor_start_time; - int64_t stall_monitor_error_threshold = 1000; // microseconds; + int64_t stall_monitor_error_threshold = 200000; // microseconds; stall_monitor_error_threshold = (stall_monitor_error_threshold << 32) / 1000000; // now in fp form if (time_stalled > stall_monitor_error_threshold) { if (stall_monitor_error_notified == 0) { - debug(1, "alsa output device appears to be stalled -- no output in %8.2 us.",(1000000.0 * stall_monitor_error_threshold) / (uint64_t)0x100000000); + debug(1, "alsa output device stalled -- no output in %8.2f us.",(1000000.0 * stall_monitor_error_threshold) / (uint64_t)0x100000000); stall_monitor_error_notified = 1; } } From 9d5ff22f2dc2591301154ade1c9b75adf81b0730 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 10 Dec 2018 22:28:52 +0000 Subject: [PATCH 275/569] Connect up the stall response to the external command calling -- add a single argument to the unfixable error call. --- audio_alsa.c | 29 ++++++++++++++++------------- common.c | 24 ++++++++++++++++-------- common.h | 4 +++- player.c | 40 +++++++++++++++++++++++++++++++++++++--- player.h | 3 ++- rtsp.c | 11 +++++++---- 6 files changed, 81 insertions(+), 30 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index d1fe42a1..71a4e7ee 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -76,8 +76,8 @@ static pthread_mutex_t alsa_mutex = PTHREAD_MUTEX_INITIALIZER; // for tracking how long the output device has stalled uint64_t stall_monitor_start_time; // zero if not initialised / not started / zeroed by flush -long stall_monitor_frame_count; // set to delay at start of time, incremented by any writes -int stall_monitor_error_notified; +long stall_monitor_frame_count; // set to delay at start of time, incremented by any writes +int stall_monitor_error_notified; static snd_output_t *output = NULL; static unsigned int desired_sample_rate; @@ -940,11 +940,10 @@ static void start(int i_sample_rate, int i_sample_format) { frame_index = 0; measurement_data_is_valid = 0; - + stall_monitor_start_time = 0; stall_monitor_frame_count = 0; stall_monitor_error_notified = 0; - } // assuming pthread cancellation is disabled @@ -1062,20 +1061,23 @@ int delay(long *the_delay) { pthread_setcancelstate(oldState, NULL); } debug(3, "Total playing time to get delay of %d frames: %8.2f us.", *the_delay, - (1000000.0 * (get_absolute_time_in_fp() - overall_time_before) / (uint64_t)0x100000000)); - + (1000000.0 * (get_absolute_time_in_fp() - overall_time_before) / (uint64_t)0x100000000)); + if ((reply == 0) && (*the_delay != 0)) { uint64_t stall_monitor_time_now = get_absolute_time_in_fp(); if ((stall_monitor_start_time != 0) && (stall_monitor_frame_count == *the_delay)) { // hasn't outputted anything since the last call to delay() int64_t time_stalled = stall_monitor_time_now - stall_monitor_start_time; int64_t stall_monitor_error_threshold = 200000; // microseconds; - stall_monitor_error_threshold = (stall_monitor_error_threshold << 32) / 1000000; // now in fp form + stall_monitor_error_threshold = + (stall_monitor_error_threshold << 32) / 1000000; // now in fp form if (time_stalled > stall_monitor_error_threshold) { if (stall_monitor_error_notified == 0) { - debug(1, "alsa output device stalled -- no output in %8.2f us.",(1000000.0 * stall_monitor_error_threshold) / (uint64_t)0x100000000); + debug(1, "alsa output device stalled -- no output in %8.2f us.", + (1000000.0 * stall_monitor_error_threshold) / (uint64_t)0x100000000); stall_monitor_error_notified = 1; } + reply = sps_extra_errno_output_stalled; } } else { // is outputting stuff, so restart the monitoring here @@ -1084,7 +1086,7 @@ int delay(long *the_delay) { } } else { // if there is an error or the delay is zero (from which it is assumed there is no output) - stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush + stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes } return reply; @@ -1114,7 +1116,8 @@ static int play(void *buf, int samples) { pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); overall_time_before = get_absolute_time_in_fp(); ret = actual_open_alsa_device(); - debug(3, "Opening time: %8.2f us.", (1000000.0 * (get_absolute_time_in_fp() - overall_time_before ) / (uint64_t)0x100000000)); + debug(3, "Opening time: %8.2f us.", + (1000000.0 * (get_absolute_time_in_fp() - overall_time_before) / (uint64_t)0x100000000)); if (ret == 0) { if (audio_alsa.volume) do_volume(set_volume); @@ -1231,8 +1234,8 @@ static int play(void *buf, int samples) { pthread_cleanup_pop(0); // release the mutex } pthread_setcancelstate(oldState, NULL); - debug(3, "Total playing time to write %d samples: %8.2f us.", - samples, (1000000.0 * (get_absolute_time_in_fp() - overall_time_before) / (uint64_t)0x100000000)); + debug(3, "Total playing time to write %d samples: %8.2f us.", samples, + (1000000.0 * (get_absolute_time_in_fp() - overall_time_before) / (uint64_t)0x100000000)); return ret; } @@ -1265,7 +1268,7 @@ static void flush(void) { pthread_cleanup_pop(0); // release the mutex pthread_setcancelstate(oldState, NULL); debug(3, "Total playing time to flush: %8.2f us.", - (1000000.0 * (get_absolute_time_in_fp() - overall_time_before) / (uint64_t)0x100000000)); + (1000000.0 * (get_absolute_time_in_fp() - overall_time_before) / (uint64_t)0x100000000)); } static void stop(void) { diff --git a/common.c b/common.c index 7cadf231..053abbe3 100644 --- a/common.c +++ b/common.c @@ -739,23 +739,31 @@ void command_start(void) { } } } -void command_execute(const char *command) { +void command_execute(const char *command, const char *extra_argument) { // this has a cancellation point if waiting is enabled if (command) { + char new_command_buffer[1024]; + char *full_command = (char *)command; + if (extra_argument != NULL) { + memset(new_command_buffer, 0, sizeof(new_command_buffer)); + snprintf(new_command_buffer, sizeof(new_command_buffer), "%s %s", command, extra_argument); + full_command = new_command_buffer; + } + /*Spawn a child to run the program.*/ pid_t pid = fork(); if (pid == 0) { /* child process */ int argC; char **argV; - if (poptParseArgvString(command, &argC, (const char ***)&argV) != + if (poptParseArgvString(full_command, &argC, (const char ***)&argV) != 0) // note that argV should be free()'d after use, but we expect this fork to exit // eventually. - debug(1, "Can't decipher command arguments in \"%s\".", command); + debug(1, "Can't decipher command arguments in \"%s\".", full_command); else { - // debug(1,"Executing command %s",command); + // debug(1,"Executing command %s",full_command); execv(argV[0], argV); - warn("Execution of command \"%s\" failed to start", command); - debug(1, "Error executing command \"%s\".", command); + warn("Execution of command \"%s\" failed to start", full_command); + debug(1, "Error executing command \"%s\".", full_command); exit(127); /* only if execv fails */ } } else { @@ -763,8 +771,8 @@ void command_execute(const char *command) { process to finish */ pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */ if (rc != pid) { - warn("Execution of command \"%s\" returned an error.", command); - debug(1, "Command \"%s\" finished with error %d", command, errno); + warn("Execution of command \"%s\" returned an error.", full_command); + debug(1, "Command \"%s\" finished with error %d", full_command, errno); } } // debug(1,"Continue after on-unfixable command"); diff --git a/common.h b/common.h index abf19e71..9e2d88f3 100644 --- a/common.h +++ b/common.h @@ -30,6 +30,8 @@ enum dbus_session_type { } dbt_type; #endif +#define sps_extra_errno_output_stalled 32768 + enum endian_type { SS_LITTLE_ENDIAN = 0, SS_PDP_ENDIAN, @@ -300,7 +302,7 @@ int config_set_lookup_bool(config_t *cfg, char *where, int *dst); void command_start(void); void command_stop(void); -void command_execute(const char *command); +void command_execute(const char *command, const char *extra_argument); void command_set_volume(double volume); int mkpath(const char *path, mode_t mode); diff --git a/player.c b/player.c index 5d60687b..ec7d2adc 100644 --- a/player.c +++ b/player.c @@ -1075,7 +1075,23 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { 1; // even if we haven't sent silence because it's zero frames long... } } else { - // debug(1, "Unable to get dac delay."); + if ((resp == sps_extra_errno_output_stalled) && + (conn->unfixable_error_reported == 0)) { + conn->unfixable_error_reported = 1; + if (config.cmd_unfixable) { + warn("Connection %d: An unfixable error has been detected -- output device " + "is stalled. Executing the " + "\"run_this_if_an_unfixable_error_is_detected\" command.", + conn->connection_number); + command_execute(config.cmd_unfixable, "output_device_stalled"); + } else { + warn("Connection %d: An unfixable error has been detected -- output device " + "is stalled. \"No " + "run_this_if_an_unfixable_error_is_detected\" command provided -- " + "nothing done.", + conn->connection_number); + } + } } } else { // no delay function on back end -- just send the prefiller silence @@ -1955,7 +1971,24 @@ void *player_thread_func(void *arg) { minimum_dac_queue_size = current_delay; // update for display later } } else { - debug(2, "Delay error %d when checking running latency.", resp); + if ((resp == sps_extra_errno_output_stalled) && + (conn->unfixable_error_reported == 0)) { + conn->unfixable_error_reported = 1; + if (config.cmd_unfixable) { + warn("Connection %d: An unfixable error has been detected -- output device is " + "stalled. Executing the " + "\"run_this_if_an_unfixable_error_is_detected\" command.", + conn->connection_number); + command_execute(config.cmd_unfixable, "output_device_stalled"); + } else { + warn("Connection %d: An unfixable error has been detected -- output device is " + "stalled. \"No " + "run_this_if_an_unfixable_error_is_detected\" command provided -- nothing " + "done.", + conn->connection_number); + } + } else + debug(2, "Delay error %d when checking running latency.", resp); } } @@ -2029,7 +2062,8 @@ void *player_thread_func(void *arg) { debug(2, "Large positive sync error: %" PRId64 ".", sync_error); int64_t local_frames_to_drop = sync_error / conn->output_sample_ratio; uint32_t frames_to_drop_sized = local_frames_to_drop; - do_flush(inframe->given_timestamp + frames_to_drop_sized, conn); // this will reset_input_flow_metrics anyway + do_flush(inframe->given_timestamp + frames_to_drop_sized, + conn); // this will reset_input_flow_metrics anyway } else if ((sync_error < 0) && ((-sync_error) > filler_length)) { debug(2, "Large negative sync error: %" PRId64 " with should_be_frame_32 of %" PRIu32 diff --git a/player.h b/player.h index 9346ef2b..413aa8da 100644 --- a/player.h +++ b/player.h @@ -85,7 +85,8 @@ typedef struct { volatile int stop; volatile int running; volatile uint64_t watchdog_bark_time; - volatile int watchdog_barks; // number of times the watchdog has timed out and done something + volatile int watchdog_barks; // number of times the watchdog has timed out and done something + int unfixable_error_reported; // set when an unfixable error command has been executed. time_t playstart; pthread_t thread, timer_requester, rtp_audio_thread, rtp_control_thread, rtp_timing_thread, diff --git a/rtsp.c b/rtsp.c index 7921f70b..04369ee1 100644 --- a/rtsp.c +++ b/rtsp.c @@ -291,13 +291,16 @@ void *player_watchdog_thread_code(void *arg) { conn->stop = 1; pthread_cancel(conn->thread); } else if (conn->watchdog_barks == 3) { - if (config.cmd_unfixable) { - warn("Connection %d: An unfixable error has been detected. Executing the " + if ((config.cmd_unfixable) && (conn->unfixable_error_reported == 0)) { + conn->unfixable_error_reported = 1; + warn("Connection %d: An unfixable error has been detected -- can't stop a play " + "session. Executing the " "\"run_this_if_an_unfixable_error_is_detected\" command.", conn->connection_number); - command_execute(config.cmd_unfixable); + command_execute(config.cmd_unfixable, "unable_to_cancel_play_session"); } else { - warn("Connection %d: An unfixable error has been detected. \"No " + warn("Connection %d: An unfixable error has been detected -- can't stop a play " + "session. \"No " "run_this_if_an_unfixable_error_is_detected\" command provided -- nothing done.", conn->connection_number); } From 45ce9092142ffb2c73a6fa4a198fbc84323dd1b4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 11 Dec 2018 10:56:36 +0000 Subject: [PATCH 276/569] Tidy up some debug messages and warnings, remove old diagnostic codes and settings. --- audio_alsa.c | 84 ++++++++++------------------------------------------ common.h | 3 +- player.c | 9 +----- rtsp.c | 9 ++---- 4 files changed, 19 insertions(+), 86 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 71a4e7ee..761e06d9 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -75,9 +75,9 @@ audio_output audio_alsa = { static pthread_mutex_t alsa_mutex = PTHREAD_MUTEX_INITIALIZER; // for tracking how long the output device has stalled -uint64_t stall_monitor_start_time; // zero if not initialised / not started / zeroed by flush -long stall_monitor_frame_count; // set to delay at start of time, incremented by any writes -int stall_monitor_error_notified; +uint64_t stall_monitor_start_time; // zero if not initialised / not started / zeroed by flush +long stall_monitor_frame_count; // set to delay at start of time, incremented by any writes +uint64_t stall_monitor_error_threshold; // if the time is longer than this, it's an error static snd_output_t *output = NULL; static unsigned int desired_sample_rate; @@ -221,9 +221,7 @@ static int init(int argc, char **argv) { config.audio_backend_latency_offset = 0; config.audio_backend_buffer_desired_length = 0.15; - config.alsa_maximum_write_time = 0.016; // 16 milliseconds -- if it takes longer, we need to know - config.alsa_maximum_interface_response_time = - 0.001; // one millisecond -- if it takes longer, we need to know + config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem // get settings from settings file first, allow them to be overridden by // command line options @@ -382,25 +380,14 @@ static int init(int argc, char **argv) { } } - /* Get the optional alsa_maximum_interface_response_time setting. */ - if (config_lookup_float(config.cfg, "alsa.maximum_interface_response_time", &dvalue)) { - if (dvalue < 0.0) { - warn("Invalid alsa maximum interface response time setting \"%f\". It " - "must be greater than 0. Default is \"%f\". No setting is made.", - dvalue, config.alsa_maximum_interface_response_time); - } else { - config.alsa_maximum_interface_response_time = dvalue; - } - } - - /* Get the optional alsa_maximum_write_time setting. */ - if (config_lookup_float(config.cfg, "alsa.maximum_write_time", &dvalue)) { + /* Get the optional alsa_maximum_stall_time setting. */ + if (config_lookup_float(config.cfg, "alsa.maximum_stall_time", &dvalue)) { if (dvalue < 0.0) { warn("Invalid alsa maximum write time setting \"%f\". It " "must be greater than 0. Default is \"%f\". No setting is made.", - dvalue, config.alsa_maximum_write_time); + dvalue, config.alsa_maximum_stall_time); } else { - config.alsa_maximum_write_time = dvalue; + config.alsa_maximum_stall_time = dvalue; } } } @@ -943,7 +930,10 @@ static void start(int i_sample_rate, int i_sample_format) { stall_monitor_start_time = 0; stall_monitor_frame_count = 0; - stall_monitor_error_notified = 0; + + stall_monitor_error_threshold = + (uint64_t)1000000 * config.alsa_maximum_stall_time; // stall time max to microseconds; + stall_monitor_error_threshold = (stall_monitor_error_threshold << 32) / 1000000; // now in fp form } // assuming pthread cancellation is disabled @@ -984,7 +974,6 @@ int my_snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { } int delay(long *the_delay) { - uint64_t overall_time_before = get_absolute_time_in_fp(); *the_delay = 0; // snd_pcm_sframes_t is a signed long -- hence the return of a "long" int reply = 0; @@ -998,10 +987,8 @@ int delay(long *the_delay) { int derr; snd_pcm_state_t dac_state = snd_pcm_state(alsa_handle); if (dac_state == SND_PCM_STATE_RUNNING) { - *the_delay = 0; // just to see what happens - uint64_t time_before = get_absolute_time_in_fp(); + reply = snd_pcm_delay(alsa_handle, the_delay); - uint64_t response_time = get_absolute_time_in_fp() - time_before; if (reply != 0) { debug(1, "Error %d in delay(): \"%s\". Delay reported is %d frames.", reply, @@ -1020,15 +1007,6 @@ int delay(long *the_delay) { measurement_data_is_valid = 0; } } - uint64_t maximum_permitted_response_time = - (uint64_t)(config.alsa_maximum_interface_response_time * 1000000); // microseconds - maximum_permitted_response_time = (maximum_permitted_response_time << 32) / 1000000; - if (response_time > maximum_permitted_response_time) { - debug(3, "Maximum response time of %8.2f us exceeded reading delay. Response time was " - "%12.2f us.", - (1000000.0 * maximum_permitted_response_time) / (uint64_t)0x100000000, - (1000000.0 * response_time) / (uint64_t)0x100000000); - } } else { reply = -EIO; // shomething is wrong frame_index = 0; // we'll be starting over... @@ -1060,23 +1038,12 @@ int delay(long *the_delay) { // reply = -EIO; // pretend something bad has happened pthread_setcancelstate(oldState, NULL); } - debug(3, "Total playing time to get delay of %d frames: %8.2f us.", *the_delay, - (1000000.0 * (get_absolute_time_in_fp() - overall_time_before) / (uint64_t)0x100000000)); - if ((reply == 0) && (*the_delay != 0)) { uint64_t stall_monitor_time_now = get_absolute_time_in_fp(); if ((stall_monitor_start_time != 0) && (stall_monitor_frame_count == *the_delay)) { // hasn't outputted anything since the last call to delay() - int64_t time_stalled = stall_monitor_time_now - stall_monitor_start_time; - int64_t stall_monitor_error_threshold = 200000; // microseconds; - stall_monitor_error_threshold = - (stall_monitor_error_threshold << 32) / 1000000; // now in fp form + uint64_t time_stalled = stall_monitor_time_now - stall_monitor_start_time; if (time_stalled > stall_monitor_error_threshold) { - if (stall_monitor_error_notified == 0) { - debug(1, "alsa output device stalled -- no output in %8.2f us.", - (1000000.0 * stall_monitor_error_threshold) / (uint64_t)0x100000000); - stall_monitor_error_notified = 1; - } reply = sps_extra_errno_output_stalled; } } else { @@ -1106,7 +1073,6 @@ int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) { } static int play(void *buf, int samples) { - uint64_t overall_time_before = get_absolute_time_in_fp(); // debug(3,"audio_alsa play called."); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable @@ -1114,10 +1080,7 @@ static int play(void *buf, int samples) { if (alsa_handle == NULL) { pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); - overall_time_before = get_absolute_time_in_fp(); ret = actual_open_alsa_device(); - debug(3, "Opening time: %8.2f us.", - (1000000.0 * (get_absolute_time_in_fp() - overall_time_before) / (uint64_t)0x100000000)); if (ret == 0) { if (audio_alsa.volume) do_volume(set_volume); @@ -1151,12 +1114,8 @@ static int play(void *buf, int samples) { if ((samples != 0) && (buf != NULL)) { // debug(3, "write %d frames.", samples); - uint64_t maximum_permitted_writing_time = (config.alsa_maximum_write_time * 1000000000); - maximum_permitted_writing_time = (maximum_permitted_writing_time << 32) / 1000000000; - - overall_time_before = get_absolute_time_in_fp(); err = alsa_pcm_write(alsa_handle, buf, samples); - uint64_t writing_time = get_absolute_time_in_fp() - overall_time_before; + stall_monitor_frame_count += samples; if (err < 0) { @@ -1171,13 +1130,6 @@ static int play(void *buf, int samples) { err, samples, snd_strerror(err)); } - if (writing_time > maximum_permitted_writing_time) { - debug(3, "Taking too long to write %d samples. Playing time: %8.2f us, writing time: " - "%8.2f us.", - samples, (1000000.0 * maximum_permitted_writing_time) / (uint64_t)0x100000000, - (1000000.0 * writing_time) / (uint64_t)0x100000000); - } - if (frame_index == 0) { frames_sent_for_playing = samples; } else { @@ -1234,14 +1186,10 @@ static int play(void *buf, int samples) { pthread_cleanup_pop(0); // release the mutex } pthread_setcancelstate(oldState, NULL); - debug(3, "Total playing time to write %d samples: %8.2f us.", samples, - (1000000.0 * (get_absolute_time_in_fp() - overall_time_before) / (uint64_t)0x100000000)); - return ret; } static void flush(void) { - uint64_t overall_time_before = get_absolute_time_in_fp(); // debug(2,"audio_alsa flush called."); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable @@ -1267,8 +1215,6 @@ static void flush(void) { debug_mutex_unlock(&alsa_mutex, 3); pthread_cleanup_pop(0); // release the mutex pthread_setcancelstate(oldState, NULL); - debug(3, "Total playing time to flush: %8.2f us.", - (1000000.0 * (get_absolute_time_in_fp() - overall_time_before) / (uint64_t)0x100000000)); } static void stop(void) { diff --git a/common.h b/common.h index 9e2d88f3..3ac2f1a0 100644 --- a/common.h +++ b/common.h @@ -187,8 +187,7 @@ typedef struct { float loudness_reference_volume_db; int alsa_use_hardware_mute; - double alsa_maximum_interface_response_time; - double alsa_maximum_write_time; + double alsa_maximum_stall_time; #if defined(CONFIG_DBUS_INTERFACE) enum dbus_session_type dbus_service_bus_type; diff --git a/player.c b/player.c index ec7d2adc..42985019 100644 --- a/player.c +++ b/player.c @@ -1079,16 +1079,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { (conn->unfixable_error_reported == 0)) { conn->unfixable_error_reported = 1; if (config.cmd_unfixable) { - warn("Connection %d: An unfixable error has been detected -- output device " - "is stalled. Executing the " - "\"run_this_if_an_unfixable_error_is_detected\" command.", - conn->connection_number); command_execute(config.cmd_unfixable, "output_device_stalled"); } else { - warn("Connection %d: An unfixable error has been detected -- output device " - "is stalled. \"No " - "run_this_if_an_unfixable_error_is_detected\" command provided -- " - "nothing done.", + warn("An unfixable error, \"output_device_stalled\", has been detected.", conn->connection_number); } } diff --git a/rtsp.c b/rtsp.c index 04369ee1..30505008 100644 --- a/rtsp.c +++ b/rtsp.c @@ -293,15 +293,10 @@ void *player_watchdog_thread_code(void *arg) { } else if (conn->watchdog_barks == 3) { if ((config.cmd_unfixable) && (conn->unfixable_error_reported == 0)) { conn->unfixable_error_reported = 1; - warn("Connection %d: An unfixable error has been detected -- can't stop a play " - "session. Executing the " - "\"run_this_if_an_unfixable_error_is_detected\" command.", - conn->connection_number); command_execute(config.cmd_unfixable, "unable_to_cancel_play_session"); } else { - warn("Connection %d: An unfixable error has been detected -- can't stop a play " - "session. \"No " - "run_this_if_an_unfixable_error_is_detected\" command provided -- nothing done.", + warn("Connection %d: An unfixable error, code \"unable_to_cancel_play_session\" has " + "been detected.", conn->connection_number); } } From e454572dc789913d1c536784b02cd9f0683c841d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 11 Dec 2018 11:26:39 +0000 Subject: [PATCH 277/569] Further debug tidy-ups and update the configuration file. --- mdns_avahi.c | 2 +- player.c | 5 +++-- rtsp.c | 3 +-- scripts/shairport-sync.conf | 7 ++++--- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/mdns_avahi.c b/mdns_avahi.c index e1abe286..2d5cf569 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -98,7 +98,7 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn if (dacpid) { dacpid += strlen("iTunes_Ctrl_"); if (strcmp(dacpid, dbs->dacp_id) == 0) { - debug(1, "Client \"%s\"'s DACP port: %u.", dbs->dacp_id, port); + debug(3, "Client \"%s\"'s DACP port: %u.", dbs->dacp_id, port); #ifdef CONFIG_DACP_CLIENT dacp_monitor_port_update_callback(dacpid, port); #endif diff --git a/player.c b/player.c index 42985019..9259aaa4 100644 --- a/player.c +++ b/player.c @@ -1081,8 +1081,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (config.cmd_unfixable) { command_execute(config.cmd_unfixable, "output_device_stalled"); } else { - warn("An unfixable error, \"output_device_stalled\", has been detected.", - conn->connection_number); + warn( + "an unrecoverable error, \"output_device_stalled\", has been detected.", + conn->connection_number); } } } diff --git a/rtsp.c b/rtsp.c index 30505008..12ac99f0 100644 --- a/rtsp.c +++ b/rtsp.c @@ -295,8 +295,7 @@ void *player_watchdog_thread_code(void *arg) { conn->unfixable_error_reported = 1; command_execute(config.cmd_unfixable, "unable_to_cancel_play_session"); } else { - warn("Connection %d: An unfixable error, code \"unable_to_cancel_play_session\" has " - "been detected.", + warn("an unrecoverable error, \"unable_to_cancel_play_session\", has been detected.", conn->connection_number); } } diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 689f55ea..98cc9ba5 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -53,7 +53,9 @@ sessioncontrol = { // run_this_before_play_begins = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line // run_this_after_play_ends = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line -// run_this_if_an_unfixable_error_is_detected = "/full/path/to/application and args"; // if a problem occurs that can't be cleared. It might restart the whole device. +// run_this_if_an_unfixable_error_is_detected = "/full/path/to/application and args"; // if a problem occurs that can't be cleared by Shairport Sync itself, hook a program on here to deal with it. An error code-string is passed as the last argument. +// Many of these "unfixable" problems are caused by malfunctioning output devices, and sometimes it is necessary to restart the whole device to clear the problem. +// You could hook on a program to do this automatically, but beware -- the device may then power off and restart without warning! // wait_for_completion = "no"; // set to "yes" to get Shairport Sync to wait until the "run_this..." applications have terminated before continuing // allow_session_interruption = "no"; // set to "yes" to allow another device to interrupt Shairport Sync while it's playing from an existing audio source // session_timeout = 120; // wait for this number of seconds after a source disappears before terminating the session and becoming available again. @@ -74,8 +76,7 @@ alsa = // buffer_size = ; // Use this optional advanced setting to set the alsa buffer size near to this value // use_mmap_if_available = "yes"; // Use this optional advanced setting to control whether MMAP-based output is used to communicate with the DAC. Default is "yes" // use_hardware_mute_if_available = "no"; // Use this optional advanced setting to control whether the hardware in the DAC is used for muting. Default is "no", for compatibility with other audio players. -// maximum_interface_response_time = 0.001; // Use this optional advanced setting to control how long a response should be waited for on the interface between Shairport Sync and the ALSA subsystem. -// maximum_write_time = 0.015; // Use this optional advanced setting to control how long it should take to write audio data to the ALSA subsystem. +// maximum_stall_time = 0.200; // Use this optional advanced setting to control how long to wait for data to be consumed by the output device before considering it an error. It should never approach 200 ms. }; // Parameters for the "sndio" audio back end. All are optional. From 3535863689a38f4d127a726e3d81411b41cd2156 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 11 Dec 2018 11:28:08 +0000 Subject: [PATCH 278/569] Cleaned up unfixable errors code and examples, updated configuration settings. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 7d209028..6d8a4227 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.3d35], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d36], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From dad5e28208f6ce386493bda890ab6fc199622e26 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 11 Dec 2018 16:25:53 +0000 Subject: [PATCH 279/569] Stay with the standard delay call for the present time. --- audio_alsa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_alsa.c b/audio_alsa.c index 761e06d9..ea712a5e 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1140,7 +1140,7 @@ static int play(void *buf, int samples) { frame_index++; if ((frame_index == start_measurement_from_this_frame) || (frame_index % 32 == 0)) { long fl = 0; - err2 = my_snd_pcm_delay(alsa_handle, &fl); + err2 = snd_pcm_delay(alsa_handle, &fl); if (err2 != 0) { debug(1, "Error %d in delay in play(): \"%s\". Delay reported is %d frames.", err2, snd_strerror(err2), fl); From c9211ef2394cb9fae424fd5d461b88518c6b378e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 11 Dec 2018 16:42:55 +0000 Subject: [PATCH 280/569] Update RELEASENOTES.md --- RELEASENOTES.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 5599a8b0..0fc5e040 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,31 @@ +Version 3.3d36 to Version 3.3d22 +==== + +**Enhancements and Bug Fixes** + +These updates are about stability. + +With great help from [gibman](https://github.com/gibman) — see [#772](https://github.com/mikebrady/shairport-sync/issues/772) for the gory details — myriad issues have been identified and fixed. In particular, [gibman](https://github.com/gibman) shared an automated way of stress-testing Shairport Sync, and this has resulted in the detection of many bugs. + +With due apologies to Shakespeare, we have taken up arms against a sea of troubles, and by opposing we have ended them. It is hoped that the result is considerably more stable and can better withstand the, uh, slings and arrows of outrageous fortune and other hard-to-reproduce scenarios. + +Here is a flavour of some of the issues addressed: +* Replace the existing watchdog, which only offered partial coverage, with a much more robust thread-based watchdog. +* Allow a reported remote processing time of zero. +* Fix a logical error that could cause an endless loop during a flush. +* Ensure a player thread is correctly initialised before allowing it to be cancelled cleanly. +* Ensure the player thread always has a cancellation point so that it should always be possible to ask it to terminate. +* If a play session is interrupted, wait for it to terminate for up to three seconds. +* If a play session is interrupted, ensure the new session uses a different set of UDP ports. This is to ensure that data from the interrupted session – which might be still in transit – doesn't enter the new session. +* Make all ALSA command sequences un-cancellable. This is to ensure that ALSA subsystem will not be left in a partially-initialised state if Shairport Sync terminates abruptly. +* When a connection is terminated abruptly, ensure that all the UDP ports is use are closed properly. +* Impose timeouts on both reading and writing to the supervisory RTSP connection governing a session. +* When closing the RTSP connection due to an error, close it immediately, without waiting for a full TCP handshake, because, if the other end has erred, the handshake may never come. +* Fix a parameter initialisation error in a situation where there is no hardware mixer. + +**New Feature** +* A new `run_this_if_an_unfixable_error_is_detected` (in the `sessioncontrol` group of settings) program hook is provided. At the moment, two conditions can cause this to be activated. The first is if the watchdog is unable to terminate a play session. The second is if the output device stalls for a long period. Both conditions can be caused by malfunctioning DACs. The external program could, for example, reboot the device. + Version 3.3d21 ==== **Enhancements** From eb5046593c35cd03df5e753d496c008501a61b40 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 11 Dec 2018 17:05:12 +0000 Subject: [PATCH 281/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0fc5e040..52ec8a88 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -7,7 +7,7 @@ These updates are about stability. With great help from [gibman](https://github.com/gibman) — see [#772](https://github.com/mikebrady/shairport-sync/issues/772) for the gory details — myriad issues have been identified and fixed. In particular, [gibman](https://github.com/gibman) shared an automated way of stress-testing Shairport Sync, and this has resulted in the detection of many bugs. -With due apologies to Shakespeare, we have taken up arms against a sea of troubles, and by opposing we have ended them. It is hoped that the result is considerably more stable and can better withstand the, uh, slings and arrows of outrageous fortune and other hard-to-reproduce scenarios. +An so, with due apologies to Shakespeare, we have taken up arms against a sea of troubles, and by opposing we have ended them. It is hoped that the result is considerably more stable and can better withstand the, uh, slings and arrows of outrageous fortune and other hard-to-reproduce scenarios. Here is a flavour of some of the issues addressed: * Replace the existing watchdog, which only offered partial coverage, with a much more robust thread-based watchdog. From 530b9501bd7c7f8e2d972bbffedb152e6c0cf223 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 11 Dec 2018 17:07:16 +0000 Subject: [PATCH 282/569] Update RELEASENOTES.md --- RELEASENOTES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 52ec8a88..8b17a7bb 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,10 +1,10 @@ Version 3.3d36 to Version 3.3d22 ==== -**Enhancements and Bug Fixes** - These updates are about stability. +**Enhancements and Bug Fixes** + With great help from [gibman](https://github.com/gibman) — see [#772](https://github.com/mikebrady/shairport-sync/issues/772) for the gory details — myriad issues have been identified and fixed. In particular, [gibman](https://github.com/gibman) shared an automated way of stress-testing Shairport Sync, and this has resulted in the detection of many bugs. An so, with due apologies to Shakespeare, we have taken up arms against a sea of troubles, and by opposing we have ended them. It is hoped that the result is considerably more stable and can better withstand the, uh, slings and arrows of outrageous fortune and other hard-to-reproduce scenarios. From 3cb427338b9dc49b7b85447d215c92e6a604d99d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 12 Dec 2018 14:49:37 +0000 Subject: [PATCH 283/569] Update RELEASENOTES.md --- RELEASENOTES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 8b17a7bb..f7d52ef9 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -7,7 +7,7 @@ These updates are about stability. With great help from [gibman](https://github.com/gibman) — see [#772](https://github.com/mikebrady/shairport-sync/issues/772) for the gory details — myriad issues have been identified and fixed. In particular, [gibman](https://github.com/gibman) shared an automated way of stress-testing Shairport Sync, and this has resulted in the detection of many bugs. -An so, with due apologies to Shakespeare, we have taken up arms against a sea of troubles, and by opposing we have ended them. It is hoped that the result is considerably more stable and can better withstand the, uh, slings and arrows of outrageous fortune and other hard-to-reproduce scenarios. +And so, with apologies to Shakespeare, we have taken up arms against a sea of troubles, and by opposing we have ended them. It is hoped that the result is considerably more stable and can better withstand the, uh, slings and arrows of outrageous fortune and other hard-to-reproduce scenarios. Here is a flavour of some of the issues addressed: * Replace the existing watchdog, which only offered partial coverage, with a much more robust thread-based watchdog. @@ -22,6 +22,7 @@ Here is a flavour of some of the issues addressed: * Impose timeouts on both reading and writing to the supervisory RTSP connection governing a session. * When closing the RTSP connection due to an error, close it immediately, without waiting for a full TCP handshake, because, if the other end has erred, the handshake may never come. * Fix a parameter initialisation error in a situation where there is no hardware mixer. +* Fix an MQTT-related crash by ignoring unrecognised commands. **New Feature** * A new `run_this_if_an_unfixable_error_is_detected` (in the `sessioncontrol` group of settings) program hook is provided. At the moment, two conditions can cause this to be activated. The first is if the watchdog is unable to terminate a play session. The second is if the output device stalls for a long period. Both conditions can be caused by malfunctioning DACs. The external program could, for example, reboot the device. From 307d9c3b640a38d5ffac41793cc98473fc69b3b1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 12 Dec 2018 14:50:25 +0000 Subject: [PATCH 284/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f7d52ef9..3012d12a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -7,7 +7,7 @@ These updates are about stability. With great help from [gibman](https://github.com/gibman) — see [#772](https://github.com/mikebrady/shairport-sync/issues/772) for the gory details — myriad issues have been identified and fixed. In particular, [gibman](https://github.com/gibman) shared an automated way of stress-testing Shairport Sync, and this has resulted in the detection of many bugs. -And so, with apologies to Shakespeare, we have taken up arms against a sea of troubles, and by opposing we have ended them. It is hoped that the result is considerably more stable and can better withstand the, uh, slings and arrows of outrageous fortune and other hard-to-reproduce scenarios. +And so, with apologies to Shakespeare, we have taken up arms against a sea of troubles, and by opposing we have ended them. It is hoped that the result is considerably more stable and can better withstand the, uh, slings and arrows of outrageous fortune. Here is a flavour of some of the issues addressed: * Replace the existing watchdog, which only offered partial coverage, with a much more robust thread-based watchdog. From b40b80247f628b7b7c978efd95928a8c7ea921d6 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 12 Dec 2018 21:21:54 +0000 Subject: [PATCH 285/569] Fix a compilation error w.r.t. debuglev and fix a complation warning. --- FFTConvolver/convolver.cpp | 2 +- common.c | 2 +- common.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/FFTConvolver/convolver.cpp b/FFTConvolver/convolver.cpp index 4589c8ed..55ad42f4 100644 --- a/FFTConvolver/convolver.cpp +++ b/FFTConvolver/convolver.cpp @@ -40,7 +40,7 @@ void convolver_init(const char* filename, int max_length) float buffer_l[size]; float buffer_r[size]; - int i; + unsigned int i; for (i=0; i Date: Wed, 12 Dec 2018 21:46:59 +0000 Subject: [PATCH 286/569] Fix a compilation error w.r.t. debuglev and fix a complation warning. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 6d8a4227..512156ef 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.3d36], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d37], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From a8fe3bd13b13d6da771916342d62fc0c4f24b8b6 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 12 Dec 2018 22:07:55 +0000 Subject: [PATCH 287/569] Remove connection with common.h -- works properly. --- FFTConvolver/convolver.cpp | 5 ++--- common.c | 2 +- common.h | 3 ++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/FFTConvolver/convolver.cpp b/FFTConvolver/convolver.cpp index 55ad42f4..f115cf85 100644 --- a/FFTConvolver/convolver.cpp +++ b/FFTConvolver/convolver.cpp @@ -4,10 +4,9 @@ #include #include "FFTConvolver.h" #include "Utilities.h" -extern "C" { -#include "../common.h" -} +extern "C" void die(const char *format, ...); +extern "C" void debug(int level, const char *format, ...); static fftconvolver::FFTConvolver convolver_l; static fftconvolver::FFTConvolver convolver_r; diff --git a/common.c b/common.c index 46654290..053abbe3 100644 --- a/common.c +++ b/common.c @@ -90,7 +90,7 @@ static volatile int requested_connection_state_to_output = 1; shairport_cfg config; -static volatile int debuglev = 0; +volatile int debuglev = 0; sigset_t pselect_sigset; diff --git a/common.h b/common.h index 0d031d8b..243052b6 100644 --- a/common.h +++ b/common.h @@ -259,7 +259,8 @@ int64_t ranarray64i(); void resetFreeUDPPort(); uint16_t nextFreeUDPPort(); -static volatile int debuglev; +volatile int debuglev; + void die(const char *format, ...); void warn(const char *format, ...); void inform(const char *format, ...); From 87f3de2e9df8881c8b1e9b48df44895b897bc6bd Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 12 Dec 2018 22:21:40 +0000 Subject: [PATCH 288/569] Update RELEASENOTES.md --- RELEASENOTES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 3012d12a..f9c363bd 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,4 +1,4 @@ -Version 3.3d36 to Version 3.3d22 +Version 3.3d37 to Version 3.3d22 ==== These updates are about stability. @@ -23,6 +23,7 @@ Here is a flavour of some of the issues addressed: * When closing the RTSP connection due to an error, close it immediately, without waiting for a full TCP handshake, because, if the other end has erred, the handshake may never come. * Fix a parameter initialisation error in a situation where there is no hardware mixer. * Fix an MQTT-related crash by ignoring unrecognised commands. +* Fix a compilation error and a warning when using the `--with-convolution` configuration option. **New Feature** * A new `run_this_if_an_unfixable_error_is_detected` (in the `sessioncontrol` group of settings) program hook is provided. At the moment, two conditions can cause this to be activated. The first is if the watchdog is unable to terminate a play session. The second is if the output device stalls for a long period. Both conditions can be caused by malfunctioning DACs. The external program could, for example, reboot the device. From 7cc434e989b2d58ac610f4e44008f77d32679dac Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 13 Dec 2018 14:47:52 +0000 Subject: [PATCH 289/569] Experimentally, add the ability to prevent the DAC making popping noises by feeding it with silence instead of allowing it to stop. --- audio_alsa.c | 113 +++++++++++++++++++++++++++++++++--- rtsp.c | 2 +- scripts/shairport-sync.conf | 1 + 3 files changed, 106 insertions(+), 10 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 761e06d9..dca29733 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -47,6 +47,7 @@ static void flush(void); int delay(long *the_delay); void do_mute(int request); int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played); +void *alsa_buffer_monitor_thread_code(void *arg); static void volume(double vol); void do_volume(double vol); @@ -74,6 +75,8 @@ audio_output audio_alsa = { static pthread_mutex_t alsa_mutex = PTHREAD_MUTEX_INITIALIZER; +pthread_t alsa_buffer_monitor_thread; + // for tracking how long the output device has stalled uint64_t stall_monitor_start_time; // zero if not initialised / not started / zeroed by flush long stall_monitor_frame_count; // set to delay at start of time, incremented by any writes @@ -82,6 +85,7 @@ uint64_t stall_monitor_error_threshold; // if the time is longer than this, it's static snd_output_t *output = NULL; static unsigned int desired_sample_rate; static enum sps_format_t sample_format; +int frame_size; // in bytes for interleaved stereo static snd_pcm_t *alsa_handle = NULL; static snd_pcm_hw_params_t *alsa_params = NULL; @@ -100,6 +104,7 @@ static char *alsa_mix_ctrl = "Master"; static int alsa_mix_index = 0; static int hardware_mixer = 0; static int has_softvol = 0; +int keep_dac_busy = 0; // true if the dac must be kept busy with samples static int volume_set_request = 0; // set when an external request is made to set the volume. int mute_request_pending = 0; // set when an external request is made to mute or unmute. @@ -390,6 +395,10 @@ static int init(int argc, char **argv) { config.alsa_maximum_stall_time = dvalue; } } + + // Get the optional "Keep DAC Busy setting" + config_set_lookup_bool(config.cfg, "alsa.disable_standby_mode", &keep_dac_busy); + debug(1, "alsa: disable_standby_mode is set to \"%s\".", keep_dac_busy ? "yes" : "no"); } optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour @@ -519,19 +528,39 @@ static int init(int argc, char **argv) { close_mixer(); } debug_mutex_unlock(&alsa_mutex, 3); // release the mutex + pthread_cleanup_pop(0); pthread_setcancelstate(oldState, NULL); } else { debug(1, "alsa: no hardware mixer selected."); } - alsa_mix_handle = NULL; + + // so, now, if the option to keep the DAC running has been selected, start a thread to monitor the + // length of the queue + // if the queue gets too short, stuff it with silence + + desired_sample_rate = config.output_rate; + sample_format = config.output_format; + + if (keep_dac_busy) + pthread_create(&alsa_buffer_monitor_thread, NULL, &alsa_buffer_monitor_thread_code, NULL); + return response; } static void deinit(void) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable // debug(2,"audio_alsa deinit called."); stop(); + if (keep_dac_busy) { + debug(1, "Cancel buffer monitor thread."); + pthread_cancel(alsa_buffer_monitor_thread); + debug(1, "Join buffer monitor thread."); + pthread_join(alsa_buffer_monitor_thread, NULL); + } + pthread_setcancelstate(oldState, NULL); } // assuming pthread cancellation is disabled @@ -605,27 +634,35 @@ int actual_open_alsa_device(void) { switch (sample_format) { case SPS_FORMAT_S8: sf = SND_PCM_FORMAT_S8; + frame_size = 2; break; case SPS_FORMAT_U8: sf = SND_PCM_FORMAT_U8; + frame_size = 2; break; case SPS_FORMAT_S16: sf = SND_PCM_FORMAT_S16; + frame_size = 4; break; case SPS_FORMAT_S24: sf = SND_PCM_FORMAT_S24; + frame_size = 8; break; case SPS_FORMAT_S24_3LE: sf = SND_PCM_FORMAT_S24_3LE; + frame_size = 6; break; case SPS_FORMAT_S24_3BE: sf = SND_PCM_FORMAT_S24_3BE; + frame_size = 6; break; case SPS_FORMAT_S32: sf = SND_PCM_FORMAT_S32; + frame_size = 8; break; default: sf = SND_PCM_FORMAT_S16; // this is just to quieten a compiler warning + frame_size = 4; debug(1, "Unsupported output format at audio_alsa.c"); return -1; } @@ -1095,7 +1132,9 @@ static int play(void *buf, int samples) { pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0); // snd_pcm_sframes_t current_delay = 0; int err, err2; - if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) { + if ((snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) || + (snd_pcm_state(alsa_handle) == SND_PCM_STATE_OPEN) || + (snd_pcm_state(alsa_handle) == SND_PCM_STATE_SETUP)) { if ((err = snd_pcm_prepare(alsa_handle))) { debug(1, "Error preparing after underrun: \"%s\".", snd_strerror(err)); err = snd_pcm_recover(alsa_handle, err, 1); @@ -1201,16 +1240,17 @@ static void flush(void) { stall_monitor_start_time = 0; if ((derr = snd_pcm_drop(alsa_handle))) debug(1, "Error %d (\"%s\") dropping output device.", derr, snd_strerror(derr)); + if (keep_dac_busy == 0) { + if ((derr = snd_pcm_hw_free(alsa_handle))) + debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr)); - if ((derr = snd_pcm_hw_free(alsa_handle))) - debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr)); - - // flush also closes the device - if ((derr = snd_pcm_close(alsa_handle))) - debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr)); + // flush also closes the device + if ((derr = snd_pcm_close(alsa_handle))) + debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr)); + alsa_handle = NULL; + } frame_index = 0; measurement_data_is_valid = 0; - alsa_handle = NULL; } debug_mutex_unlock(&alsa_mutex, 3); pthread_cleanup_pop(0); // release the mutex @@ -1352,3 +1392,58 @@ void do_mute(int mute_state_requested) { mute_request_pending = 0; pthread_setcancelstate(oldState, NULL); } + +void alsa_buffer_monitor_thread_cleanup_function(__attribute__((unused)) void *arg) { + debug(1, "alsa: alsa_buffer_monitor_thread_cleanup_function called."); +} + +void *alsa_buffer_monitor_thread_code(void *arg) { + // debug(1,"alsa: alsa_buffer_monitor_thread_code called."); + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable + int ret = 0; + if (alsa_handle == NULL) { + + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); + ret = actual_open_alsa_device(); + if (ret == 0) { + if (audio_alsa.volume) + do_volume(set_volume); + if (audio_alsa.mute) + do_mute(0); + } + + debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(0); // release the mutex + } + pthread_cleanup_push(alsa_buffer_monitor_thread_cleanup_function, arg); + pthread_setcancelstate(oldState, NULL); + + int sleep_time_ms = 60; + int frames_of_silence = desired_sample_rate * sleep_time_ms / 1000; + size_t size_of_silence_buffer = frames_of_silence * frame_size; + // debug(1,"Silence buffer length: %u bytes.",size_of_silence_buffer); + void *silence = malloc(size_of_silence_buffer); + if (silence == NULL) { + debug(1, "Failed to allocate memory for a silent frame buffer."); + } else { + } + + long buffer_size; + int reply; + while (1) { + usleep((sleep_time_ms * 1000) / 2); + reply = delay(&buffer_size); + if (reply != 0) + debug(1, "error %d during buffer monitoring of delay", reply); + if (buffer_size < (((int)desired_sample_rate * sleep_time_ms) / (1000))) { + // debug(1,"Writing %d frames of silence because only %lu frames remain in the + // buffer.",frames_of_silence,buffer_size); + memset(silence, 0, size_of_silence_buffer); + play(silence, frames_of_silence); + } + pthread_testcancel(); + } + pthread_cleanup_pop(0); + pthread_exit(NULL); +} diff --git a/rtsp.c b/rtsp.c index 12ac99f0..bdd686a0 100644 --- a/rtsp.c +++ b/rtsp.c @@ -2137,7 +2137,7 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { debug(3, "Connection %d: Checking play lock.", conn->connection_number); debug_mutex_lock(&playing_conn_lock, 1000000, 3); // get it if (playing_conn == conn) { // if it's ours - debug(2, "Connection %d: Unlocking play lock.", conn->connection_number); + debug(1, "Connection %d: Unlocking play lock -- end of play session.", conn->connection_number); playing_conn = NULL; // let it go } debug_mutex_unlock(&playing_conn_lock, 3); diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 98cc9ba5..aca41a74 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -77,6 +77,7 @@ alsa = // use_mmap_if_available = "yes"; // Use this optional advanced setting to control whether MMAP-based output is used to communicate with the DAC. Default is "yes" // use_hardware_mute_if_available = "no"; // Use this optional advanced setting to control whether the hardware in the DAC is used for muting. Default is "no", for compatibility with other audio players. // maximum_stall_time = 0.200; // Use this optional advanced setting to control how long to wait for data to be consumed by the output device before considering it an error. It should never approach 200 ms. +// standby_mode = "yes"; // Some DACs make small "popping" noises when they go in and out of standby mode. Set this to "no" to keep the DAC active all the time. }; // Parameters for the "sndio" audio back end. All are optional. From b0fe51788e0037b9051d5235d98b6aacc0e5e9d6 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 13 Dec 2018 14:48:21 +0000 Subject: [PATCH 290/569] Experimentally, add the ability to prevent the DAC making popping noises by feeding it with silence instead of allowing it to stop. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 6d8a4227..5d3937ca 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.3d36], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d38], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From bbc0ea1ab96135796f60eddf8d0aedacf096bf15 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 13 Dec 2018 15:42:51 +0000 Subject: [PATCH 291/569] Update shairport-sync.conf --- scripts/shairport-sync.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index aca41a74..39db8077 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -77,7 +77,7 @@ alsa = // use_mmap_if_available = "yes"; // Use this optional advanced setting to control whether MMAP-based output is used to communicate with the DAC. Default is "yes" // use_hardware_mute_if_available = "no"; // Use this optional advanced setting to control whether the hardware in the DAC is used for muting. Default is "no", for compatibility with other audio players. // maximum_stall_time = 0.200; // Use this optional advanced setting to control how long to wait for data to be consumed by the output device before considering it an error. It should never approach 200 ms. -// standby_mode = "yes"; // Some DACs make small "popping" noises when they go in and out of standby mode. Set this to "no" to keep the DAC active all the time. +// disable_standby_mode = "no"; // Some DACs make small "popping" noises when they go in and out of standby mode. Set this to "yes" to keep the DAC active all the time. }; // Parameters for the "sndio" audio back end. All are optional. From 5dd9fbc4fb5e2e4984bf7e6eb7bd0a4701fcca45 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 13 Dec 2018 16:59:18 +0000 Subject: [PATCH 292/569] Don't drop the audio during a flush -- it creates a clunk. --- audio_alsa.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index dca29733..21e1c5e5 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1238,9 +1238,9 @@ static void flush(void) { if (alsa_handle) { stall_monitor_start_time = 0; - if ((derr = snd_pcm_drop(alsa_handle))) - debug(1, "Error %d (\"%s\") dropping output device.", derr, snd_strerror(derr)); if (keep_dac_busy == 0) { + if ((derr = snd_pcm_drop(alsa_handle))) + debug(1, "Error %d (\"%s\") dropping output device.", derr, snd_strerror(derr)); if ((derr = snd_pcm_hw_free(alsa_handle))) debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr)); @@ -1273,9 +1273,9 @@ static void parameters(audio_parameters *info) { } void do_volume(double vol) { // caller is assumed to have the alsa_mutex when using this function + debug(3, "Setting volume db to %f.", vol); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable - debug(3, "Setting volume db to %f.", vol); set_volume = vol; if (volume_set_request && (open_mixer() == 1)) { if (has_softvol) { @@ -1346,7 +1346,7 @@ static void mute(int mute_state_requested) { } void do_mute(int mute_state_requested) { - + debug(3,"Setting mute to %d.",mute_state_requested); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable @@ -1419,7 +1419,7 @@ void *alsa_buffer_monitor_thread_code(void *arg) { pthread_cleanup_push(alsa_buffer_monitor_thread_cleanup_function, arg); pthread_setcancelstate(oldState, NULL); - int sleep_time_ms = 60; + int sleep_time_ms = 80; int frames_of_silence = desired_sample_rate * sleep_time_ms / 1000; size_t size_of_silence_buffer = frames_of_silence * frame_size; // debug(1,"Silence buffer length: %u bytes.",size_of_silence_buffer); From 4046c0e73d477abb9a34022c16c3766839fad9d1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 14 Dec 2018 15:07:00 +0000 Subject: [PATCH 293/569] Fix dither, add dither to silences during lead-in, interpolatied frames and keeping out of standby, only if the output device doesn't use a mixer. Check configuration at initialisation. --- audio_alsa.c | 777 +++++++++++++++++++++++++++------------------------ common.c | 126 ++++++++- common.h | 3 +- player.c | 83 ++++-- player.h | 2 + rtsp.c | 12 +- 6 files changed, 592 insertions(+), 411 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 21e1c5e5..ed0a54a2 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -104,7 +104,9 @@ static char *alsa_mix_ctrl = "Master"; static int alsa_mix_index = 0; static int hardware_mixer = 0; static int has_softvol = 0; + int keep_dac_busy = 0; // true if the dac must be kept busy with samples +int64_t dither_random_number_store = 0; static int volume_set_request = 0; // set when an external request is made to set the volume. int mute_request_pending = 0; // set when an external request is made to mute or unmute. @@ -208,359 +210,17 @@ void do_snd_mixer_selem_set_playback_dB_all(snd_mixer_elem_t *mix_elem, double v } } -static int init(int argc, char **argv) { - // for debugging - snd_output_stdio_attach(&output, stdout, 0); +void actual_close_alsa_device() { + if (alsa_handle) { + int derr; + if ((derr = snd_pcm_hw_free(alsa_handle))) + debug(1, "Error %d (\"%s\") freeing the output device hardware while closing it.", derr, + snd_strerror(derr)); - // debug(2,"audio_alsa init called."); - int response = 0; // this will be what we return to the caller. - const char *str; - int value; - // double dvalue; - - // set up default values first - set_period_size_request = 0; - set_buffer_size_request = 0; - config.alsa_use_hardware_mute = 0; // don't use it by default - - config.audio_backend_latency_offset = 0; - config.audio_backend_buffer_desired_length = 0.15; - - config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem - - // get settings from settings file first, allow them to be overridden by - // command line options - - // do the "general" audio options. Note, these options are in the "general" stanza! - parse_general_audio_options(); - - if (config.cfg != NULL) { - double dvalue; - - /* Get the Output Device Name. */ - if (config_lookup_string(config.cfg, "alsa.output_device", &str)) { - alsa_out_dev = (char *)str; - } - - /* Get the Mixer Type setting. */ - - if (config_lookup_string(config.cfg, "alsa.mixer_type", &str)) { - inform("The alsa mixer_type setting is deprecated and has been ignored. " - "FYI, using the \"mixer_control_name\" setting automatically " - "chooses a hardware mixer."); - } - - /* Get the Mixer Device Name. */ - if (config_lookup_string(config.cfg, "alsa.mixer_device", &str)) { - alsa_mix_dev = (char *)str; - } - - /* Get the Mixer Control Name. */ - if (config_lookup_string(config.cfg, "alsa.mixer_control_name", &str)) { - alsa_mix_ctrl = (char *)str; - hardware_mixer = 1; - } - - /* Get the disable_synchronization setting. */ - if (config_lookup_string(config.cfg, "alsa.disable_synchronization", &str)) { - if (strcasecmp(str, "no") == 0) - config.no_sync = 0; - else if (strcasecmp(str, "yes") == 0) - config.no_sync = 1; - else { - warn("Invalid disable_synchronization option choice \"%s\". It should be \"yes\" or " - "\"no\". It is set to \"no\"."); - config.no_sync = 0; - } - } - - /* Get the mute_using_playback_switch setting. */ - if (config_lookup_string(config.cfg, "alsa.mute_using_playback_switch", &str)) { - inform("The alsa \"mute_using_playback_switch\" setting is deprecated. " - "Please use the \"use_hardware_mute_if_available\" setting instead."); - if (strcasecmp(str, "no") == 0) - config.alsa_use_hardware_mute = 0; - else if (strcasecmp(str, "yes") == 0) - config.alsa_use_hardware_mute = 1; - else { - warn("Invalid mute_using_playback_switch option choice \"%s\". It should be \"yes\" or " - "\"no\". It is set to \"no\"."); - config.alsa_use_hardware_mute = 0; - } - } - - /* Get the use_hardware_mute_if_available setting. */ - if (config_lookup_string(config.cfg, "alsa.use_hardware_mute_if_available", &str)) { - if (strcasecmp(str, "no") == 0) - config.alsa_use_hardware_mute = 0; - else if (strcasecmp(str, "yes") == 0) - config.alsa_use_hardware_mute = 1; - else { - warn("Invalid use_hardware_mute_if_available option choice \"%s\". It should be \"yes\" or " - "\"no\". It is set to \"no\"."); - config.alsa_use_hardware_mute = 0; - } - } - - /* Get the output format, using the same names as aplay does*/ - if (config_lookup_string(config.cfg, "alsa.output_format", &str)) { - if (strcasecmp(str, "S16") == 0) - config.output_format = SPS_FORMAT_S16; - else if (strcasecmp(str, "S24") == 0) - config.output_format = SPS_FORMAT_S24; - else if (strcasecmp(str, "S24_3LE") == 0) - config.output_format = SPS_FORMAT_S24_3LE; - else if (strcasecmp(str, "S24_3BE") == 0) - config.output_format = SPS_FORMAT_S24_3BE; - else if (strcasecmp(str, "S32") == 0) - config.output_format = SPS_FORMAT_S32; - else if (strcasecmp(str, "U8") == 0) - config.output_format = SPS_FORMAT_U8; - else if (strcasecmp(str, "S8") == 0) - config.output_format = SPS_FORMAT_S8; - else { - warn("Invalid output format \"%s\". It should be \"U8\", \"S8\", \"S16\", \"S24\", " - "\"S24_3LE\", \"S24_3BE\" or " - "\"S32\". It is set to \"S16\".", - str); - config.output_format = SPS_FORMAT_S16; - } - } - - /* Get the output rate, which must be a multiple of 44,100*/ - if (config_lookup_int(config.cfg, "alsa.output_rate", &value)) { - debug(1, "alsa output rate is %d frames per second", value); - switch (value) { - case 44100: - case 88200: - case 176400: - case 352800: - config.output_rate = value; - break; - default: - warn("Invalid output rate \"%d\". It should be a multiple of 44,100 up to 352,800. It is " - "set to 44,100", - value); - config.output_rate = 44100; - } - } - - /* Get the use_mmap_if_available setting. */ - if (config_lookup_string(config.cfg, "alsa.use_mmap_if_available", &str)) { - if (strcasecmp(str, "no") == 0) - config.no_mmap = 1; - else if (strcasecmp(str, "yes") == 0) - config.no_mmap = 0; - else { - warn("Invalid use_mmap_if_available option choice \"%s\". It should be \"yes\" or \"no\". " - "It is set to \"yes\"."); - config.no_mmap = 0; - } - } - /* Get the optional period size value */ - if (config_lookup_int(config.cfg, "alsa.period_size", &value)) { - set_period_size_request = 1; - debug(1, "Value read for period size is %d.", value); - if (value < 0) { - warn("Invalid alsa period size setting \"%d\". It " - "must be greater than 0. No setting is made.", - value); - set_period_size_request = 0; - } else { - period_size_requested = value; - } - } - - /* Get the optional buffer size value */ - if (config_lookup_int(config.cfg, "alsa.buffer_size", &value)) { - set_buffer_size_request = 1; - debug(1, "Value read for buffer size is %d.", value); - if (value < 0) { - warn("Invalid alsa buffer size setting \"%d\". It " - "must be greater than 0. No setting is made.", - value); - set_buffer_size_request = 0; - } else { - buffer_size_requested = value; - } - } - - /* Get the optional alsa_maximum_stall_time setting. */ - if (config_lookup_float(config.cfg, "alsa.maximum_stall_time", &dvalue)) { - if (dvalue < 0.0) { - warn("Invalid alsa maximum write time setting \"%f\". It " - "must be greater than 0. Default is \"%f\". No setting is made.", - dvalue, config.alsa_maximum_stall_time); - } else { - config.alsa_maximum_stall_time = dvalue; - } - } - - // Get the optional "Keep DAC Busy setting" - config_set_lookup_bool(config.cfg, "alsa.disable_standby_mode", &keep_dac_busy); - debug(1, "alsa: disable_standby_mode is set to \"%s\".", keep_dac_busy ? "yes" : "no"); + if ((derr = snd_pcm_close(alsa_handle))) + debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr)); + alsa_handle = NULL; } - - optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour - argv--; // so we shift the arguments to satisfy getopt() - argc++; - // some platforms apparently require optreset = 1; - which? - int opt; - while ((opt = getopt(argc, argv, "d:t:m:c:i:")) > 0) { - switch (opt) { - case 'd': - alsa_out_dev = optarg; - break; - - case 't': - inform("The alsa backend -t option is deprecated and has been ignored. " - "FYI, using the -c option automatically chooses a hardware " - "mixer."); - break; - - case 'm': - alsa_mix_dev = optarg; - break; - case 'c': - alsa_mix_ctrl = optarg; - hardware_mixer = 1; - break; - case 'i': - alsa_mix_index = strtol(optarg, NULL, 10); - break; - default: - warn("Invalid audio option \"-%c\" specified -- ignored.", opt); - help(); - } - } - - if (optind < argc) { - warn("Invalid audio argument: \"%s\" -- ignored", argv[optind]); - } - - debug(1, "alsa: output device name is \"%s\".", alsa_out_dev); - - if (hardware_mixer) { - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable - - if (alsa_mix_dev == NULL) - alsa_mix_dev = alsa_out_dev; - - // Now, start trying to initialise the alsa device with the settings obtained - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 1000, 1); - if (open_mixer() == 1) { - if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) < - 0) - debug(1, "Can't read mixer's [linear] min and max volumes."); - else { - if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb, - &alsa_mix_maxdb) == 0) { - - audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff - audio_alsa.parameters = ¶meters; // likewise the parameters stuff - if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) { - // For instance, the Raspberry Pi does this - debug(1, "Lowest dB value is a mute"); - mixer_volume_setting_gives_mute = 1; - alsa_mix_mute = SND_CTL_TLV_DB_GAIN_MUTE; // this may not be necessary -- it's always - // going to be SND_CTL_TLV_DB_GAIN_MUTE, right? - // debug(1, "Try minimum volume + 1 as lowest true attenuation value"); - if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1, - &alsa_mix_mindb) != 0) - debug(1, "Can't get dB value corresponding to a minimum volume + 1."); - } - debug(1, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0, - (1.0 * alsa_mix_maxdb) / 100.0); - } else { - // use the linear scale and do the db conversion ourselves - warn("The hardware mixer specified -- \"%s\" -- does not have " - "a dB volume scale.", - alsa_mix_ctrl); - - if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) { - warn("Cannot open control \"%s\"", alsa_mix_dev); - response = -1; - } - if (snd_ctl_elem_id_malloc(&elem_id) < 0) { - debug(1, "Cannot allocate memory for control \"%s\"", alsa_mix_dev); - elem_id = NULL; - response = -2; - } else { - snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_MIXER); - snd_ctl_elem_id_set_name(elem_id, alsa_mix_ctrl); - - if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) { - debug(1, "alsa: hardware mixer \"%s\" selected, with dB volume from %f to %f.", - alsa_mix_ctrl, (1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0); - has_softvol = 1; - audio_alsa.volume = - &volume; // insert the volume function now we know it can do dB stuff - audio_alsa.parameters = ¶meters; // likewise the parameters stuff - } else { - debug(1, "Cannot get the dB range from the volume control \"%s\"", alsa_mix_ctrl); - } - } - /* - debug(1, "Min and max volumes are %d and - %d.",alsa_mix_minv,alsa_mix_maxv); - alsa_mix_maxdb = 0; - if ((alsa_mix_maxv!=0) && (alsa_mix_minv!=0)) - alsa_mix_mindb = - -20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0)); - else if (alsa_mix_maxv!=0) - alsa_mix_mindb = -20*100*log10(alsa_mix_maxv*1.0); - audio_alsa.volume = &linear_volume; // insert the linear volume function - audio_alsa.parameters = ¶meters; // likewise the parameters stuff - debug(1,"Max and min dB calculated are %d and - %d.",alsa_mix_maxdb,alsa_mix_mindb); - */ - } - } - if (((config.alsa_use_hardware_mute == 1) && - (snd_mixer_selem_has_playback_switch(alsa_mix_elem))) || - mixer_volume_setting_gives_mute) { - audio_alsa.mute = &mute; // insert the mute function now we know it can do muting stuff - // debug(1, "Has mixer and mute ability we will use."); - } else { - // debug(1, "Has mixer but not using hardware mute."); - } - close_mixer(); - } - debug_mutex_unlock(&alsa_mutex, 3); // release the mutex - - pthread_cleanup_pop(0); - pthread_setcancelstate(oldState, NULL); - } else { - debug(1, "alsa: no hardware mixer selected."); - } - alsa_mix_handle = NULL; - - // so, now, if the option to keep the DAC running has been selected, start a thread to monitor the - // length of the queue - // if the queue gets too short, stuff it with silence - - desired_sample_rate = config.output_rate; - sample_format = config.output_format; - - if (keep_dac_busy) - pthread_create(&alsa_buffer_monitor_thread, NULL, &alsa_buffer_monitor_thread_code, NULL); - - return response; -} - -static void deinit(void) { - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable - // debug(2,"audio_alsa deinit called."); - stop(); - if (keep_dac_busy) { - debug(1, "Cancel buffer monitor thread."); - pthread_cancel(alsa_buffer_monitor_thread); - debug(1, "Join buffer monitor thread."); - pthread_join(alsa_buffer_monitor_thread, NULL); - } - pthread_setcancelstate(oldState, NULL); } // assuming pthread cancellation is disabled @@ -950,6 +610,370 @@ int open_alsa_device(void) { return result; } +static int init(int argc, char **argv) { + // for debugging + snd_output_stdio_attach(&output, stdout, 0); + + // debug(2,"audio_alsa init called."); + int response = 0; // this will be what we return to the caller. + const char *str; + int value; + // double dvalue; + + // set up default values first + set_period_size_request = 0; + set_buffer_size_request = 0; + config.alsa_use_hardware_mute = 0; // don't use it by default + + config.audio_backend_latency_offset = 0; + config.audio_backend_buffer_desired_length = 0.15; + + config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem + + // get settings from settings file first, allow them to be overridden by + // command line options + + // do the "general" audio options. Note, these options are in the "general" stanza! + parse_general_audio_options(); + + if (config.cfg != NULL) { + double dvalue; + + /* Get the Output Device Name. */ + if (config_lookup_string(config.cfg, "alsa.output_device", &str)) { + alsa_out_dev = (char *)str; + } + + /* Get the Mixer Type setting. */ + + if (config_lookup_string(config.cfg, "alsa.mixer_type", &str)) { + inform("The alsa mixer_type setting is deprecated and has been ignored. " + "FYI, using the \"mixer_control_name\" setting automatically " + "chooses a hardware mixer."); + } + + /* Get the Mixer Device Name. */ + if (config_lookup_string(config.cfg, "alsa.mixer_device", &str)) { + alsa_mix_dev = (char *)str; + } + + /* Get the Mixer Control Name. */ + if (config_lookup_string(config.cfg, "alsa.mixer_control_name", &str)) { + alsa_mix_ctrl = (char *)str; + hardware_mixer = 1; + } + + /* Get the disable_synchronization setting. */ + if (config_lookup_string(config.cfg, "alsa.disable_synchronization", &str)) { + if (strcasecmp(str, "no") == 0) + config.no_sync = 0; + else if (strcasecmp(str, "yes") == 0) + config.no_sync = 1; + else { + warn("Invalid disable_synchronization option choice \"%s\". It should be \"yes\" or " + "\"no\". It is set to \"no\"."); + config.no_sync = 0; + } + } + + /* Get the mute_using_playback_switch setting. */ + if (config_lookup_string(config.cfg, "alsa.mute_using_playback_switch", &str)) { + inform("The alsa \"mute_using_playback_switch\" setting is deprecated. " + "Please use the \"use_hardware_mute_if_available\" setting instead."); + if (strcasecmp(str, "no") == 0) + config.alsa_use_hardware_mute = 0; + else if (strcasecmp(str, "yes") == 0) + config.alsa_use_hardware_mute = 1; + else { + warn("Invalid mute_using_playback_switch option choice \"%s\". It should be \"yes\" or " + "\"no\". It is set to \"no\"."); + config.alsa_use_hardware_mute = 0; + } + } + + /* Get the use_hardware_mute_if_available setting. */ + if (config_lookup_string(config.cfg, "alsa.use_hardware_mute_if_available", &str)) { + if (strcasecmp(str, "no") == 0) + config.alsa_use_hardware_mute = 0; + else if (strcasecmp(str, "yes") == 0) + config.alsa_use_hardware_mute = 1; + else { + warn("Invalid use_hardware_mute_if_available option choice \"%s\". It should be \"yes\" or " + "\"no\". It is set to \"no\"."); + config.alsa_use_hardware_mute = 0; + } + } + + /* Get the output format, using the same names as aplay does*/ + if (config_lookup_string(config.cfg, "alsa.output_format", &str)) { + if (strcasecmp(str, "S16") == 0) + config.output_format = SPS_FORMAT_S16; + else if (strcasecmp(str, "S24") == 0) + config.output_format = SPS_FORMAT_S24; + else if (strcasecmp(str, "S24_3LE") == 0) + config.output_format = SPS_FORMAT_S24_3LE; + else if (strcasecmp(str, "S24_3BE") == 0) + config.output_format = SPS_FORMAT_S24_3BE; + else if (strcasecmp(str, "S32") == 0) + config.output_format = SPS_FORMAT_S32; + else if (strcasecmp(str, "U8") == 0) + config.output_format = SPS_FORMAT_U8; + else if (strcasecmp(str, "S8") == 0) + config.output_format = SPS_FORMAT_S8; + else { + warn("Invalid output format \"%s\". It should be \"U8\", \"S8\", \"S16\", \"S24\", " + "\"S24_3LE\", \"S24_3BE\" or " + "\"S32\". It is set to \"S16\".", + str); + config.output_format = SPS_FORMAT_S16; + } + } + + /* Get the output rate, which must be a multiple of 44,100*/ + if (config_lookup_int(config.cfg, "alsa.output_rate", &value)) { + debug(1, "alsa output rate is %d frames per second", value); + switch (value) { + case 44100: + case 88200: + case 176400: + case 352800: + config.output_rate = value; + break; + default: + warn("Invalid output rate \"%d\". It should be a multiple of 44,100 up to 352,800. It is " + "set to 44,100", + value); + config.output_rate = 44100; + } + } + + /* Get the use_mmap_if_available setting. */ + if (config_lookup_string(config.cfg, "alsa.use_mmap_if_available", &str)) { + if (strcasecmp(str, "no") == 0) + config.no_mmap = 1; + else if (strcasecmp(str, "yes") == 0) + config.no_mmap = 0; + else { + warn("Invalid use_mmap_if_available option choice \"%s\". It should be \"yes\" or \"no\". " + "It is set to \"yes\"."); + config.no_mmap = 0; + } + } + /* Get the optional period size value */ + if (config_lookup_int(config.cfg, "alsa.period_size", &value)) { + set_period_size_request = 1; + debug(1, "Value read for period size is %d.", value); + if (value < 0) { + warn("Invalid alsa period size setting \"%d\". It " + "must be greater than 0. No setting is made.", + value); + set_period_size_request = 0; + } else { + period_size_requested = value; + } + } + + /* Get the optional buffer size value */ + if (config_lookup_int(config.cfg, "alsa.buffer_size", &value)) { + set_buffer_size_request = 1; + debug(1, "Value read for buffer size is %d.", value); + if (value < 0) { + warn("Invalid alsa buffer size setting \"%d\". It " + "must be greater than 0. No setting is made.", + value); + set_buffer_size_request = 0; + } else { + buffer_size_requested = value; + } + } + + /* Get the optional alsa_maximum_stall_time setting. */ + if (config_lookup_float(config.cfg, "alsa.maximum_stall_time", &dvalue)) { + if (dvalue < 0.0) { + warn("Invalid alsa maximum write time setting \"%f\". It " + "must be greater than 0. Default is \"%f\". No setting is made.", + dvalue, config.alsa_maximum_stall_time); + } else { + config.alsa_maximum_stall_time = dvalue; + } + } + + // Get the optional "Keep DAC Busy setting" + config_set_lookup_bool(config.cfg, "alsa.disable_standby_mode", &keep_dac_busy); + debug(1, "alsa: disable_standby_mode is %s.", keep_dac_busy ? "on" : "off"); + } + + optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour + argv--; // so we shift the arguments to satisfy getopt() + argc++; + // some platforms apparently require optreset = 1; - which? + int opt; + while ((opt = getopt(argc, argv, "d:t:m:c:i:")) > 0) { + switch (opt) { + case 'd': + alsa_out_dev = optarg; + break; + + case 't': + inform("The alsa backend -t option is deprecated and has been ignored. " + "FYI, using the -c option automatically chooses a hardware " + "mixer."); + break; + + case 'm': + alsa_mix_dev = optarg; + break; + case 'c': + alsa_mix_ctrl = optarg; + hardware_mixer = 1; + break; + case 'i': + alsa_mix_index = strtol(optarg, NULL, 10); + break; + default: + warn("Invalid audio option \"-%c\" specified -- ignored.", opt); + help(); + } + } + + if (optind < argc) { + warn("Invalid audio argument: \"%s\" -- ignored", argv[optind]); + } + + debug(1, "alsa: output device name is \"%s\".", alsa_out_dev); + + if (hardware_mixer) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable + + if (alsa_mix_dev == NULL) + alsa_mix_dev = alsa_out_dev; + + // Now, start trying to initialise the alsa device with the settings obtained + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 1000, 1); + if (open_mixer() == 1) { + if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) < + 0) + debug(1, "Can't read mixer's [linear] min and max volumes."); + else { + if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb, + &alsa_mix_maxdb) == 0) { + + audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff + audio_alsa.parameters = ¶meters; // likewise the parameters stuff + if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) { + // For instance, the Raspberry Pi does this + debug(1, "Lowest dB value is a mute"); + mixer_volume_setting_gives_mute = 1; + alsa_mix_mute = SND_CTL_TLV_DB_GAIN_MUTE; // this may not be necessary -- it's always + // going to be SND_CTL_TLV_DB_GAIN_MUTE, right? + // debug(1, "Try minimum volume + 1 as lowest true attenuation value"); + if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1, + &alsa_mix_mindb) != 0) + debug(1, "Can't get dB value corresponding to a minimum volume + 1."); + } + debug(1, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0, + (1.0 * alsa_mix_maxdb) / 100.0); + } else { + // use the linear scale and do the db conversion ourselves + warn("The hardware mixer specified -- \"%s\" -- does not have " + "a dB volume scale.", + alsa_mix_ctrl); + + if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) { + warn("Cannot open control \"%s\"", alsa_mix_dev); + response = -1; + } + if (snd_ctl_elem_id_malloc(&elem_id) < 0) { + debug(1, "Cannot allocate memory for control \"%s\"", alsa_mix_dev); + elem_id = NULL; + response = -2; + } else { + snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_MIXER); + snd_ctl_elem_id_set_name(elem_id, alsa_mix_ctrl); + + if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) { + debug(1, "alsa: hardware mixer \"%s\" selected, with dB volume from %f to %f.", + alsa_mix_ctrl, (1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0); + has_softvol = 1; + audio_alsa.volume = + &volume; // insert the volume function now we know it can do dB stuff + audio_alsa.parameters = ¶meters; // likewise the parameters stuff + } else { + debug(1, "Cannot get the dB range from the volume control \"%s\"", alsa_mix_ctrl); + } + } + /* + debug(1, "Min and max volumes are %d and + %d.",alsa_mix_minv,alsa_mix_maxv); + alsa_mix_maxdb = 0; + if ((alsa_mix_maxv!=0) && (alsa_mix_minv!=0)) + alsa_mix_mindb = + -20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0)); + else if (alsa_mix_maxv!=0) + alsa_mix_mindb = -20*100*log10(alsa_mix_maxv*1.0); + audio_alsa.volume = &linear_volume; // insert the linear volume function + audio_alsa.parameters = ¶meters; // likewise the parameters stuff + debug(1,"Max and min dB calculated are %d and + %d.",alsa_mix_maxdb,alsa_mix_mindb); + */ + } + } + if (((config.alsa_use_hardware_mute == 1) && + (snd_mixer_selem_has_playback_switch(alsa_mix_elem))) || + mixer_volume_setting_gives_mute) { + audio_alsa.mute = &mute; // insert the mute function now we know it can do muting stuff + // debug(1, "Has mixer and mute ability we will use."); + } else { + // debug(1, "Has mixer but not using hardware mute."); + } + close_mixer(); + } + debug_mutex_unlock(&alsa_mutex, 3); // release the mutex + + pthread_cleanup_pop(0); + pthread_setcancelstate(oldState, NULL); + } else { + debug(1, "alsa: no hardware mixer selected."); + } + alsa_mix_handle = NULL; + + // so, now, if the option to keep the DAC running has been selected, start a thread to monitor the + // length of the queue + // if the queue gets too short, stuff it with silence + + desired_sample_rate = config.output_rate; + sample_format = config.output_format; + + if (response == 0) { + // try opening the device. + int ret = actual_open_alsa_device(); + if (ret == 0) + actual_close_alsa_device(); + else + die("Could not open the alsa device with the settings given."); + } + + if (keep_dac_busy) + pthread_create(&alsa_buffer_monitor_thread, NULL, &alsa_buffer_monitor_thread_code, NULL); + + return response; +} + +static void deinit(void) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable + // debug(2,"audio_alsa deinit called."); + stop(); + if (keep_dac_busy) { + debug(1, "Cancel buffer monitor thread."); + pthread_cancel(alsa_buffer_monitor_thread); + debug(1, "Join buffer monitor thread."); + pthread_join(alsa_buffer_monitor_thread, NULL); + } + pthread_setcancelstate(oldState, NULL); +} + static void start(int i_sample_rate, int i_sample_format) { // debug(2,"audio_alsa start called."); if (i_sample_rate == 0) @@ -1239,8 +1263,8 @@ static void flush(void) { if (alsa_handle) { stall_monitor_start_time = 0; if (keep_dac_busy == 0) { - if ((derr = snd_pcm_drop(alsa_handle))) - debug(1, "Error %d (\"%s\") dropping output device.", derr, snd_strerror(derr)); + if ((derr = snd_pcm_drop(alsa_handle))) + debug(1, "Error %d (\"%s\") dropping output device.", derr, snd_strerror(derr)); if ((derr = snd_pcm_hw_free(alsa_handle))) debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr)); @@ -1346,7 +1370,7 @@ static void mute(int mute_state_requested) { } void do_mute(int mute_state_requested) { - debug(3,"Setting mute to %d.",mute_state_requested); + debug(3, "Setting mute to %d.", mute_state_requested); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable @@ -1419,31 +1443,42 @@ void *alsa_buffer_monitor_thread_code(void *arg) { pthread_cleanup_push(alsa_buffer_monitor_thread_cleanup_function, arg); pthread_setcancelstate(oldState, NULL); + // The thinking is, if the device has a hardware mixer, then + // (if no source transformation is happening), Shairport Sync + // will deliver fill-in silences and the audio material without adding dither. + // So don't insert dither into the silences sent to keep the DAC busy. + + int use_dither = 0; + if (hardware_mixer == 0) + use_dither = 1; + + debug(1, "alsa: dither will %sbe added to inter-session silence.", use_dither ? "" : "not "); + int sleep_time_ms = 80; int frames_of_silence = desired_sample_rate * sleep_time_ms / 1000; size_t size_of_silence_buffer = frames_of_silence * frame_size; // debug(1,"Silence buffer length: %u bytes.",size_of_silence_buffer); void *silence = malloc(size_of_silence_buffer); if (silence == NULL) { - debug(1, "Failed to allocate memory for a silent frame buffer."); + debug(1, "alsa: failed to allocate memory for a silent frame buffer, thus " + "disable_standby_mode is \"off\"."); } else { - } - - long buffer_size; - int reply; - while (1) { - usleep((sleep_time_ms * 1000) / 2); - reply = delay(&buffer_size); - if (reply != 0) - debug(1, "error %d during buffer monitoring of delay", reply); - if (buffer_size < (((int)desired_sample_rate * sleep_time_ms) / (1000))) { - // debug(1,"Writing %d frames of silence because only %lu frames remain in the - // buffer.",frames_of_silence,buffer_size); - memset(silence, 0, size_of_silence_buffer); - play(silence, frames_of_silence); + long buffer_size; + int reply; + while (1) { + if (buffer_size < (((int)desired_sample_rate * sleep_time_ms) / (1000))) { + dither_random_number_store = generate_zero_frames( + silence, frames_of_silence, config.output_format, use_dither, // i.e. with dither + dither_random_number_store); + play(silence, frames_of_silence); + } + usleep((sleep_time_ms * 1000) / 2); + reply = delay(&buffer_size); + if (reply != 0) + debug(1, "error %d during buffer monitoring of delay", reply); + pthread_testcancel(); } - pthread_testcancel(); } - pthread_cleanup_pop(0); + pthread_cleanup_pop(1); pthread_exit(NULL); } diff --git a/common.c b/common.c index 053abbe3..aa38007f 100644 --- a/common.c +++ b/common.c @@ -1058,7 +1058,7 @@ uint64_t r64u() { return (ranval(&rx)); } int64_t r64i() { return (ranval(&rx) >> 1); } /* generate an array of 64-bit random numbers */ -const int ranarraylength = 1009; // these will be 8-byte numbers. +const int ranarraylength = 1009 * 203; // these will be 8-byte numbers. int ranarraynext; @@ -1083,9 +1083,11 @@ uint64_t ranarrayval() { void r64arrayinit() { ranarrayinit(); } -uint64_t ranarray64u() { return (ranarrayval()); } +// uint64_t ranarray64u() { return (ranarrayval()); } +uint64_t ranarray64u() { return (ranval(&rx)); } -int64_t ranarray64i() { return (ranarrayval() >> 1); } +// int64_t ranarray64i() { return (ranarrayval() >> 1); } +int64_t ranarray64i() { return (ranval(&rx) >> 1); } uint32_t nctohl(const uint8_t *p) { // read 4 characters from *p and do ntohl on them // this is to avoid possible aliasing violations @@ -1325,3 +1327,121 @@ char *get_version_string() { } return version_string; } + +int64_t generate_zero_frames(char *outp, size_t number_of_frames, enum sps_format_t format, + int with_dither, int64_t random_number_in) { + // return the last random number used + // assuming the buffer has been assigned + + int64_t previous_random_number = random_number_in; + char *p = outp; + size_t sample_number; + for (sample_number = 0; sample_number < number_of_frames * 2; sample_number++) { + + int64_t hyper_sample = 0; + // add a TPDF dither -- see + // http://www.users.qwest.net/%7Evolt42/cadenzarecording/DitherExplained.pdf + // and the discussion around https://www.hydrogenaud.io/forums/index.php?showtopic=16963&st=25 + + // I think, for a 32 --> 16 bits, the range of + // random numbers needs to be from -2^16 to 2^16, i.e. from -65536 to 65536 inclusive, not from + // -32768 to +32767 + + // See the original paper at + // http://www.ece.rochester.edu/courses/ECE472/resources/Papers/Lipshitz_1992.pdf + // by Lipshitz, Wannamaker and Vanderkooy, 1992. + + int64_t dither_mask = 0; + switch (format) { + case SPS_FORMAT_S32: + dither_mask = (int64_t)1 << (64 + 1 - 32); + break; + case SPS_FORMAT_S24: + case SPS_FORMAT_S24_3LE: + case SPS_FORMAT_S24_3BE: + dither_mask = (int64_t)1 << (64 + 1 - 24); + break; + case SPS_FORMAT_S16: + dither_mask = (int64_t)1 << (64 + 1 - 16); + break; + case SPS_FORMAT_S8: + case SPS_FORMAT_U8: + dither_mask = (int64_t)1 << (64 + 1 - 8); + break; + case SPS_FORMAT_UNKNOWN: + die("Unexpected SPS_FORMAT_UNKNOWN while calculating dither mask."); + } + dither_mask -= 1; + // int64_t r = r64i(); + int64_t r = ranarray64i(); + + int64_t tpdf = (r & dither_mask) - (previous_random_number & dither_mask); + + // add dither if permitted -- no need to check for clipping, as the sample is, uh, zero + + if (with_dither != 0) + hyper_sample += tpdf; + + // move the result to the desired position in the int64_t + char *op = p; + int result; // this is the length of the sample + + uint8_t byt; + switch (format) { + case SPS_FORMAT_S32: + hyper_sample >>= (64 - 32); + *(int32_t *)op = hyper_sample; + result = 4; + break; + case SPS_FORMAT_S24_3LE: + hyper_sample >>= (64 - 24); + byt = (uint8_t)hyper_sample; + *op++ = byt; + byt = (uint8_t)(hyper_sample >> 8); + *op++ = byt; + byt = (uint8_t)(hyper_sample >> 16); + *op++ = byt; + result = 3; + break; + case SPS_FORMAT_S24_3BE: + hyper_sample >>= (64 - 24); + byt = (uint8_t)(hyper_sample >> 16); + *op++ = byt; + byt = (uint8_t)(hyper_sample >> 8); + *op++ = byt; + byt = (uint8_t)hyper_sample; + *op++ = byt; + result = 3; + break; + case SPS_FORMAT_S24: + hyper_sample >>= (64 - 24); + *(int32_t *)op = hyper_sample; + result = 4; + break; + case SPS_FORMAT_S16: + hyper_sample >>= (64 - 16); + *(int16_t *)op = (int16_t)hyper_sample; + result = 2; + break; + case SPS_FORMAT_S8: + hyper_sample >>= (int8_t)(64 - 8); + *op = hyper_sample; + result = 1; + break; + case SPS_FORMAT_U8: + hyper_sample >>= (uint8_t)(64 - 8); + hyper_sample += 128; + *op = hyper_sample; + result = 1; + break; + default: + result = 0; // stop a compiler warning + die("Unexpected SPS_FORMAT_UNKNOWN while outputting samples"); + } + p += result; + previous_random_number = r; + } + // hack + // memset(outp,0,number_of_frames * 4); + return previous_random_number; +} diff --git a/common.h b/common.h index 3ac2f1a0..17024f5e 100644 --- a/common.h +++ b/common.h @@ -337,6 +337,7 @@ char *get_version_string(); // mallocs a string space -- remember to free it aft void sps_nanosleep(const time_t sec, const long nanosec); // waits for this time, even through interruptions -int usleep_uncancellable(useconds_t usec); +int64_t generate_zero_frames(char *outp, size_t number_of_frames, enum sps_format_t format, + int with_dither, int64_t random_number_in); #endif // _COMMON_H diff --git a/player.c b/player.c index 9259aaa4..816ff40a 100644 --- a/player.c +++ b/player.c @@ -1032,7 +1032,12 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // this could happen if the dac delay mysteriously grows between samples, // which could happen in a transition between having no interpolation and // having interpolated buffer numbers. - debug(2, + + // this will happen benignly if standby is being prevented, because a + // thread in the alsa back end will be stuffing frames of silence in there + // to keep it busy + + debug(3, "frame size (fs) < 0 with max_dac_delay of %lld and dac_delay of %ld", max_dac_delay, dac_delay); fs = 0; @@ -1062,7 +1067,11 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (silence == NULL) debug(1, "Failed to allocate %d byte silence buffer.", fs); else { - memset(silence, 0, conn->output_bytes_per_frame * fs); + + conn->previous_random_number = generate_zero_frames( + silence, fs, config.output_format, conn->enable_dither, + conn->previous_random_number); + // debug(1,"Frames to start: %llu, DAC delay %d, buffer: %d // packets.",exact_frame_gap,dac_delay,seq_diff(conn->ab_read, // conn->ab_write, conn->ab_read)); @@ -1106,7 +1115,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { debug(1, "Failed to allocate %d frame silence buffer.", fs); else { // debug(1, "No delay function -- outputting %d frames of silence.", fs); - memset(silence, 0, conn->output_bytes_per_frame * 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); } @@ -1427,22 +1438,22 @@ void player_thread_cleanup_handler(void *arg) { mdns_dacp_monitor_set_id(NULL); // say we're not interested in following that DACP id any more #endif - debug(2, "Cancelling timing, control and audio threads..."); - debug(2, "Cancel timing thread."); + debug(3, "Cancelling timing, control and audio threads..."); + debug(3, "Cancel timing thread."); pthread_cancel(conn->rtp_timing_thread); - debug(2, "Join timing thread."); + debug(3, "Join timing thread."); pthread_join(conn->rtp_timing_thread, NULL); - debug(2, "Timing thread terminated."); - debug(2, "Cancel control thread."); + debug(3, "Timing thread terminated."); + debug(3, "Cancel control thread."); pthread_cancel(conn->rtp_control_thread); - debug(2, "Join control thread."); + debug(3, "Join control thread."); pthread_join(conn->rtp_control_thread, NULL); - debug(2, "Control thread terminated."); - debug(2, "Cancel audio thread."); + debug(3, "Control thread terminated."); + debug(3, "Cancel audio thread."); pthread_cancel(conn->rtp_audio_thread); - debug(2, "Join audio thread."); + debug(3, "Join audio thread."); pthread_join(conn->rtp_audio_thread, NULL); - debug(2, "Audio thread terminated."); + debug(3, "Audio thread terminated."); if (conn->outbuf) { free(conn->outbuf); @@ -1486,8 +1497,6 @@ void *player_thread_func(void *arg) { conn->latency = 88200; } - config.output->start(config.output_rate, config.output_format); // will need a corresponding stop - init_decoder((int32_t *)&conn->stream.fmtp, conn); // this sets up incoming rate, bit depth, channels. // No pthread cancellation point in here @@ -1625,10 +1634,16 @@ void *player_thread_func(void *arg) { debug(3, "Dithering will be enabled because the input bit depth is greater than the output bit " "depth"); } - if (conn->fix_volume != 0x10000) { + if (config.output->parameters == NULL) { debug(3, "Dithering will be enabled because the output volume is being altered in software"); } + if ((config.output->parameters == NULL) || (conn->input_bit_depth > output_bit_depth) || + (config.playback_mode == ST_mono)) + conn->enable_dither = 1; + + config.output->start(config.output_rate, config.output_format); // will need a corresponding stop + // we need an intermediate "transition" buffer // if ((input_rate!=config.output_rate) || (input_bit_depth!=output_bit_depth)) { @@ -1766,8 +1781,9 @@ void *player_thread_func(void *arg) { } else { // the player may change the contents of the buffer, so it has to be zeroed each time; // might as well malloc and freee it locally - memset(silence, 0, conn->output_bytes_per_frame * conn->max_frames_per_packet * - conn->output_sample_ratio); + conn->previous_random_number = generate_zero_frames( + silence, conn->max_frames_per_packet * conn->output_sample_ratio, + config.output_format, conn->enable_dither, conn->previous_random_number); config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio); free(silence); } @@ -1787,16 +1803,19 @@ void *player_thread_func(void *arg) { } else { // the player may change the contents of the buffer, so it has to be zeroed each time; // might as well malloc and freee it locally - memset(silence, 0, conn->output_bytes_per_frame * conn->max_frames_per_packet * - conn->output_sample_ratio); + conn->previous_random_number = generate_zero_frames( + silence, conn->max_frames_per_packet * conn->output_sample_ratio, + config.output_format, conn->enable_dither, conn->previous_random_number); config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio); free(silence); } } else { - int enable_dither = 0; - if ((conn->fix_volume != 0x10000) || (conn->input_bit_depth > output_bit_depth) || + + if ((config.output->parameters == NULL) || (conn->input_bit_depth > output_bit_depth) || (config.playback_mode == ST_mono)) - enable_dither = 1; + conn->enable_dither = 1; + else + conn->enable_dither = 0; // here, let's transform the frame of data, if necessary @@ -2069,7 +2088,11 @@ void *player_thread_func(void *arg) { size_t silence_length_sized = silence_length; char *long_silence = malloc(conn->output_bytes_per_frame * silence_length_sized); if (long_silence) { - memset(long_silence, 0, conn->output_bytes_per_frame * silence_length_sized); + + conn->previous_random_number = + generate_zero_frames(long_silence, silence_length_sized, config.output_format, + conn->enable_dither, conn->previous_random_number); + debug(2, "Play a silence of %d frames.", silence_length_sized); config.output->play(long_silence, silence_length_sized); free(long_silence); @@ -2193,14 +2216,14 @@ void *player_thread_func(void *arg) { (config.packet_stuffing == ST_basic)) { play_samples = stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format, - conn->outbuf, amount_to_stuff, enable_dither, conn); + conn->outbuf, amount_to_stuff, conn->enable_dither, conn); } #ifdef CONFIG_SOXR else if (config.packet_stuffing == ST_soxr) { // if (amount_to_stuff) debug(1,"Soxr stuff..."); play_samples = stuff_buffer_soxr_32((int32_t *)conn->tbuf, (int32_t *)conn->sbuf, inbuflength, config.output_format, conn->outbuf, - amount_to_stuff, enable_dither, conn); + amount_to_stuff, conn->enable_dither, conn); } #endif @@ -2256,7 +2279,7 @@ void *player_thread_func(void *arg) { // synchronising play_samples = stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format, - conn->outbuf, 0, enable_dither, conn); + conn->outbuf, 0, conn->enable_dither, conn); if (conn->outbuf == NULL) debug(1, "NULL outbuf to play -- skipping it."); else @@ -2780,16 +2803,16 @@ int player_stop(rtsp_conn_info *conn) { // debuglev = 3; debug(3, "player_stop"); if (conn->player_thread) { - debug(2, "player_thread cancel..."); + debug(3, "player_thread cancel..."); pthread_cancel(*conn->player_thread); - debug(2, "player_thread join..."); + debug(3, "player_thread join..."); if (pthread_join(*conn->player_thread, NULL) == -1) { char errorstring[1024]; strerror_r(errno, (char *)errorstring, sizeof(errorstring)); debug(1, "Connection %d: error %d joining player thread: \"%s\".", conn->connection_number, errno, (char *)errorstring); } else { - debug(2, "player_thread joined."); + debug(3, "player_thread joined."); } free(conn->player_thread); conn->player_thread = NULL; diff --git a/player.h b/player.h index 413aa8da..7dd1c186 100644 --- a/player.h +++ b/player.h @@ -246,6 +246,8 @@ typedef struct { // zero uint32_t 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 } rtsp_conn_info; uint32_t modulo_32_offset(uint32_t from, uint32_t to); diff --git a/rtsp.c b/rtsp.c index bdd686a0..eb2eaeae 100644 --- a/rtsp.c +++ b/rtsp.c @@ -265,7 +265,7 @@ int have_player(rtsp_conn_info *conn) { void player_watchdog_thread_cleanup_handler(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; - debug(2, "Connection %d: Watchdog Exit.", conn->connection_number); + debug(3, "Connection %d: Watchdog Exit.", conn->connection_number); } void *player_watchdog_thread_code(void *arg) { @@ -2127,22 +2127,22 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { if (rc) debug(1, "Connection %d: error %d destroying flush_mutex.", conn->connection_number, rc); - debug(2, "Cancel watchdog thread."); + debug(3, "Cancel watchdog thread."); pthread_cancel(conn->player_watchdog_thread); - debug(2, "Join watchdog thread."); + debug(3, "Join watchdog thread."); pthread_join(conn->player_watchdog_thread, NULL); - debug(2, "Delete watchdog mutex."); + debug(3, "Delete watchdog mutex."); pthread_mutex_destroy(&conn->watchdog_mutex); debug(3, "Connection %d: Checking play lock.", conn->connection_number); debug_mutex_lock(&playing_conn_lock, 1000000, 3); // get it if (playing_conn == conn) { // if it's ours - debug(1, "Connection %d: Unlocking play lock -- end of play session.", conn->connection_number); + debug(3, "Connection %d: Unlocking play lock.", conn->connection_number); playing_conn = NULL; // let it go } debug_mutex_unlock(&playing_conn_lock, 3); - debug(2, "Connection %d: RTSP thread terminated.", conn->connection_number); + debug(2, "Connection %d: terminated.", conn->connection_number); conn->running = 0; pthread_setcancelstate(oldState, NULL); } From e6c3ddb9bf61247934feee431a92e918fedf23c3 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 14 Dec 2018 15:07:38 +0000 Subject: [PATCH 294/569] Fix dither, add dither to silences during lead-in, interpolatied frames and keeping out of standby, only if the output device doesn't use a mixer. Check configuration at initialisation. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 5d3937ca..01216fcc 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.3d38], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d39], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From c20fc79a6e71cc516b2b4a918088f418f90a8443 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 14 Dec 2018 16:07:50 +0000 Subject: [PATCH 295/569] Update RELEASENOTES.md --- RELEASENOTES.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f9c363bd..79f98a3d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,26 @@ +Version 3.3d39 to Version 3.3d28 +==== + +**New Feature** + +This important new feature helps with removing some annoying clicking / popping noises. It is really pretty impressive, especially combined with the fixes to the dithering code described below. If you can enable 24- or 32-bit audio output to your output device, you can get even better results. + +This is an ALSA-specific attempt to remove the annoying low-level clicking sounds that some output devices make just when they start processing audio and somethines when they stop. Typically a faint click might be heard just before a play session starts or just before audio resumes after a pause. Similarly, a faint click can sometimes be heard just after a play session ends. + +It is extremely difficuly to remove these clicks completely from the hardware, so this new feature ensures that — once Shairport Sync has started — the output device avoids situations where these clicks might be generated by *always* playing audio. To accomplish this, if Shairport Sync isn't actually playing anything, audio frames consisting of silence are sent to the output device. Apart from the initial startup transition, the output device never stops playing, avoiding both starting and stopping transitions. + +To enable this feature, a new `alsa` group setting with the name `disable_standby_mode` is available. (If you do a full `$ sudo make install`, a new sample configuration file with this setting in it is installed at `/etc/shairport-sync.conf.sample` or `/usr/local/etc/shairport-sync.conf.sample`.) The default for the setting is `"no"`. + +A downside to this feature is that the output device totally dedicated to Shairport Sync. For this reason, this new feature is disabled by default. + +Note -- this setting is likely to change. It will probably be necessary to modify it somehow to enable it to be used in integrated systems. Your feedback would be welcome. + +**Enhanccements and Bug Fixes** + +At present, Shairport Sync adds dither to the audio if (a) the built-in software-based volume control is used, (b) if the audio is mixed to mono or (3) if there is a change in sample size, say from 16- to 24-bit. The code for generating dither has been much improved. Due to a poor pseudo-random number arrangement, the dither noise didn't sound like white noise -- now it does. + +In addition, if dither is to be added, it is also added to the silence inserted just prior to the start of play, and is also added tthe silent frames used to prevent the output device from going into standby mode, if selected. + Version 3.3d37 to Version 3.3d22 ==== From efa592db41a17c8eb63b483f42a108f6c1bd5e73 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 14 Dec 2018 16:10:09 +0000 Subject: [PATCH 296/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 79f98a3d..5e4fa121 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,4 +1,4 @@ -Version 3.3d39 to Version 3.3d28 +Version 3.3d39 to Version 3.3d38 ==== **New Feature** From 4ba0947ddb82d27e9e15893284816919f6784189 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 15 Dec 2018 15:36:44 +0000 Subject: [PATCH 297/569] Move the keep_dac_busy in to a configuration setting. Always start the keep_dac_busy thread and check if the keep_dac_busy is set. Don't dither is ignore volume control is set. --- audio_alsa.c | 52 +++++++++++++++++++++++++++++----------------------- common.h | 2 +- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 277b18ef..9a93b888 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -105,7 +105,6 @@ static int alsa_mix_index = 0; static int hardware_mixer = 0; static int has_softvol = 0; -int keep_dac_busy = 0; // true if the dac must be kept busy with samples int64_t dither_random_number_store = 0; static int volume_set_request = 0; // set when an external request is made to set the volume. @@ -799,8 +798,11 @@ static int init(int argc, char **argv) { } // Get the optional "Keep DAC Busy setting" - config_set_lookup_bool(config.cfg, "alsa.disable_standby_mode", &keep_dac_busy); - debug(1, "alsa: disable_standby_mode is %s.", keep_dac_busy ? "on" : "off"); + int kdb; + if (config_set_lookup_bool(config.cfg, "alsa.disable_standby_mode", &kdb)) { + config.keep_dac_busy = kdb; + } + debug(1, "alsa: disable_standby_mode is %s.", config.keep_dac_busy ? "on" : "off"); } optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour @@ -954,8 +956,7 @@ static int init(int argc, char **argv) { die("Could not open the alsa device with the settings given."); } - if (keep_dac_busy) - pthread_create(&alsa_buffer_monitor_thread, NULL, &alsa_buffer_monitor_thread_code, NULL); + pthread_create(&alsa_buffer_monitor_thread, NULL, &alsa_buffer_monitor_thread_code, NULL); return response; } @@ -965,12 +966,10 @@ static void deinit(void) { pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable // debug(2,"audio_alsa deinit called."); stop(); - if (keep_dac_busy) { - debug(1, "Cancel buffer monitor thread."); - pthread_cancel(alsa_buffer_monitor_thread); - debug(1, "Join buffer monitor thread."); - pthread_join(alsa_buffer_monitor_thread, NULL); - } + debug(1, "Cancel buffer monitor thread."); + pthread_cancel(alsa_buffer_monitor_thread); + debug(1, "Join buffer monitor thread."); + pthread_join(alsa_buffer_monitor_thread, NULL); pthread_setcancelstate(oldState, NULL); } @@ -1262,7 +1261,7 @@ static void flush(void) { if (alsa_handle) { stall_monitor_start_time = 0; - if (keep_dac_busy == 0) { + if (config.keep_dac_busy == 0) { if ((derr = snd_pcm_drop(alsa_handle))) debug(1, "Error %d (\"%s\") dropping output device.", derr, snd_strerror(derr)); if ((derr = snd_pcm_hw_free(alsa_handle))) @@ -1447,9 +1446,12 @@ void *alsa_buffer_monitor_thread_code(void *arg) { // (if no source transformation is happening), Shairport Sync // will deliver fill-in silences and the audio material without adding dither. // So don't insert dither into the silences sent to keep the DAC busy. + + // Also, if the ignore_volume_setting is set, the audio is sent through unaltered, + // so, in that circumstance, don't add dither either. int use_dither = 0; - if (hardware_mixer == 0) + if ((hardware_mixer == 0) && (config.ignore_volume_control != 0)) use_dither = 1; debug(1, "alsa: dither will %sbe added to inter-session silence.", use_dither ? "" : "not "); @@ -1466,16 +1468,20 @@ void *alsa_buffer_monitor_thread_code(void *arg) { long buffer_size; int reply; while (1) { - if (buffer_size < (((int)desired_sample_rate * sleep_time_ms) / (1000))) { - dither_random_number_store = generate_zero_frames( - silence, frames_of_silence, config.output_format, use_dither, // i.e. with dither - dither_random_number_store); - play(silence, frames_of_silence); - } - usleep((sleep_time_ms * 1000) / 2); - reply = delay(&buffer_size); - if (reply != 0) - debug(1, "error %d during buffer monitoring of delay", reply); + if (config.keep_dac_busy != 0) { + if (buffer_size < (((int)desired_sample_rate * sleep_time_ms) / (1000))) { + dither_random_number_store = generate_zero_frames( + silence, frames_of_silence, config.output_format, use_dither, // i.e. with dither + dither_random_number_store); + play(silence, frames_of_silence); + } + usleep((sleep_time_ms * 1000) / 2); + reply = delay(&buffer_size); + if (reply != 0) + debug(1, "error %d during buffer monitoring of delay", reply); + } else { + usleep(sleep_time_ms * 1000); + } pthread_testcancel(); } } diff --git a/common.h b/common.h index 3132f370..8d028b3b 100644 --- a/common.h +++ b/common.h @@ -186,8 +186,8 @@ typedef struct { int loudness; float loudness_reference_volume_db; int alsa_use_hardware_mute; - double alsa_maximum_stall_time; + volatile int keep_dac_busy; #if defined(CONFIG_DBUS_INTERFACE) enum dbus_session_type dbus_service_bus_type; From aa2e2c3d24d7a5426268b9fd2bb78fe648a3e301 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 15 Dec 2018 17:50:01 +0000 Subject: [PATCH 298/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 5e4fa121..277ee530 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -5,7 +5,7 @@ Version 3.3d39 to Version 3.3d38 This important new feature helps with removing some annoying clicking / popping noises. It is really pretty impressive, especially combined with the fixes to the dithering code described below. If you can enable 24- or 32-bit audio output to your output device, you can get even better results. -This is an ALSA-specific attempt to remove the annoying low-level clicking sounds that some output devices make just when they start processing audio and somethines when they stop. Typically a faint click might be heard just before a play session starts or just before audio resumes after a pause. Similarly, a faint click can sometimes be heard just after a play session ends. +This is an ALSA-specific attempt to remove the annoying low-level clicking sounds that some output devices make just when they start processing audio and sometimes when they stop. Typically a faint click might be heard just before a play session starts or just before audio resumes after a pause. Similarly, a faint click can sometimes be heard just after a play session ends. It is extremely difficuly to remove these clicks completely from the hardware, so this new feature ensures that — once Shairport Sync has started — the output device avoids situations where these clicks might be generated by *always* playing audio. To accomplish this, if Shairport Sync isn't actually playing anything, audio frames consisting of silence are sent to the output device. Apart from the initial startup transition, the output device never stops playing, avoiding both starting and stopping transitions. From 14e867a66b2e983c0e130262bc9aa8c78d1d6b85 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 17 Dec 2018 11:30:23 +0000 Subject: [PATCH 299/569] use dither if using software mixer (i.e. no hardware mixer and not ignoring the volume control) -- fix a mistake in accounting for the ignore_volume_control setting. --- audio_alsa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_alsa.c b/audio_alsa.c index 9a93b888..e661a458 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1451,7 +1451,7 @@ void *alsa_buffer_monitor_thread_code(void *arg) { // so, in that circumstance, don't add dither either. int use_dither = 0; - if ((hardware_mixer == 0) && (config.ignore_volume_control != 0)) + if ((hardware_mixer == 0) && (config.ignore_volume_control == 0)) use_dither = 1; debug(1, "alsa: dither will %sbe added to inter-session silence.", use_dither ? "" : "not "); From 4db9c117a25bcb428be2153188304d574a1edb4a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 17 Dec 2018 11:31:28 +0000 Subject: [PATCH 300/569] use dither if using software mixer (i.e. no hardware mixer and not ignoring the volume control) -- fix a mistake in accounting for the ignore_volume_control setting. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 01216fcc..0e8bbb1b 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.3d39], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d40], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 57e9619e5bbe18f80da8a0bbd43fc616acdf107e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 17 Dec 2018 13:14:40 +0000 Subject: [PATCH 301/569] Don't add dither if volume control is set to max. --- audio_alsa.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/audio_alsa.c b/audio_alsa.c index e661a458..8cb96e05 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1451,7 +1451,7 @@ void *alsa_buffer_monitor_thread_code(void *arg) { // so, in that circumstance, don't add dither either. int use_dither = 0; - if ((hardware_mixer == 0) && (config.ignore_volume_control == 0)) + if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && (config.airplay_volume != 0.0)) use_dither = 1; debug(1, "alsa: dither will %sbe added to inter-session silence.", use_dither ? "" : "not "); @@ -1470,6 +1470,10 @@ void *alsa_buffer_monitor_thread_code(void *arg) { while (1) { if (config.keep_dac_busy != 0) { if (buffer_size < (((int)desired_sample_rate * sleep_time_ms) / (1000))) { + if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && (config.airplay_volume != 0.0)) + use_dither = 1; + else + use_dither = 0; dither_random_number_store = generate_zero_frames( silence, frames_of_silence, config.output_format, use_dither, // i.e. with dither dither_random_number_store); From cb17fb263ada0907152fdee884d7701c042c8747 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 17 Dec 2018 16:31:03 +0000 Subject: [PATCH 302/569] Don't send silence into the alsa device within the scanner's sleep time of the last legitimate write to it. This is to stop the scanner inserting silent frames while the player is flushing fames. --- audio_alsa.c | 46 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 8cb96e05..17fc672a 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -133,6 +133,8 @@ static uint64_t frames_played_at_measurement_start_time; static uint64_t measurement_time; static uint64_t frames_played_at_measurement_time; +static uint64_t most_recent_write_time; + static uint64_t frames_sent_for_playing; static uint64_t frame_index; static int measurement_data_is_valid; @@ -956,6 +958,7 @@ static int init(int argc, char **argv) { die("Could not open the alsa device with the settings given."); } + most_recent_write_time = 0; // could be used by the alsa_buffer_monitor_thread_code pthread_create(&alsa_buffer_monitor_thread, NULL, &alsa_buffer_monitor_thread_code, NULL); return response; @@ -1079,7 +1082,7 @@ int delay(long *the_delay) { debug(2, "delay not available -- state is SND_PCM_STATE_XRUN"); } else { - debug(2, "Error -- ALSA delay(): bad state: %d.", dac_state); + debug(1, "Error -- ALSA delay(): bad state: %d.", dac_state); } if ((derr = snd_pcm_prepare(alsa_handle))) { debug(1, "Error preparing after delay error: \"%s\".", snd_strerror(derr)); @@ -1175,7 +1178,7 @@ static int play(void *buf, int samples) { debug(1, "empty buffer being passed to pcm_writei -- skipping it"); if ((samples != 0) && (buf != NULL)) { // debug(3, "write %d frames.", samples); - + most_recent_write_time = get_absolute_time_in_fp(); err = alsa_pcm_write(alsa_handle, buf, samples); stall_monitor_frame_count += samples; @@ -1457,6 +1460,10 @@ void *alsa_buffer_monitor_thread_code(void *arg) { debug(1, "alsa: dither will %sbe added to inter-session silence.", use_dither ? "" : "not "); int sleep_time_ms = 80; + uint64_t sleep_time_in_fp = sleep_time_ms; + sleep_time_in_fp = sleep_time_in_fp << 32; + sleep_time_in_fp = sleep_time_in_fp / 1000; + debug(1,"alsa: sleep_time: %d ms or 0x%" PRIx64 " in fp form.",sleep_time_ms,sleep_time_in_fp); int frames_of_silence = desired_sample_rate * sleep_time_ms / 1000; size_t size_of_silence_buffer = frames_of_silence * frame_size; // debug(1,"Silence buffer length: %u bytes.",size_of_silence_buffer); @@ -1469,24 +1476,25 @@ void *alsa_buffer_monitor_thread_code(void *arg) { int reply; while (1) { if (config.keep_dac_busy != 0) { - if (buffer_size < (((int)desired_sample_rate * sleep_time_ms) / (1000))) { - if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && (config.airplay_volume != 0.0)) - use_dither = 1; - else - use_dither = 0; - dither_random_number_store = generate_zero_frames( - silence, frames_of_silence, config.output_format, use_dither, // i.e. with dither - dither_random_number_store); - play(silence, frames_of_silence); + uint64_t present_time = get_absolute_time_in_fp(); + if ((most_recent_write_time == 0) || ((present_time - most_recent_write_time) > sleep_time_in_fp)) { + reply = delay(&buffer_size); + if (reply != 0) + buffer_size = 0; + if (buffer_size < (((int)desired_sample_rate * sleep_time_ms) / (1000))) { + if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && (config.airplay_volume != 0.0)) + use_dither = 1; + else + use_dither = 0; + dither_random_number_store = generate_zero_frames( + silence, frames_of_silence, config.output_format, use_dither, // i.e. with dither + dither_random_number_store); + play(silence, frames_of_silence); + } } - usleep((sleep_time_ms * 1000) / 2); - reply = delay(&buffer_size); - if (reply != 0) - debug(1, "error %d during buffer monitoring of delay", reply); - } else { - usleep(sleep_time_ms * 1000); - } - pthread_testcancel(); + } + usleep((sleep_time_ms * 1000) / 2); // has a cancellation point in it +// pthread_testcancel(); } } pthread_cleanup_pop(1); From a96d28c4e5e7dbc187ab3aaff1239982e6a5f7f1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 17 Dec 2018 16:44:19 +0000 Subject: [PATCH 303/569] Need to use an untimed play for the silence buffer filler. --- audio_alsa.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 17fc672a..f9ce5840 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -133,7 +133,7 @@ static uint64_t frames_played_at_measurement_start_time; static uint64_t measurement_time; static uint64_t frames_played_at_measurement_time; -static uint64_t most_recent_write_time; +volatile uint64_t most_recent_write_time; static uint64_t frames_sent_for_playing; static uint64_t frame_index; @@ -1135,7 +1135,8 @@ int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) { return response; } -static int play(void *buf, int samples) { +int untimed_play(void *buf, int samples) { + // debug(3,"audio_alsa play called."); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable @@ -1178,7 +1179,6 @@ static int play(void *buf, int samples) { debug(1, "empty buffer being passed to pcm_writei -- skipping it"); if ((samples != 0) && (buf != NULL)) { // debug(3, "write %d frames.", samples); - most_recent_write_time = get_absolute_time_in_fp(); err = alsa_pcm_write(alsa_handle, buf, samples); stall_monitor_frame_count += samples; @@ -1283,6 +1283,11 @@ static void flush(void) { pthread_setcancelstate(oldState, NULL); } +static int play(void *buf, int samples) { + most_recent_write_time = get_absolute_time_in_fp(); // this is to regulate access by the silence filler threrad + return untimed_play(buf,samples); +} + static void stop(void) { // debug(2,"audio_alsa stop called."); // when we want to stop, we want the alsa device From 2830cdf712751b72714661acb90e1705ba332e19 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 17 Dec 2018 16:50:36 +0000 Subject: [PATCH 304/569] adjust minimum interval between last audio write and writing silence. --- audio_alsa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_alsa.c b/audio_alsa.c index f9ce5840..664bb6ae 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1482,7 +1482,7 @@ void *alsa_buffer_monitor_thread_code(void *arg) { while (1) { if (config.keep_dac_busy != 0) { uint64_t present_time = get_absolute_time_in_fp(); - if ((most_recent_write_time == 0) || ((present_time - most_recent_write_time) > sleep_time_in_fp)) { + if ((most_recent_write_time == 0) || ((present_time - most_recent_write_time) > (sleep_time_in_fp/2))) { reply = delay(&buffer_size); if (reply != 0) buffer_size = 0; From 70cd4fe81f14fe86f15ab14eceba689dda09603b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 17 Dec 2018 17:11:46 +0000 Subject: [PATCH 305/569] Further adjustments to the silence thread -- make the sleep time 30 ms, thus if the queue goes below 60 ms of samples, silence will be inserted unless an audio write occurred within the last 30 ms. --- audio_alsa.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 664bb6ae..72fd2ba4 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1464,12 +1464,12 @@ void *alsa_buffer_monitor_thread_code(void *arg) { debug(1, "alsa: dither will %sbe added to inter-session silence.", use_dither ? "" : "not "); - int sleep_time_ms = 80; + int sleep_time_ms = 30; uint64_t sleep_time_in_fp = sleep_time_ms; sleep_time_in_fp = sleep_time_in_fp << 32; sleep_time_in_fp = sleep_time_in_fp / 1000; debug(1,"alsa: sleep_time: %d ms or 0x%" PRIx64 " in fp form.",sleep_time_ms,sleep_time_in_fp); - int frames_of_silence = desired_sample_rate * sleep_time_ms / 1000; + int frames_of_silence = (desired_sample_rate * sleep_time_ms * 2) / 1000; size_t size_of_silence_buffer = frames_of_silence * frame_size; // debug(1,"Silence buffer length: %u bytes.",size_of_silence_buffer); void *silence = malloc(size_of_silence_buffer); @@ -1482,11 +1482,11 @@ void *alsa_buffer_monitor_thread_code(void *arg) { while (1) { if (config.keep_dac_busy != 0) { uint64_t present_time = get_absolute_time_in_fp(); - if ((most_recent_write_time == 0) || ((present_time - most_recent_write_time) > (sleep_time_in_fp/2))) { + if ((most_recent_write_time == 0) || ((present_time - most_recent_write_time) > (sleep_time_in_fp))) { reply = delay(&buffer_size); if (reply != 0) buffer_size = 0; - if (buffer_size < (((int)desired_sample_rate * sleep_time_ms) / (1000))) { + if (buffer_size < frames_of_silence) { if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && (config.airplay_volume != 0.0)) use_dither = 1; else @@ -1498,7 +1498,7 @@ void *alsa_buffer_monitor_thread_code(void *arg) { } } } - usleep((sleep_time_ms * 1000) / 2); // has a cancellation point in it + usleep(sleep_time_ms * 1000); // has a cancellation point in it // pthread_testcancel(); } } From 284cb9d3fbf74464efbe779e486451f14b3afad9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 26 Dec 2018 10:41:20 +0000 Subject: [PATCH 306/569] Return genuine error codes where available in actual_open_alsa_device. --- audio_alsa.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 72fd2ba4..e8360793 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -254,7 +254,7 @@ int actual_open_alsa_device(void) { ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, 0); if (ret < 0) - return (-10); + return ret; snd_pcm_hw_params_alloca(&alsa_params); snd_pcm_sw_params_alloca(&alsa_swparams); @@ -264,7 +264,7 @@ int actual_open_alsa_device(void) { warn("audio_alsa: Broken configuration for device \"%s\": no configurations " "available", alsa_out_dev); - return -11; + return ret; } if ((config.no_mmap == 0) && @@ -289,7 +289,7 @@ int actual_open_alsa_device(void) { if (ret < 0) { warn("audio_alsa: Access type not available for device \"%s\": %s", alsa_out_dev, snd_strerror(ret)); - return -12; + return ret; } snd_pcm_format_t sf; switch (sample_format) { @@ -325,27 +325,27 @@ int actual_open_alsa_device(void) { sf = SND_PCM_FORMAT_S16; // this is just to quieten a compiler warning frame_size = 4; debug(1, "Unsupported output format at audio_alsa.c"); - return -1; + return -EINVAL; } ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sf); if (ret < 0) { warn("audio_alsa: Sample format %d not available for device \"%s\": %s", sample_format, alsa_out_dev, snd_strerror(ret)); - return -2; + return ret; } ret = snd_pcm_hw_params_set_channels(alsa_handle, alsa_params, 2); if (ret < 0) { warn("audio_alsa: Channels count (2) not available for device \"%s\": %s", alsa_out_dev, snd_strerror(ret)); - return -3; + return ret; } ret = snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params, &my_sample_rate, &dir); if (ret < 0) { warn("audio_alsa: Rate %iHz not available for playback: %s", desired_sample_rate, snd_strerror(ret)); - return -4; + return ret; } if (set_period_size_request != 0) { @@ -355,7 +355,7 @@ int actual_open_alsa_device(void) { if (ret < 0) { warn("audio_alsa: cannot set period size of %lu: %s", period_size_requested, snd_strerror(ret)); - return -5; + return ret; } else { snd_pcm_uframes_t actual_period_size; snd_pcm_hw_params_get_period_size(alsa_params, &actual_period_size, &dir); @@ -372,7 +372,7 @@ int actual_open_alsa_device(void) { if (ret < 0) { warn("audio_alsa: cannot set buffer size of %lu: %s", buffer_size_requested, snd_strerror(ret)); - return -6; + return ret; } else { snd_pcm_uframes_t actual_buffer_size; snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_size); @@ -387,7 +387,7 @@ int actual_open_alsa_device(void) { if (ret < 0) { warn("audio_alsa: Unable to set hw parameters for device \"%s\": %s.", alsa_out_dev, snd_strerror(ret)); - return -7; + return ret; } // check parameters after attempting to set them… @@ -412,28 +412,28 @@ int actual_open_alsa_device(void) { if (my_sample_rate != desired_sample_rate) { warn("Can't set the D/A converter to %d.", desired_sample_rate); - return -8; + return -EINVAL; } ret = snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_length); if (ret < 0) { warn("audio_alsa: Unable to get hw buffer length for device \"%s\": %s.", alsa_out_dev, snd_strerror(ret)); - return -9; + return ret; } ret = snd_pcm_sw_params_current(alsa_handle, alsa_swparams); if (ret < 0) { warn("audio_alsa: Unable to get current sw parameters for device \"%s\": %s.", alsa_out_dev, snd_strerror(ret)); - return -10; + return ret; } ret = snd_pcm_sw_params_set_tstamp_mode(alsa_handle, alsa_swparams, SND_PCM_TSTAMP_ENABLE); if (ret < 0) { warn("audio_alsa: Can't enable timestamp mode of device: \"%s\": %s.", alsa_out_dev, snd_strerror(ret)); - return -11; + return ret; } /* write the sw parameters */ @@ -441,7 +441,7 @@ int actual_open_alsa_device(void) { if (ret < 0) { warn("audio_alsa: Unable to set software parameters of device: \"%s\": %s.", alsa_out_dev, snd_strerror(ret)); - return -12; + return ret; } if (actual_buffer_length < config.audio_backend_buffer_desired_length + minimal_buffer_headroom) { @@ -955,7 +955,7 @@ static int init(int argc, char **argv) { if (ret == 0) actual_close_alsa_device(); else - die("Could not open the alsa device with the settings given."); + die("audio_alsa error %d opening the alsa device. Incorrect settings or device already busy?",ret); } most_recent_write_time = 0; // could be used by the alsa_buffer_monitor_thread_code From 10985a0bec68aafb343dfcdf70378180556f6bfa Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 26 Dec 2018 10:42:52 +0000 Subject: [PATCH 307/569] Bug fix: close a socket even if it doesn't successfully connect. Enhance some debug messages. --- dacp.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/dacp.c b/dacp.c index 4c502cfa..f79a249e 100644 --- a/dacp.c +++ b/dacp.c @@ -133,8 +133,8 @@ void mutex_lock_cleanup(void *arg) { } void connect_cleanup(void *arg) { - // debug(1, "connect cleanup called."); int *fd = (int *)arg; + debug(2, "dacp_send_command: close socket %d.",*fd); close(*fd); } @@ -209,21 +209,23 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // debug(1, "DACP socket could not be created -- error %d: \"%s\".",errno,strerror(errno)); response.code = 497; // Can't establish a socket to the DACP server } else { + pthread_cleanup_push(connect_cleanup, (void *)&sockfd); + debug(2, "dacp_send_command: open socket %d.",sockfd); + struct timeval tv; tv.tv_sec = 2; tv.tv_usec = 0; if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1) - debug(1, "Error %d setting receive timeout for DACP service.", errno); + debug(1, "dacp_send_command: error %d setting receive timeout.", errno); if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1) - debug(1, "Error %d setting send timeout for DACP service.", errno); + debug(1, "dacp_send_command: error %d setting send timeout.", errno); // connect! // debug(1, "DACP socket created."); if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) { - debug(3, "DACP connect failed with errno %d.", errno); + debug(2, "dacp_send_command: connect failed with errno %d.", errno); response.code = 496; // Can't connect to the DACP server } else { - pthread_cleanup_push(connect_cleanup, (void *)&sockfd); // debug(1,"DACP connect succeeded."); snprintf(message, sizeof(message), @@ -231,11 +233,9 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { command, dacp_server.ip_string, dacp_server.port, dacp_server.active_remote_id); // Send command - // debug(1,"DACP connect message: \"%s\".",message); - if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1) - debug(1, "Error %d setting send timeout for DACP service.", errno); + debug(2,"dacp_send_command: connect message: \"%s\".",message); if (send(sockfd, message, strlen(message), 0) != (ssize_t)strlen(message)) { - // debug(1, "Send failed"); + debug(1, "dacp_send_command: send failed."); response.code = 493; // Client failed to send a message } else { @@ -255,7 +255,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { while (needmore && !looperror) { const char *data = buffer; if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1) - debug(1, "Error %d setting receive timeout for DACP service.", errno); + debug(1, "dacp_send_command: error %d setting receive timeout.", errno); int ndata = recv(sockfd, buffer, sizeof(buffer), 0); // debug(3, "Received %d bytes: \"%s\".", ndata, buffer); if (ndata <= 0) { @@ -278,7 +278,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { } if (http_iserror(&rt)) { - debug(3, "Error parsing data."); + debug(3, "dacp_send_command: error parsing data."); free(response.body); response.body = NULL; response.malloced_size = 0; @@ -290,17 +290,17 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { pthread_cleanup_pop( 0); // this should *not* free the malloced buffer -- just pop the malloc cleanup } - pthread_cleanup_pop(1); // this should close the socket - // close(sockfd); - // debug(1,"DACP socket closed."); } + pthread_cleanup_pop(1); // this should close the socket + // close(sockfd); + // debug(1,"DACP socket closed."); } pthread_cleanup_pop(1); // this should unlock the dacp_conversation_lock); // pthread_mutex_unlock(&dacp_conversation_lock); // debug(1,"Sent command\"%s\" with a response body of size %d.",command,response.size); // debug(1,"dacp_conversation_lock released."); } else { - debug(3, "Could not acquire a lock on the dacp transmit/receive section when attempting to " + debug(3, "dacp_send_command: could not acquire a lock on the dacp transmit/receive section when attempting to " "send the command \"%s\". Possible timeout?", command); response.code = 494; // This client is already busy From 6e5f84d2a088df2b2b982e194511fed15112ac87 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 27 Dec 2018 13:59:20 +0000 Subject: [PATCH 308/569] Always leave space for, and put a NULL at the end of an RTSP packet. --- rtsp.c | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/rtsp.c b/rtsp.c index eb2eaeae..84e06607 100644 --- a/rtsp.c +++ b/rtsp.c @@ -558,7 +558,7 @@ fail: enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) { enum rtsp_read_request_response reply = rtsp_read_request_response_ok; ssize_t buflen = 4096; - char *buf = malloc(buflen + 1); + char *buf = malloc(buflen + 1); //add a NUL at the end rtsp_message *msg = NULL; @@ -622,7 +622,7 @@ do { } if (msg_size > buflen) { - buf = realloc(buf, msg_size); + buf = realloc(buf, msg_size+1); if (!buf) { warn("too much content"); reply = rtsp_read_request_response_error; @@ -692,6 +692,8 @@ do { msg->contentlength = inbuf; msg->content = buf; + char *jp = inbuf+buf; + *jp = '\0'; *the_packet = msg; return reply; @@ -1000,22 +1002,38 @@ static void handle_ignore(rtsp_conn_info *conn, rtsp_message *req, rtsp_message */ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req, - __attribute__((unused)) rtsp_message *resp) { + __attribute__((unused)) rtsp_message *resp) { + char *cp = req->content; int cp_left = req->contentlength; + /* + int k = cp_left; + if (k>max_bytes) + k = max_bytes; + for (i = 0; i < k; i++) + snprintf((char *)buf + 2 * i, 3, "%02x", cp[i]); + debug(1, "handle_set_parameter_parameter: \"%s\".",buf); + */ + char *next; while (cp_left && cp) { next = nextline(cp, cp_left); - cp_left -= next - cp; + // note: "next" will return NULL if there is no \r or \n or \r\n at the end of this + // but we are always guaranteed that if cp is not null, it will be pointing to something NUL-terminated - if (!strncmp(cp, "volume: ", 8)) { - float volume = atof(cp + 8); + if (next) + cp_left -= (next - cp); + else + cp_left = 0; + + if (!strncmp(cp, "volume: ", strlen("volume: "))) { + float volume = atof(cp + strlen("volume: ")); // debug(2, "AirPlay request to set volume to: %f.", volume); player_volume(volume, conn); } else #ifdef CONFIG_METADATA - if (!strncmp(cp, "progress: ", 10)) { - char *progress = cp + 10; + if (!strncmp(cp, "progress: ", strlen("progress: "))) { + char *progress = cp + strlen("volume: "); // debug(2, "progress: \"%s\"\n",progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per // second send_ssnc_metadata('prgr', strdup(progress), strlen(progress), 1); From 1828545602ea568d8edae98ac883245c483d1173 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 27 Dec 2018 14:01:32 +0000 Subject: [PATCH 309/569] Account for duration of samples to be played and use the untimed_play when calculating silence-filling backoff period. --- audio_alsa.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index e8360793..f3c27d35 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -952,12 +952,12 @@ static int init(int argc, char **argv) { if (response == 0) { // try opening the device. int ret = actual_open_alsa_device(); + if (ret == 0) actual_close_alsa_device(); else die("audio_alsa error %d opening the alsa device. Incorrect settings or device already busy?",ret); } - most_recent_write_time = 0; // could be used by the alsa_buffer_monitor_thread_code pthread_create(&alsa_buffer_monitor_thread, NULL, &alsa_buffer_monitor_thread_code, NULL); @@ -1284,7 +1284,10 @@ static void flush(void) { } static int play(void *buf, int samples) { - most_recent_write_time = get_absolute_time_in_fp(); // this is to regulate access by the silence filler threrad + uint64_t time_now = get_absolute_time_in_fp(); // this is to regulate access by the silence filler thread + uint64_t sample_duration = ((uint64_t)samples) << 32; + sample_duration = sample_duration / desired_sample_rate; + most_recent_write_time = time_now + sample_duration; return untimed_play(buf,samples); } @@ -1468,7 +1471,7 @@ void *alsa_buffer_monitor_thread_code(void *arg) { uint64_t sleep_time_in_fp = sleep_time_ms; sleep_time_in_fp = sleep_time_in_fp << 32; sleep_time_in_fp = sleep_time_in_fp / 1000; - debug(1,"alsa: sleep_time: %d ms or 0x%" PRIx64 " in fp form.",sleep_time_ms,sleep_time_in_fp); + // debug(1,"alsa: sleep_time: %d ms or 0x%" PRIx64 " in fp form.",sleep_time_ms,sleep_time_in_fp); int frames_of_silence = (desired_sample_rate * sleep_time_ms * 2) / 1000; size_t size_of_silence_buffer = frames_of_silence * frame_size; // debug(1,"Silence buffer length: %u bytes.",size_of_silence_buffer); @@ -1482,7 +1485,8 @@ void *alsa_buffer_monitor_thread_code(void *arg) { while (1) { if (config.keep_dac_busy != 0) { uint64_t present_time = get_absolute_time_in_fp(); - if ((most_recent_write_time == 0) || ((present_time - most_recent_write_time) > (sleep_time_in_fp))) { + + if ((most_recent_write_time == 0) || ((present_time > most_recent_write_time) && ((present_time - most_recent_write_time) > (sleep_time_in_fp)))) { reply = delay(&buffer_size); if (reply != 0) buffer_size = 0; @@ -1494,7 +1498,8 @@ void *alsa_buffer_monitor_thread_code(void *arg) { dither_random_number_store = generate_zero_frames( silence, frames_of_silence, config.output_format, use_dither, // i.e. with dither dither_random_number_store); - play(silence, frames_of_silence); + //debug(1,"Play %d frames of silence with most_recent_write_time of %" PRIx64 ".", frames_of_silence,most_recent_write_time); + untimed_play(silence, frames_of_silence); } } } From 668f78062eba7b981841bfd31730cf275a131a9f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 27 Dec 2018 14:02:19 +0000 Subject: [PATCH 310/569] Fix a few memory leaks by deleting responses even when the reply is somehow illegal. --- dacp.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/dacp.c b/dacp.c index f79a249e..827cb704 100644 --- a/dacp.c +++ b/dacp.c @@ -134,7 +134,7 @@ void mutex_lock_cleanup(void *arg) { void connect_cleanup(void *arg) { int *fd = (int *)arg; - debug(2, "dacp_send_command: close socket %d.",*fd); + // debug(2, "dacp_send_command: close socket %d.",*fd); close(*fd); } @@ -210,7 +210,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { response.code = 497; // Can't establish a socket to the DACP server } else { pthread_cleanup_push(connect_cleanup, (void *)&sockfd); - debug(2, "dacp_send_command: open socket %d.",sockfd); + // debug(2, "dacp_send_command: open socket %d.",sockfd); struct timeval tv; tv.tv_sec = 2; @@ -223,7 +223,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // connect! // debug(1, "DACP socket created."); if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) { - debug(2, "dacp_send_command: connect failed with errno %d.", errno); + // debug(2, "dacp_send_command: connect failed with errno %d.", errno); response.code = 496; // Can't connect to the DACP server } else { // debug(1,"DACP connect succeeded."); @@ -233,7 +233,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { command, dacp_server.ip_string, dacp_server.port, dacp_server.active_remote_id); // Send command - debug(2,"dacp_send_command: connect message: \"%s\".",message); + // debug(3,"dacp_send_command: connect message: \"%s\".",message); if (send(sockfd, message, strlen(message), 0) != (ssize_t)strlen(message)) { debug(1, "dacp_send_command: send failed."); response.code = 493; // Client failed to send a message @@ -259,8 +259,8 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { int ndata = recv(sockfd, buffer, sizeof(buffer), 0); // debug(3, "Received %d bytes: \"%s\".", ndata, buffer); if (ndata <= 0) { - debug(1, "dacp_send_command -- error receiving response for command \"%s\".", - command); + //debug(1, "dacp_send_command -- error receiving response for command \"%s\".", + // command); free(response.body); response.body = NULL; response.malloced_size = 0; @@ -907,10 +907,14 @@ int dacp_get_client_volume(int32_t *result) { debug(1, "Too short a response from getproperty?properties=dmcp.volume"); } // debug(1, "Overall Volume is %d.", overall_volume); + } + + if (server_reply) { + // debug(1, "Freeing response memory."); free(server_reply); - } /* else { - debug(1, "Unexpected response %d to dacp volume control request", response); - } */ + server_reply = NULL; + } + if (result) { *result = overall_volume; // debug(1,"dacp_get_client_volume returns: %" PRId32 ".",overall_volume); @@ -1048,7 +1052,12 @@ int dacp_get_speaker_list(dacp_spkr_stuff *speaker_info, int max_size_of_array, free(server_reply); server_reply = NULL; } else { - debug(1, "Unexpected response %d to dacp speakers request", response); + // debug(1, "Unexpected response %d to dacp speakers request", response); + if (server_reply) { + debug(1, "Freeing response memory."); + free(server_reply); + server_reply = NULL; + } } if (actual_speaker_count) *actual_speaker_count = speaker_count; From ad6c721d5ae6f7b47554ada00fddbebf7ecbb107 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 27 Dec 2018 14:04:31 +0000 Subject: [PATCH 311/569] Format --- audio_alsa.c | 75 ++++++++++++++++++++++++++++------------------------ dacp.c | 9 ++++--- rtsp.c | 17 ++++++------ 3 files changed, 55 insertions(+), 46 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index f3c27d35..764a4381 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -802,7 +802,7 @@ static int init(int argc, char **argv) { // Get the optional "Keep DAC Busy setting" int kdb; if (config_set_lookup_bool(config.cfg, "alsa.disable_standby_mode", &kdb)) { - config.keep_dac_busy = kdb; + config.keep_dac_busy = kdb; } debug(1, "alsa: disable_standby_mode is %s.", config.keep_dac_busy ? "on" : "off"); } @@ -956,9 +956,10 @@ static int init(int argc, char **argv) { if (ret == 0) actual_close_alsa_device(); else - die("audio_alsa error %d opening the alsa device. Incorrect settings or device already busy?",ret); + die("audio_alsa error %d opening the alsa device. Incorrect settings or device already busy?", + ret); } - most_recent_write_time = 0; // could be used by the alsa_buffer_monitor_thread_code + most_recent_write_time = 0; // could be used by the alsa_buffer_monitor_thread_code pthread_create(&alsa_buffer_monitor_thread, NULL, &alsa_buffer_monitor_thread_code, NULL); return response; @@ -969,10 +970,10 @@ static void deinit(void) { pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable // debug(2,"audio_alsa deinit called."); stop(); - debug(1, "Cancel buffer monitor thread."); - pthread_cancel(alsa_buffer_monitor_thread); - debug(1, "Join buffer monitor thread."); - pthread_join(alsa_buffer_monitor_thread, NULL); + debug(1, "Cancel buffer monitor thread."); + pthread_cancel(alsa_buffer_monitor_thread); + debug(1, "Join buffer monitor thread."); + pthread_join(alsa_buffer_monitor_thread, NULL); pthread_setcancelstate(oldState, NULL); } @@ -1136,7 +1137,7 @@ int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) { } int untimed_play(void *buf, int samples) { - + // debug(3,"audio_alsa play called."); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable @@ -1284,11 +1285,12 @@ static void flush(void) { } static int play(void *buf, int samples) { - uint64_t time_now = get_absolute_time_in_fp(); // this is to regulate access by the silence filler thread + uint64_t time_now = + get_absolute_time_in_fp(); // this is to regulate access by the silence filler thread uint64_t sample_duration = ((uint64_t)samples) << 32; sample_duration = sample_duration / desired_sample_rate; - most_recent_write_time = time_now + sample_duration; - return untimed_play(buf,samples); + most_recent_write_time = time_now + sample_duration; + return untimed_play(buf, samples); } static void stop(void) { @@ -1457,12 +1459,13 @@ void *alsa_buffer_monitor_thread_code(void *arg) { // (if no source transformation is happening), Shairport Sync // will deliver fill-in silences and the audio material without adding dither. // So don't insert dither into the silences sent to keep the DAC busy. - + // Also, if the ignore_volume_setting is set, the audio is sent through unaltered, // so, in that circumstance, don't add dither either. int use_dither = 0; - if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && (config.airplay_volume != 0.0)) + if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && + (config.airplay_volume != 0.0)) use_dither = 1; debug(1, "alsa: dither will %sbe added to inter-session silence.", use_dither ? "" : "not "); @@ -1483,28 +1486,32 @@ void *alsa_buffer_monitor_thread_code(void *arg) { long buffer_size; int reply; while (1) { - if (config.keep_dac_busy != 0) { - uint64_t present_time = get_absolute_time_in_fp(); - - if ((most_recent_write_time == 0) || ((present_time > most_recent_write_time) && ((present_time - most_recent_write_time) > (sleep_time_in_fp)))) { - reply = delay(&buffer_size); - if (reply != 0) - buffer_size = 0; - if (buffer_size < frames_of_silence) { - if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && (config.airplay_volume != 0.0)) - use_dither = 1; - else - use_dither = 0; - dither_random_number_store = generate_zero_frames( - silence, frames_of_silence, config.output_format, use_dither, // i.e. with dither - dither_random_number_store); - //debug(1,"Play %d frames of silence with most_recent_write_time of %" PRIx64 ".", frames_of_silence,most_recent_write_time); - untimed_play(silence, frames_of_silence); - } - } - } + if (config.keep_dac_busy != 0) { + uint64_t present_time = get_absolute_time_in_fp(); + + if ((most_recent_write_time == 0) || + ((present_time > most_recent_write_time) && + ((present_time - most_recent_write_time) > (sleep_time_in_fp)))) { + reply = delay(&buffer_size); + if (reply != 0) + buffer_size = 0; + if (buffer_size < frames_of_silence) { + if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && + (config.airplay_volume != 0.0)) + use_dither = 1; + else + use_dither = 0; + dither_random_number_store = generate_zero_frames( + silence, frames_of_silence, config.output_format, use_dither, // i.e. with dither + dither_random_number_store); + // debug(1,"Play %d frames of silence with most_recent_write_time of %" PRIx64 ".", + // frames_of_silence,most_recent_write_time); + untimed_play(silence, frames_of_silence); + } + } + } usleep(sleep_time_ms * 1000); // has a cancellation point in it -// pthread_testcancel(); + // pthread_testcancel(); } } pthread_cleanup_pop(1); diff --git a/dacp.c b/dacp.c index 827cb704..8f93f7df 100644 --- a/dacp.c +++ b/dacp.c @@ -211,7 +211,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { } else { pthread_cleanup_push(connect_cleanup, (void *)&sockfd); // debug(2, "dacp_send_command: open socket %d.",sockfd); - + struct timeval tv; tv.tv_sec = 2; tv.tv_usec = 0; @@ -259,7 +259,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { int ndata = recv(sockfd, buffer, sizeof(buffer), 0); // debug(3, "Received %d bytes: \"%s\".", ndata, buffer); if (ndata <= 0) { - //debug(1, "dacp_send_command -- error receiving response for command \"%s\".", + // debug(1, "dacp_send_command -- error receiving response for command \"%s\".", // command); free(response.body); response.body = NULL; @@ -300,7 +300,8 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // debug(1,"Sent command\"%s\" with a response body of size %d.",command,response.size); // debug(1,"dacp_conversation_lock released."); } else { - debug(3, "dacp_send_command: could not acquire a lock on the dacp transmit/receive section when attempting to " + debug(3, "dacp_send_command: could not acquire a lock on the dacp transmit/receive section " + "when attempting to " "send the command \"%s\". Possible timeout?", command); response.code = 494; // This client is already busy @@ -908,7 +909,7 @@ int dacp_get_client_volume(int32_t *result) { } // debug(1, "Overall Volume is %d.", overall_volume); } - + if (server_reply) { // debug(1, "Freeing response memory."); free(server_reply); diff --git a/rtsp.c b/rtsp.c index 84e06607..bf0ad370 100644 --- a/rtsp.c +++ b/rtsp.c @@ -558,7 +558,7 @@ fail: enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) { enum rtsp_read_request_response reply = rtsp_read_request_response_ok; ssize_t buflen = 4096; - char *buf = malloc(buflen + 1); //add a NUL at the end + char *buf = malloc(buflen + 1); // add a NUL at the end rtsp_message *msg = NULL; @@ -622,7 +622,7 @@ do { } if (msg_size > buflen) { - buf = realloc(buf, msg_size+1); + buf = realloc(buf, msg_size + 1); if (!buf) { warn("too much content"); reply = rtsp_read_request_response_error; @@ -692,7 +692,7 @@ do { msg->contentlength = inbuf; msg->content = buf; - char *jp = inbuf+buf; + char *jp = inbuf + buf; *jp = '\0'; *the_packet = msg; return reply; @@ -1002,8 +1002,8 @@ static void handle_ignore(rtsp_conn_info *conn, rtsp_message *req, rtsp_message */ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req, - __attribute__((unused)) rtsp_message *resp) { - + __attribute__((unused)) rtsp_message *resp) { + char *cp = req->content; int cp_left = req->contentlength; /* @@ -1014,12 +1014,13 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req, snprintf((char *)buf + 2 * i, 3, "%02x", cp[i]); debug(1, "handle_set_parameter_parameter: \"%s\".",buf); */ - + char *next; while (cp_left && cp) { next = nextline(cp, cp_left); // note: "next" will return NULL if there is no \r or \n or \r\n at the end of this - // but we are always guaranteed that if cp is not null, it will be pointing to something NUL-terminated + // but we are always guaranteed that if cp is not null, it will be pointing to something + // NUL-terminated if (next) cp_left -= (next - cp); @@ -1032,7 +1033,7 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req, player_volume(volume, conn); } else #ifdef CONFIG_METADATA - if (!strncmp(cp, "progress: ", strlen("progress: "))) { + if (!strncmp(cp, "progress: ", strlen("progress: "))) { char *progress = cp + strlen("volume: "); // debug(2, "progress: \"%s\"\n",progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per // second From fbb381133e27ec3467054f5291305908600bdf64 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 27 Dec 2018 14:05:48 +0000 Subject: [PATCH 312/569] Fix some memory leaks, improve silence filling backoff time calculation, alwasy add a NUL to an RTSP message. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 0e8bbb1b..578dd4b5 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.3d40], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d41], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 59b3abd9654de8945dd996d459a5f037d19ed8d5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 27 Dec 2018 14:36:29 +0000 Subject: [PATCH 313/569] quieten some dacp debug messages --- dacp.c | 2 +- metadata_hub.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dacp.c b/dacp.c index 8f93f7df..04821650 100644 --- a/dacp.c +++ b/dacp.c @@ -475,7 +475,7 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) { if ((bad_result_count == config.scan_max_bad_response_count) || (idle_scan_count == config.scan_max_inactive_count)) { - debug(1, "DACP server status scanning stopped."); + debug(2, "DACP server status scanning stopped."); dacp_server.scan_enable = 0; } pthread_cleanup_pop(1); diff --git a/metadata_hub.c b/metadata_hub.c index 2b04d388..6938de5f 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -170,7 +170,7 @@ void metadata_hub_modify_epilog(int modified) { if ((metadata_store.dacp_server_active == 0) && (metadata_store.dacp_server_has_been_active != 0)) { - debug(1, "dacp_scanner going inactive -- release track metadata and artwork"); + debug(2, "dacp_scanner going inactive -- release track metadata and artwork"); if (metadata_store.track_metadata) { m = 1; metadata_hub_release_track_metadata(metadata_store.track_metadata); From 919c5fe8b8e4da46bd5641c653376280b3756884 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 28 Dec 2018 10:35:13 +0000 Subject: [PATCH 314/569] Add some diagnostics around the state of the DAC during delays. --- audio_alsa.c | 9 +++++++-- player.c | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 764a4381..33669018 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1493,8 +1493,13 @@ void *alsa_buffer_monitor_thread_code(void *arg) { ((present_time > most_recent_write_time) && ((present_time - most_recent_write_time) > (sleep_time_in_fp)))) { reply = delay(&buffer_size); - if (reply != 0) - buffer_size = 0; + if (reply != 0) { + buffer_size = 0; + char errorstring[1024]; + strerror_r(-reply, (char *)errorstring, sizeof(errorstring)); + debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", + reply, (char *)errorstring); + } if (buffer_size < frames_of_silence) { if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && (config.airplay_volume != 0.0)) diff --git a/player.c b/player.c index 816ff40a..1ce5494a 100644 --- a/player.c +++ b/player.c @@ -1017,8 +1017,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // this might happen if a big clock adjustment was made at just the wrong // time. - debug(1, "Run a bit past the exact start time by %" PRId64 " frames.", - -exact_frame_gap); + debug(1, "Run a bit past the exact start time by %" PRId64 " frames with a DAC delay of %ld frames.", + -exact_frame_gap, dac_delay); if (config.output->flush) config.output->flush(); ab_resync(conn); From fc085726312175c141117383913de7f922a65f9b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 29 Dec 2018 09:34:10 +0000 Subject: [PATCH 315/569] Modify my_snd_pcm_delay but don't test it yet! --- audio_alsa.c | 52 +++++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 33669018..f32df151 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -467,8 +467,9 @@ int actual_open_alsa_device(void) { buffer_size); } */ - debug(1, "The alsa buffer is smaller (%lu bytes) than the desired backend buffer " - "length (%ld) you have chosen.", + debug(1, + "The alsa buffer is smaller (%lu bytes) than the desired backend buffer " + "length (%ld) you have chosen.", actual_buffer_length, config.audio_backend_buffer_desired_length); } @@ -1001,7 +1002,10 @@ static void start(int i_sample_rate, int i_sample_format) { } // assuming pthread cancellation is disabled -int my_snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { +int my_snd_pcm_state_and_delay(snd_pcm_t *pcm, snd_pcm_state_t *state, snd_pcm_sframes_t *delayp) { + *state = SND_PCM_STATE_LAST + 1; // unknown + *delayp = 0; + int ret; snd_pcm_status_t *alsa_snd_pcm_status; snd_pcm_status_alloca(&alsa_snd_pcm_status); @@ -1011,29 +1015,27 @@ int my_snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { ret = snd_pcm_status(pcm, alsa_snd_pcm_status); if (ret) { - *delayp = 0; return ret; } - snd_pcm_state_t state = snd_pcm_status_get_state(alsa_snd_pcm_status); - if (state != SND_PCM_STATE_RUNNING) { - *delayp = 0; - return -EIO; // might be a better code than this... + *state = snd_pcm_status_get_state(alsa_snd_pcm_status); + + if (*state == SND_PCM_STATE_RUNNING) { + + clock_gettime(CLOCK_MONOTONIC, &tn); + snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); + + uint64_t t1 = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; + uint64_t t2 = update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; + uint64_t delta = t1 - t2; + + uint64_t frames_played_since_last_interrupt = + ((uint64_t)desired_sample_rate * delta) / 1000000000; + snd_pcm_sframes_t frames_played_since_last_interrupt_sized = frames_played_since_last_interrupt; + + *delayp = + snd_pcm_status_get_delay(alsa_snd_pcm_status) - frames_played_since_last_interrupt_sized; } - - clock_gettime(CLOCK_MONOTONIC, &tn); - snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); - - uint64_t t1 = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; - uint64_t t2 = update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; - uint64_t delta = t1 - t2; - - uint64_t frames_played_since_last_interrupt = - ((uint64_t)desired_sample_rate * delta) / 1000000000; - snd_pcm_sframes_t frames_played_since_last_interrupt_sized = frames_played_since_last_interrupt; - - *delayp = - snd_pcm_status_get_delay(alsa_snd_pcm_status) - frames_played_since_last_interrupt_sized; return 0; } @@ -1494,11 +1496,11 @@ void *alsa_buffer_monitor_thread_code(void *arg) { ((present_time - most_recent_write_time) > (sleep_time_in_fp)))) { reply = delay(&buffer_size); if (reply != 0) { - buffer_size = 0; + buffer_size = 0; char errorstring[1024]; strerror_r(-reply, (char *)errorstring, sizeof(errorstring)); - debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", - reply, (char *)errorstring); + debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply, + (char *)errorstring); } if (buffer_size < frames_of_silence) { if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && From fe857037decd393acc1070b2421446e497e06adf Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 31 Dec 2018 17:46:05 +0000 Subject: [PATCH 316/569] If output is stalled, don't write any more frames to the output device. --- audio_alsa.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index f32df151..0ae4ab25 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -81,6 +81,7 @@ pthread_t alsa_buffer_monitor_thread; uint64_t stall_monitor_start_time; // zero if not initialised / not started / zeroed by flush long stall_monitor_frame_count; // set to delay at start of time, incremented by any writes uint64_t stall_monitor_error_threshold; // if the time is longer than this, it's an error +int stalled; static snd_output_t *output = NULL; static unsigned int desired_sample_rate; @@ -993,6 +994,7 @@ static void start(int i_sample_rate, int i_sample_format) { frame_index = 0; measurement_data_is_valid = 0; + stalled = 0; stall_monitor_start_time = 0; stall_monitor_frame_count = 0; @@ -1110,15 +1112,18 @@ int delay(long *the_delay) { // hasn't outputted anything since the last call to delay() uint64_t time_stalled = stall_monitor_time_now - stall_monitor_start_time; if (time_stalled > stall_monitor_error_threshold) { + stalled = 1; reply = sps_extra_errno_output_stalled; } } else { // is outputting stuff, so restart the monitoring here + stalled = 0; stall_monitor_start_time = stall_monitor_time_now; stall_monitor_frame_count = *the_delay; } } else { // if there is an error or the delay is zero (from which it is assumed there is no output) + stalled = 0; stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes } @@ -1180,7 +1185,7 @@ int untimed_play(void *buf, int samples) { debug(1, "NULL buffer passed to pcm_writei -- skipping it"); if (samples == 0) debug(1, "empty buffer being passed to pcm_writei -- skipping it"); - if ((samples != 0) && (buf != NULL)) { + if ((samples != 0) && (buf != NULL) && (stalled == 0)) { // debug(3, "write %d frames.", samples); err = alsa_pcm_write(alsa_handle, buf, samples); @@ -1499,8 +1504,8 @@ void *alsa_buffer_monitor_thread_code(void *arg) { buffer_size = 0; char errorstring[1024]; strerror_r(-reply, (char *)errorstring, sizeof(errorstring)); - debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply, - (char *)errorstring); + //debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply, + // (char *)errorstring); } if (buffer_size < frames_of_silence) { if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && From b4505593b27d77a38bbef58a5ade9d869e7c3c3c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 31 Dec 2018 17:46:31 +0000 Subject: [PATCH 317/569] If output is stalled, don't write any more frames to the output device. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 578dd4b5..3ef39546 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.3d41], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d42], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 6f8b35d121907e0a04c0b20bf552b3a5cdb8ab5c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 2 Jan 2019 14:43:25 +0000 Subject: [PATCH 318/569] Try out a new alsa delay routine. Could be very buggy! --- audio_alsa.c | 125 ++++++++++++++++++++++++++++++++++++++++++++++++--- common.h | 3 +- player.c | 19 ++++---- 3 files changed, 133 insertions(+), 14 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 0ae4ab25..3cf00d6d 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -44,6 +44,7 @@ static void start(int i_sample_rate, int i_sample_format); static int play(void *buf, int samples); static void stop(void); static void flush(void); +// int delay(long *the_delay); int delay(long *the_delay); void do_mute(int request); int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played); @@ -468,9 +469,8 @@ int actual_open_alsa_device(void) { buffer_size); } */ - debug(1, - "The alsa buffer is smaller (%lu bytes) than the desired backend buffer " - "length (%ld) you have chosen.", + debug(1, "The alsa buffer is smaller (%lu bytes) than the desired backend buffer " + "length (%ld) you have chosen.", actual_buffer_length, config.audio_backend_buffer_desired_length); } @@ -601,6 +601,14 @@ int actual_open_alsa_device(void) { break; } } + + // now open the device + ret = snd_pcm_prepare(alsa_handle); + if (ret) { + warn("alsa: can't prepare device."); + return ret; + } + return 0; } @@ -1042,6 +1050,113 @@ int my_snd_pcm_state_and_delay(snd_pcm_t *pcm, snd_pcm_state_t *state, snd_pcm_s } int delay(long *the_delay) { + // returns 0 if the device is in a valid state -- SND_PCM_STATE_RUNNING or SND_PCM_STATE_PREPARED + // and returns the actual delay if running or 0 if prepared in *the_delay + + // otherwise return an error code + // the error code could be a Unix errno code or a snderror code, or + // the sps_extra_code_output_stalled or the sps_extra_code_output_state_cannot_make_ready codes + int ret = 0; + *the_delay = 0; + if (alsa_handle == NULL) + ret = ENODEV; + else { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0); + + snd_pcm_state_t state; + snd_pcm_sframes_t delay; + + snd_pcm_status_t *alsa_snd_pcm_status; + snd_pcm_status_alloca(&alsa_snd_pcm_status); + + struct timespec tn; // time now + snd_htimestamp_t update_timestamp; // actually a struct timespec + + ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); + if (ret == 0) { + state = snd_pcm_status_get_state(alsa_snd_pcm_status); + + if ((state == SND_PCM_STATE_SUSPENDED) || (state == SND_PCM_STATE_XRUN)) { + debug(1, "alsa: recovering from suspended or xrun in delay."); + ret = snd_pcm_recover(alsa_handle, ret, 1); + if (ret == 0) { + ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); + if (ret == 0) + state = snd_pcm_status_get_state(alsa_snd_pcm_status); + } + } + + // here, the device must be either SND_PCM_STATE_RUNNING + // or SND_PCM_STATE_PREPARED or SND_PCM_STATE_DRAINING + + if (ret == 0) { + + if ((state == SND_PCM_STATE_RUNNING) || (state == SND_PCM_STATE_DRAINING)) { + + snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); + delay = snd_pcm_status_get_delay(alsa_snd_pcm_status); + + if (state == SND_PCM_STATE_DRAINING) + debug(1, "alsa: draining with a delay of %d.", delay); + + clock_gettime(CLOCK_MONOTONIC, &tn); + uint64_t time_now_ns = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; + uint64_t update_timestamp_ns = + update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; + + // see if it's stalled + + if ((stall_monitor_start_time != 0) && (stall_monitor_frame_count == delay)) { + // hasn't outputted anything since the last call to delay() + + if (((update_timestamp_ns - stall_monitor_start_time) > + stall_monitor_error_threshold) || + ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) { + stalled = 1; + ret = sps_extra_code_output_stalled; + } + } else { + stalled = 0; + stall_monitor_start_time = update_timestamp_ns; + stall_monitor_frame_count = delay; + } + + if (ret == 0) { + uint64_t delta = time_now_ns - update_timestamp_ns; + + uint64_t frames_played_since_last_interrupt = + ((uint64_t)desired_sample_rate * delta) / 1000000000; + snd_pcm_sframes_t frames_played_since_last_interrupt_sized = + frames_played_since_last_interrupt; + + *the_delay = delay - frames_played_since_last_interrupt_sized; + } + } else { // not running, thus no delay information, thus can't check for stall + stalled = 0; + stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush + stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes + + // not running, thus no delay information, thus can't check for frame rates + frame_index = 0; // we'll be starting over... + measurement_data_is_valid = 0; + + if ((state != SND_PCM_STATE_PREPARED)) { + debug(1, "alsa: can't get device into valid state in delay. State is %d.",state); + ret = sps_extra_code_output_state_cannot_make_ready; + } + } + } + } + debug_mutex_unlock(&alsa_mutex, 0); + pthread_cleanup_pop(0); + pthread_setcancelstate(oldState, NULL); + } + return ret; +} + +int old_delay(long *the_delay) { *the_delay = 0; // snd_pcm_sframes_t is a signed long -- hence the return of a "long" int reply = 0; @@ -1113,7 +1228,7 @@ int delay(long *the_delay) { uint64_t time_stalled = stall_monitor_time_now - stall_monitor_start_time; if (time_stalled > stall_monitor_error_threshold) { stalled = 1; - reply = sps_extra_errno_output_stalled; + reply = sps_extra_code_output_stalled; } } else { // is outputting stuff, so restart the monitoring here @@ -1504,7 +1619,7 @@ void *alsa_buffer_monitor_thread_code(void *arg) { buffer_size = 0; char errorstring[1024]; strerror_r(-reply, (char *)errorstring, sizeof(errorstring)); - //debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply, + // debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply, // (char *)errorstring); } if (buffer_size < frames_of_silence) { diff --git a/common.h b/common.h index 8d028b3b..c42d76a3 100644 --- a/common.h +++ b/common.h @@ -30,7 +30,8 @@ enum dbus_session_type { } dbt_type; #endif -#define sps_extra_errno_output_stalled 32768 +#define sps_extra_code_output_stalled 32768 +#define sps_extra_code_output_state_cannot_make_ready 32769 enum endian_type { SS_LITTLE_ENDIAN = 0, diff --git a/player.c b/player.c index 1ce5494a..b14b9daf 100644 --- a/player.c +++ b/player.c @@ -473,7 +473,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in abuf_t *abuf = 0; if (!conn->ab_synced) { - // if this is the first packetÉ + // if this is the first packet... debug(3, "syncing to seqno %u.", seqno); conn->ab_write = seqno; conn->ab_read = seqno; @@ -495,7 +495,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in } if (conn->ab_write == - seqno) { // if this is the expected packet (which could be the first packetÉ) + seqno) { // if this is the expected packet (which could be the first packet...) uint64_t reception_time = get_absolute_time_in_fp(); if (conn->input_frame_rate_starting_point_is_valid == 0) { if ((conn->packet_count_since_flush >= 500) && (conn->packet_count_since_flush <= 510)) { @@ -1017,7 +1017,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // this might happen if a big clock adjustment was made at just the wrong // time. - debug(1, "Run a bit past the exact start time by %" PRId64 " frames with a DAC delay of %ld frames.", + debug(1, "Run a bit past the exact start time by %" PRId64 + " frames with a DAC delay of %ld frames.", -exact_frame_gap, dac_delay); if (config.output->flush) config.output->flush(); @@ -1084,7 +1085,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { 1; // even if we haven't sent silence because it's zero frames long... } } else { - if ((resp == sps_extra_errno_output_stalled) && + if ((resp == sps_extra_code_output_stalled) && (conn->unfixable_error_reported == 0)) { conn->unfixable_error_reported = 1; if (config.cmd_unfixable) { @@ -1973,8 +1974,8 @@ void *player_thread_func(void *arg) { if (config.output->delay) { long l_delay; resp = config.output->delay(&l_delay); - current_delay = l_delay; if (resp == 0) { // no error + current_delay = l_delay; if (current_delay < 0) { debug(2, "Underrun of %lld frames reported, but ignored.", current_delay); current_delay = @@ -1984,7 +1985,8 @@ void *player_thread_func(void *arg) { minimum_dac_queue_size = current_delay; // update for display later } } else { - if ((resp == sps_extra_errno_output_stalled) && + current_delay = 0; + if ((resp == sps_extra_code_output_stalled) && (conn->unfixable_error_reported == 0)) { conn->unfixable_error_reported = 1; if (config.cmd_unfixable) { @@ -2005,7 +2007,7 @@ void *player_thread_func(void *arg) { } } - if (resp >= 0) { + if (resp == 0) { uint32_t should_be_frame_32; local_time_to_frame(local_time_now, &should_be_frame_32, conn); @@ -2828,4 +2830,5 @@ int player_stop(rtsp_conn_info *conn) { // debuglev = dl; return -1; } -} \ No newline at end of file +} + From 59d26918f620f2c9ed9791f86238ad2285260c91 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 2 Jan 2019 19:49:15 +0000 Subject: [PATCH 319/569] new play(), delay(), delay_prep_and_status() routines in place and working, but with the old stuff still around. --- audio_alsa.c | 299 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 215 insertions(+), 84 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 3cf00d6d..79276aba 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -254,6 +254,7 @@ int actual_open_alsa_device(void) { if (config.no_sync != 0) audio_alsa.delay = NULL; + // ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, 0); if (ret < 0) return ret; @@ -1049,8 +1050,130 @@ int my_snd_pcm_state_and_delay(snd_pcm_t *pcm, snd_pcm_state_t *state, snd_pcm_s return 0; } +// assuming pthread cancellation is disabled +int my_snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { + int ret; + snd_pcm_status_t *alsa_snd_pcm_status; + snd_pcm_status_alloca(&alsa_snd_pcm_status); + + struct timespec tn; // time now + snd_htimestamp_t update_timestamp; // actually a struct timespec + + ret = snd_pcm_status(pcm, alsa_snd_pcm_status); + if (ret) { + *delayp = 0; + return ret; + } + + snd_pcm_state_t state = snd_pcm_status_get_state(alsa_snd_pcm_status); + if (state != SND_PCM_STATE_RUNNING) { + *delayp = 0; + return -EIO; // might be a better code than this... + } + + clock_gettime(CLOCK_MONOTONIC, &tn); + snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); + + uint64_t t1 = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; + uint64_t t2 = update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; + uint64_t delta = t1 - t2; + + uint64_t frames_played_since_last_interrupt = + ((uint64_t)desired_sample_rate * delta) / 1000000000; + snd_pcm_sframes_t frames_played_since_last_interrupt_sized = frames_played_since_last_interrupt; + + *delayp = + snd_pcm_status_get_delay(alsa_snd_pcm_status) - frames_played_since_last_interrupt_sized; + return 0; +} + +int delay_prep_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { + snd_pcm_status_t *alsa_snd_pcm_status; + snd_pcm_status_alloca(&alsa_snd_pcm_status); + + struct timespec tn; // time now + snd_htimestamp_t update_timestamp; // actually a struct timespec + + int ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); + if (ret == 0) { + *state = snd_pcm_status_get_state(alsa_snd_pcm_status); + + if ((*state == SND_PCM_STATE_SUSPENDED) || (*state == SND_PCM_STATE_XRUN)) { + debug(1, "alsa: recovering from suspended or xrun in delay."); + ret = snd_pcm_recover(alsa_handle, ret, 1); + if (ret == 0) { + ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); + if (ret == 0) + *state = snd_pcm_status_get_state(alsa_snd_pcm_status); + } + } + + // here, the device must be either SND_PCM_STATE_RUNNING + // or SND_PCM_STATE_PREPARED or SND_PCM_STATE_DRAINING + + if (ret == 0) { + + if ((*state == SND_PCM_STATE_RUNNING) || (*state == SND_PCM_STATE_DRAINING)) { + + snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); + *delay = snd_pcm_status_get_delay(alsa_snd_pcm_status); + + if (*state == SND_PCM_STATE_DRAINING) + debug(1, "alsa: draining with a delay of %d.", delay); + + clock_gettime(CLOCK_MONOTONIC, &tn); + uint64_t time_now_ns = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; + uint64_t update_timestamp_ns = + update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; + + // see if it's stalled + + if ((stall_monitor_start_time != 0) && (stall_monitor_frame_count == *delay)) { + // hasn't outputted anything since the last call to delay() + + if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) || + ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) { + stalled = 1; + ret = sps_extra_code_output_stalled; + } + } else { + stalled = 0; + stall_monitor_start_time = update_timestamp_ns; + stall_monitor_frame_count = *delay; + } + + if (ret == 0) { + uint64_t delta = time_now_ns - update_timestamp_ns; + + uint64_t frames_played_since_last_interrupt = + ((uint64_t)desired_sample_rate * delta) / 1000000000; + snd_pcm_sframes_t frames_played_since_last_interrupt_sized = + frames_played_since_last_interrupt; + + *delay = *delay - frames_played_since_last_interrupt_sized; + } + } else { // not running, thus no delay information, thus can't check for stall + stalled = 0; + stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush + stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes + + // not running, thus no delay information, thus can't check for frame rates + frame_index = 0; // we'll be starting over... + measurement_data_is_valid = 0; + + if ((*state != SND_PCM_STATE_PREPARED)) { + debug(1, "alsa: can't get device into valid state in delay. State is %d.", state); + ret = sps_extra_code_output_state_cannot_make_ready; + } + } + } + } + return ret; +} + int delay(long *the_delay) { // returns 0 if the device is in a valid state -- SND_PCM_STATE_RUNNING or SND_PCM_STATE_PREPARED + // or SND_PCM_STATE_DRAINING // and returns the actual delay if running or 0 if prepared in *the_delay // otherwise return an error code @@ -1062,96 +1185,26 @@ int delay(long *the_delay) { ret = ENODEV; else { int oldState; + + snd_pcm_state_t state; + snd_pcm_sframes_t my_delay; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0); - snd_pcm_state_t state; - snd_pcm_sframes_t delay; + ret = delay_prep_and_status(&state, &my_delay); - snd_pcm_status_t *alsa_snd_pcm_status; - snd_pcm_status_alloca(&alsa_snd_pcm_status); + ret = 0; - struct timespec tn; // time now - snd_htimestamp_t update_timestamp; // actually a struct timespec + if ((ret == 0) && (state != SND_PCM_STATE_RUNNING) && (state != SND_PCM_STATE_DRAINING) && + (state != SND_PCM_STATE_PREPARED)) + ret = sps_extra_code_output_state_cannot_make_ready; - ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); - if (ret == 0) { - state = snd_pcm_status_get_state(alsa_snd_pcm_status); - - if ((state == SND_PCM_STATE_SUSPENDED) || (state == SND_PCM_STATE_XRUN)) { - debug(1, "alsa: recovering from suspended or xrun in delay."); - ret = snd_pcm_recover(alsa_handle, ret, 1); - if (ret == 0) { - ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); - if (ret == 0) - state = snd_pcm_status_get_state(alsa_snd_pcm_status); - } - } - - // here, the device must be either SND_PCM_STATE_RUNNING - // or SND_PCM_STATE_PREPARED or SND_PCM_STATE_DRAINING - - if (ret == 0) { - - if ((state == SND_PCM_STATE_RUNNING) || (state == SND_PCM_STATE_DRAINING)) { - - snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); - delay = snd_pcm_status_get_delay(alsa_snd_pcm_status); - - if (state == SND_PCM_STATE_DRAINING) - debug(1, "alsa: draining with a delay of %d.", delay); - - clock_gettime(CLOCK_MONOTONIC, &tn); - uint64_t time_now_ns = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; - uint64_t update_timestamp_ns = - update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; - - // see if it's stalled - - if ((stall_monitor_start_time != 0) && (stall_monitor_frame_count == delay)) { - // hasn't outputted anything since the last call to delay() - - if (((update_timestamp_ns - stall_monitor_start_time) > - stall_monitor_error_threshold) || - ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) { - stalled = 1; - ret = sps_extra_code_output_stalled; - } - } else { - stalled = 0; - stall_monitor_start_time = update_timestamp_ns; - stall_monitor_frame_count = delay; - } - - if (ret == 0) { - uint64_t delta = time_now_ns - update_timestamp_ns; - - uint64_t frames_played_since_last_interrupt = - ((uint64_t)desired_sample_rate * delta) / 1000000000; - snd_pcm_sframes_t frames_played_since_last_interrupt_sized = - frames_played_since_last_interrupt; - - *the_delay = delay - frames_played_since_last_interrupt_sized; - } - } else { // not running, thus no delay information, thus can't check for stall - stalled = 0; - stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush - stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes - - // not running, thus no delay information, thus can't check for frame rates - frame_index = 0; // we'll be starting over... - measurement_data_is_valid = 0; - - if ((state != SND_PCM_STATE_PREPARED)) { - debug(1, "alsa: can't get device into valid state in delay. State is %d.",state); - ret = sps_extra_code_output_state_cannot_make_ready; - } - } - } - } debug_mutex_unlock(&alsa_mutex, 0); pthread_cleanup_pop(0); pthread_setcancelstate(oldState, NULL); + + *the_delay = my_delay; // note: snd_pcm_sframes_t is a long } return ret; } @@ -1171,7 +1224,7 @@ int old_delay(long *the_delay) { snd_pcm_state_t dac_state = snd_pcm_state(alsa_handle); if (dac_state == SND_PCM_STATE_RUNNING) { - reply = snd_pcm_delay(alsa_handle, the_delay); + reply = my_snd_pcm_delay(alsa_handle, the_delay); if (reply != 0) { debug(1, "Error %d in delay(): \"%s\". Delay reported is %d frames.", reply, @@ -1260,6 +1313,84 @@ int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) { int untimed_play(void *buf, int samples) { + // debug(3,"audio_alsa play called."); + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable + int ret = 0; + if (alsa_handle == NULL) { + + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); + ret = actual_open_alsa_device(); + if (ret == 0) { + if (audio_alsa.volume) + do_volume(set_volume); + if (audio_alsa.mute) + do_mute(0); + } + + debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(0); // release the mutex + } + if (ret == 0) { + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0); + + snd_pcm_state_t state; + snd_pcm_sframes_t my_delay; + ret = delay_prep_and_status(&state, &my_delay); + + if (ret == 0) { // will be non-zero if an error or a stall + if ((state == SND_PCM_STATE_PREPARED) || (state == SND_PCM_STATE_RUNNING)) { + + if ((samples != 0) && (buf != NULL)) { + + // debug(3, "write %d frames.", samples); + ret = alsa_pcm_write(alsa_handle, buf, samples); + if (ret == samples) { + stall_monitor_frame_count += samples; + + if (frame_index == 0) { + frames_sent_for_playing = samples; + } else { + frames_sent_for_playing += samples; + } + + const uint64_t start_measurement_from_this_frame = + (2 * 44100) / 352; // two seconds of frames… + + frame_index++; + + if ((frame_index == start_measurement_from_this_frame) || + ((frame_index > start_measurement_from_this_frame) && (frame_index % 32 == 0))) { + + measurement_time = get_absolute_time_in_fp(); + frames_played_at_measurement_time = frames_sent_for_playing - my_delay - samples; + + if (frame_index == start_measurement_from_this_frame) { + // debug(1, "Start frame counting"); + frames_played_at_measurement_start_time = frames_played_at_measurement_time; + measurement_start_time = measurement_time; + measurement_data_is_valid = 1; + } + } + } else { + debug(1, "alsa: error writing to alsa device."); + } + } + } else { + debug(1, "alsa: device in incorrect state (%d) for play.", state); + frame_index = 0; + measurement_data_is_valid = 0; + } + } + debug_mutex_unlock(&alsa_mutex, 0); + pthread_cleanup_pop(0); // release the mutex + } + pthread_setcancelstate(oldState, NULL); + return ret; +} + +int old_untimed_play(void *buf, int samples) { + // debug(3,"audio_alsa play called."); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable @@ -1619,8 +1750,8 @@ void *alsa_buffer_monitor_thread_code(void *arg) { buffer_size = 0; char errorstring[1024]; strerror_r(-reply, (char *)errorstring, sizeof(errorstring)); - // debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply, - // (char *)errorstring); + debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply, + (char *)errorstring); } if (buffer_size < frames_of_silence) { if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && From 8d8b8c05f6025039e7cfda6d8f225b22fcb6948a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 2 Jan 2019 19:54:12 +0000 Subject: [PATCH 320/569] new play(), delay(), delay_prep_and_status() routines in place and working, but with the old stuff taken out. --- audio_alsa.c | 291 +-------------------------------------------------- 1 file changed, 1 insertion(+), 290 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 79276aba..c41c5a06 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1,7 +1,7 @@ /* * libalsa output driver. This file is part of Shairport. * Copyright (c) Muffinman, Skaman 2013 - * Copyright (c) Mike Brady 2014 -- 2018 + * Copyright (c) Mike Brady 2014 -- 2019 * All rights reserved. * * Permission is hereby granted, free of charge, to any person @@ -44,7 +44,6 @@ static void start(int i_sample_rate, int i_sample_format); static int play(void *buf, int samples); static void stop(void); static void flush(void); -// int delay(long *the_delay); int delay(long *the_delay); void do_mute(int request); int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played); @@ -82,7 +81,6 @@ pthread_t alsa_buffer_monitor_thread; uint64_t stall_monitor_start_time; // zero if not initialised / not started / zeroed by flush long stall_monitor_frame_count; // set to delay at start of time, incremented by any writes uint64_t stall_monitor_error_threshold; // if the time is longer than this, it's an error -int stalled; static snd_output_t *output = NULL; static unsigned int desired_sample_rate; @@ -1003,7 +1001,6 @@ static void start(int i_sample_rate, int i_sample_format) { frame_index = 0; measurement_data_is_valid = 0; - stalled = 0; stall_monitor_start_time = 0; stall_monitor_frame_count = 0; @@ -1012,81 +1009,6 @@ static void start(int i_sample_rate, int i_sample_format) { stall_monitor_error_threshold = (stall_monitor_error_threshold << 32) / 1000000; // now in fp form } -// assuming pthread cancellation is disabled -int my_snd_pcm_state_and_delay(snd_pcm_t *pcm, snd_pcm_state_t *state, snd_pcm_sframes_t *delayp) { - *state = SND_PCM_STATE_LAST + 1; // unknown - *delayp = 0; - - int ret; - snd_pcm_status_t *alsa_snd_pcm_status; - snd_pcm_status_alloca(&alsa_snd_pcm_status); - - struct timespec tn; // time now - snd_htimestamp_t update_timestamp; // actually a struct timespec - - ret = snd_pcm_status(pcm, alsa_snd_pcm_status); - if (ret) { - return ret; - } - - *state = snd_pcm_status_get_state(alsa_snd_pcm_status); - - if (*state == SND_PCM_STATE_RUNNING) { - - clock_gettime(CLOCK_MONOTONIC, &tn); - snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); - - uint64_t t1 = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; - uint64_t t2 = update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; - uint64_t delta = t1 - t2; - - uint64_t frames_played_since_last_interrupt = - ((uint64_t)desired_sample_rate * delta) / 1000000000; - snd_pcm_sframes_t frames_played_since_last_interrupt_sized = frames_played_since_last_interrupt; - - *delayp = - snd_pcm_status_get_delay(alsa_snd_pcm_status) - frames_played_since_last_interrupt_sized; - } - return 0; -} - -// assuming pthread cancellation is disabled -int my_snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { - int ret; - snd_pcm_status_t *alsa_snd_pcm_status; - snd_pcm_status_alloca(&alsa_snd_pcm_status); - - struct timespec tn; // time now - snd_htimestamp_t update_timestamp; // actually a struct timespec - - ret = snd_pcm_status(pcm, alsa_snd_pcm_status); - if (ret) { - *delayp = 0; - return ret; - } - - snd_pcm_state_t state = snd_pcm_status_get_state(alsa_snd_pcm_status); - if (state != SND_PCM_STATE_RUNNING) { - *delayp = 0; - return -EIO; // might be a better code than this... - } - - clock_gettime(CLOCK_MONOTONIC, &tn); - snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); - - uint64_t t1 = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; - uint64_t t2 = update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; - uint64_t delta = t1 - t2; - - uint64_t frames_played_since_last_interrupt = - ((uint64_t)desired_sample_rate * delta) / 1000000000; - snd_pcm_sframes_t frames_played_since_last_interrupt_sized = frames_played_since_last_interrupt; - - *delayp = - snd_pcm_status_get_delay(alsa_snd_pcm_status) - frames_played_since_last_interrupt_sized; - return 0; -} - int delay_prep_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { snd_pcm_status_t *alsa_snd_pcm_status; snd_pcm_status_alloca(&alsa_snd_pcm_status); @@ -1133,11 +1055,9 @@ int delay_prep_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) || ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) { - stalled = 1; ret = sps_extra_code_output_stalled; } } else { - stalled = 0; stall_monitor_start_time = update_timestamp_ns; stall_monitor_frame_count = *delay; } @@ -1153,7 +1073,6 @@ int delay_prep_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { *delay = *delay - frames_played_since_last_interrupt_sized; } } else { // not running, thus no delay information, thus can't check for stall - stalled = 0; stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes @@ -1209,95 +1128,6 @@ int delay(long *the_delay) { return ret; } -int old_delay(long *the_delay) { - *the_delay = 0; - // snd_pcm_sframes_t is a signed long -- hence the return of a "long" - int reply = 0; - // debug(3,"audio_alsa delay called."); - if (alsa_handle == NULL) { - reply = -ENODEV; - } else { - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0); - int derr; - snd_pcm_state_t dac_state = snd_pcm_state(alsa_handle); - if (dac_state == SND_PCM_STATE_RUNNING) { - - reply = my_snd_pcm_delay(alsa_handle, the_delay); - - if (reply != 0) { - debug(1, "Error %d in delay(): \"%s\". Delay reported is %d frames.", reply, - snd_strerror(reply), *the_delay); - derr = snd_pcm_recover(alsa_handle, reply, 1); - if (derr < 0) - warn("Error %d -- could not clear an error after attempting delay(): \"%s\".", derr, - snd_strerror(derr)); - - frame_index = 0; - measurement_data_is_valid = 0; - } else { - if (*the_delay == 0) { - // there's nothing in the pipeline, so we can't measure frame rate. - frame_index = 0; // we'll be starting over... - measurement_data_is_valid = 0; - } - } - } else { - reply = -EIO; // shomething is wrong - frame_index = 0; // we'll be starting over... - measurement_data_is_valid = 0; - - if (dac_state == SND_PCM_STATE_PREPARED) { - debug(2, "delay not available -- state is SND_PCM_STATE_PREPARED"); - } else { - if (dac_state == SND_PCM_STATE_XRUN) { - debug(2, "delay not available -- state is SND_PCM_STATE_XRUN"); - } else { - - debug(1, "Error -- ALSA delay(): bad state: %d.", dac_state); - } - if ((derr = snd_pcm_prepare(alsa_handle))) { - debug(1, "Error preparing after delay error: \"%s\".", snd_strerror(derr)); - derr = snd_pcm_recover(alsa_handle, derr, 1); - if (derr < 0) - warn("Error %d -- could not clear an error after attempting to recover following a " - "delay(): \"%s\".", - derr, snd_strerror(derr)); - } - } - } - debug_mutex_unlock(&alsa_mutex, 0); - pthread_cleanup_pop(0); - // here, occasionally pretend there's a problem with pcm_get_delay() - // if ((random() % 100000) < 3) // keep it pretty rare - // reply = -EIO; // pretend something bad has happened - pthread_setcancelstate(oldState, NULL); - } - if ((reply == 0) && (*the_delay != 0)) { - uint64_t stall_monitor_time_now = get_absolute_time_in_fp(); - if ((stall_monitor_start_time != 0) && (stall_monitor_frame_count == *the_delay)) { - // hasn't outputted anything since the last call to delay() - uint64_t time_stalled = stall_monitor_time_now - stall_monitor_start_time; - if (time_stalled > stall_monitor_error_threshold) { - stalled = 1; - reply = sps_extra_code_output_stalled; - } - } else { - // is outputting stuff, so restart the monitoring here - stalled = 0; - stall_monitor_start_time = stall_monitor_time_now; - stall_monitor_frame_count = *the_delay; - } - } else { - // if there is an error or the delay is zero (from which it is assumed there is no output) - stalled = 0; - stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush - stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes - } - return reply; -} - int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) { int response = 0; // zero means okay if (measurement_data_is_valid) { @@ -1389,125 +1219,6 @@ int untimed_play(void *buf, int samples) { return ret; } -int old_untimed_play(void *buf, int samples) { - - // debug(3,"audio_alsa play called."); - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable - int ret = 0; - if (alsa_handle == NULL) { - - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); - ret = actual_open_alsa_device(); - if (ret == 0) { - if (audio_alsa.volume) - do_volume(set_volume); - if (audio_alsa.mute) - do_mute(0); - } - - debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(0); // release the mutex - } - if (ret == 0) { - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0); - // snd_pcm_sframes_t current_delay = 0; - int err, err2; - if ((snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) || - (snd_pcm_state(alsa_handle) == SND_PCM_STATE_OPEN) || - (snd_pcm_state(alsa_handle) == SND_PCM_STATE_SETUP)) { - if ((err = snd_pcm_prepare(alsa_handle))) { - debug(1, "Error preparing after underrun: \"%s\".", snd_strerror(err)); - err = snd_pcm_recover(alsa_handle, err, 1); - if (err < 0) - warn("Error %d -- could not clear an error after detecting underrun in play(): \"%s\".", - err, snd_strerror(err)); - } - frame_index = 0; // we'll be starting over - measurement_data_is_valid = 0; - } else if ((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) || - (snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING)) { - if (buf == NULL) - debug(1, "NULL buffer passed to pcm_writei -- skipping it"); - if (samples == 0) - debug(1, "empty buffer being passed to pcm_writei -- skipping it"); - if ((samples != 0) && (buf != NULL) && (stalled == 0)) { - // debug(3, "write %d frames.", samples); - err = alsa_pcm_write(alsa_handle, buf, samples); - - stall_monitor_frame_count += samples; - - if (err < 0) { - frame_index = 0; - measurement_data_is_valid = 0; - debug(1, "Error %d writing %d samples in play(): \"%s\".", err, samples, - snd_strerror(err)); - err = snd_pcm_recover(alsa_handle, err, 1); - if (err < 0) - warn("Error %d -- could not clear an error after attempting to write %d samples in " - "play(): \"%s\".", - err, samples, snd_strerror(err)); - } - - if (frame_index == 0) { - frames_sent_for_playing = samples; - } else { - frames_sent_for_playing += samples; - } - const uint64_t start_measurement_from_this_frame = - (2 * 44100) / 352; // two seconds of frames… - frame_index++; - if ((frame_index == start_measurement_from_this_frame) || (frame_index % 32 == 0)) { - long fl = 0; - err2 = snd_pcm_delay(alsa_handle, &fl); - if (err2 != 0) { - debug(1, "Error %d in delay in play(): \"%s\". Delay reported is %d frames.", err2, - snd_strerror(err2), fl); - err2 = snd_pcm_recover(alsa_handle, err2, 1); - if (err2 < 0) - warn("Error %d -- could not clear an error after checking delay in play(): \"%s\".", - err2, snd_strerror(err2)); - frame_index = 0; - measurement_data_is_valid = 0; - } else { - if (fl == 0) { - // there's nothing in the pipeline, so we can't measure frame rate. - frame_index = 0; // we'll be starting over... - measurement_data_is_valid = 0; - } - } - - measurement_time = get_absolute_time_in_fp(); - frames_played_at_measurement_time = frames_sent_for_playing - fl; - if (frame_index == start_measurement_from_this_frame) { - // debug(1, "Start frame counting"); - frames_played_at_measurement_start_time = frames_played_at_measurement_time; - measurement_start_time = measurement_time; - measurement_data_is_valid = 1; - } - } - } - } else { - debug(1, "Error -- ALSA device in incorrect state (%d) for play.", - snd_pcm_state(alsa_handle)); - if ((err = snd_pcm_prepare(alsa_handle))) { - debug(1, "Error preparing after play error: \"%s\".", snd_strerror(err)); - err2 = snd_pcm_recover(alsa_handle, err, 1); - if (err2 < 0) - warn("Error %d -- could not clear an error after reporting ALSA device in incorrect " - "state for play: \"%s\".", - err2, snd_strerror(err2)); - } - frame_index = 0; - measurement_data_is_valid = 0; - } - debug_mutex_unlock(&alsa_mutex, 0); - pthread_cleanup_pop(0); // release the mutex - } - pthread_setcancelstate(oldState, NULL); - return ret; -} - static void flush(void) { // debug(2,"audio_alsa flush called."); int oldState; From 15729ebcd43005039dc40169491599b3b53177ff Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 2 Jan 2019 22:40:48 +0000 Subject: [PATCH 321/569] Fix trivial error in a debug message. --- audio_alsa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_alsa.c b/audio_alsa.c index c41c5a06..26aac7b0 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1081,7 +1081,7 @@ int delay_prep_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { measurement_data_is_valid = 0; if ((*state != SND_PCM_STATE_PREPARED)) { - debug(1, "alsa: can't get device into valid state in delay. State is %d.", state); + debug(1, "alsa: can't get device into valid state in delay. State is %d.", *state); ret = sps_extra_code_output_state_cannot_make_ready; } } From b973619d66e613956947841eb290f302773e7455 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 2 Jan 2019 22:42:48 +0000 Subject: [PATCH 322/569] Using new delay() and play() functions. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 3ef39546..c4b783bb 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.3d42], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d43], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 41c8c8b9a166941d75fa3d88bcf348b68c6698cb Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 3 Jan 2019 10:19:44 +0000 Subject: [PATCH 323/569] Strengthen the ability to recover from an XRUN by putting in a call to snd_pcm_prepare first. --- audio_alsa.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 26aac7b0..43e26749 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1021,8 +1021,10 @@ int delay_prep_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { *state = snd_pcm_status_get_state(alsa_snd_pcm_status); if ((*state == SND_PCM_STATE_SUSPENDED) || (*state == SND_PCM_STATE_XRUN)) { - debug(1, "alsa: recovering from suspended or xrun in delay."); - ret = snd_pcm_recover(alsa_handle, ret, 1); + debug(1, "alsa: recovering from SND_PCM_STATE_* %d in delay_prep_and_status.", *state); + ret = snd_pcm_prepare(alsa_handle); + if (ret == 0) + ret = snd_pcm_recover(alsa_handle, ret, 1); if (ret == 0) { ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); if (ret == 0) From f5707d4f180ae16539f6aff20195db526c0860ce Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 3 Jan 2019 10:48:09 +0000 Subject: [PATCH 324/569] Stop dropping metadata with a zero id -- it's not infallible way of determining if the metadata is invalid. --- metadata_hub.c | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/metadata_hub.c b/metadata_hub.c index 6938de5f..aacfb3a3 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -489,27 +489,15 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin track_metadata = (struct track_metadata_bundle *)malloc(sizeof(struct track_metadata_bundle)); if (track_metadata == NULL) die("Could not allocate memory for track metadata."); - memset(track_metadata, 0, sizeof(struct track_metadata_bundle)); // now we have a valid track - // metadata space, but the - // metadata itself - // might turin out to be invalid. Specifically, YouTube on iOS can generate a sequence of - // track metadata that is invalid. - // it is distinguished by having an item_id of zero. + memset(track_metadata, 0, sizeof(struct track_metadata_bundle)); break; case 'mden': if (track_metadata) { - if ((track_metadata->item_id_received == 0) || (track_metadata->item_id != 0)) { - // i.e. it's only invalid if it has definitely been given an item_id of zero - metadata_hub_modify_prolog(); - metadata_hub_release_track_metadata(metadata_store.track_metadata); - metadata_store.track_metadata = track_metadata; - track_metadata = NULL; - metadata_hub_modify_epilog(1); - } else { - debug(1, "The track information received is invalid -- dropping it"); - metadata_hub_release_track_metadata(track_metadata); - track_metadata = NULL; - } + metadata_hub_modify_prolog(); + metadata_hub_release_track_metadata(metadata_store.track_metadata); + metadata_store.track_metadata = track_metadata; + track_metadata = NULL; + metadata_hub_modify_epilog(1); } debug(2, "MH Metadata stream processing end."); break; From c35fb69e33dff6a964e5851aa4cdf0b70b2e0315 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 3 Jan 2019 10:48:54 +0000 Subject: [PATCH 325/569] Fix parsing error with progress 'prgr' metadata --- rtsp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rtsp.c b/rtsp.c index bf0ad370..74d1c54f 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1034,8 +1034,8 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req, } else #ifdef CONFIG_METADATA if (!strncmp(cp, "progress: ", strlen("progress: "))) { - char *progress = cp + strlen("volume: "); - // debug(2, "progress: \"%s\"\n",progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per + char *progress = cp + strlen("progress: "); + // debug(2, "progress: \"%s\"",progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per // second send_ssnc_metadata('prgr', strdup(progress), strlen(progress), 1); From 8e5733e6ae88c47d7359eca556e1616091f7cd3c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 11:29:18 +0000 Subject: [PATCH 326/569] Check before using snd_pcm_prepare in delay() --- audio_alsa.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 43e26749..0528c270 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1021,14 +1021,23 @@ int delay_prep_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { *state = snd_pcm_status_get_state(alsa_snd_pcm_status); if ((*state == SND_PCM_STATE_SUSPENDED) || (*state == SND_PCM_STATE_XRUN)) { - debug(1, "alsa: recovering from SND_PCM_STATE_* %d in delay_prep_and_status.", *state); - ret = snd_pcm_prepare(alsa_handle); - if (ret == 0) - ret = snd_pcm_recover(alsa_handle, ret, 1); + debug(1, "alsa: recovering from SND_PCM_STATE_* %d in delay_prep_and_status.", *state); + ret = snd_pcm_recover(alsa_handle, ret, 1); if (ret == 0) { ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); if (ret == 0) *state = snd_pcm_status_get_state(alsa_snd_pcm_status); + if (*state != SND_PCM_STATE_PREPARED) { + debug(1, "alsa: snd_pcm_recover failed -- have to use snd_pcm_prepare."); + ret = snd_pcm_prepare(alsa_handle); + if (ret == 0) + ret = snd_pcm_recover(alsa_handle, ret, 1); + if (ret == 0) { + ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); + if (ret == 0) + *state = snd_pcm_status_get_state(alsa_snd_pcm_status); + } + } } } From 4f9730e3b22745f3d4b6a6bdf625d7cbd5be2682 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 13:47:44 +0000 Subject: [PATCH 327/569] Give a nice message if the soxr library isn't installed. --- configure.ac | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/configure.ac b/configure.ac index c4b783bb..f6a1c0f8 100644 --- a/configure.ac +++ b/configure.ac @@ -175,14 +175,15 @@ AC_ARG_WITH(soxr, [ --with-soxr = choose libsoxr for high-quality interpolation AC_DEFINE([CONFIG_SOXR], 1, [Include support for using the SoX Resampler library for interpolation]) PKG_CHECK_MODULES( [SOXR], [soxr], - [LIBS="${SOXR_LIBS} ${LIBS}"]) + [LIBS="${SOXR_LIBS} ${LIBS}"], + [AC_MSG_ERROR(soxr support requires the libsoxr library -- libsoxr-dev suggested!)]) else AC_CHECK_LIB([avutil],[av_get_cpu_flags]) if test "x${ac_cv_lib_avutil_av_get_cpu_flags}" = xyes ; then # soxr may link against libavutil, depending on the architecture, but for the sake of simplicity link with it if it is found - AC_CHECK_LIB([soxr],[soxr_create], , AC_MSG_ERROR(soxr support requested but libsoxr not found!), [-lavutil]) + AC_CHECK_LIB([soxr],[soxr_create], , AC_MSG_ERROR(soxr support requires the libsoxr library -- libsoxr-dev suggested!), [-lavutil]) else - AC_CHECK_LIB([soxr],[soxr_create], , AC_MSG_ERROR(soxr support requested but libsoxr not found!)) + AC_CHECK_LIB([soxr],[soxr_create], , AC_MSG_ERROR(soxr support requires the libsoxr library -- libsoxr-dev suggested!)) fi fi ], ) From 4610d0870d8b5fc58f4acedec6d482916ea1e4be Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 13:48:56 +0000 Subject: [PATCH 328/569] Raise the soxr minimum buffer size to something sensible and multiply it by the output ratio --- player.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/player.c b/player.c index b14b9daf..5885568b 100644 --- a/player.c +++ b/player.c @@ -95,7 +95,7 @@ #define MAX_PACKET 2048 // DAC buffer occupancy stuff -#define DAC_BUFFER_QUEUE_MINIMUM_LENGTH 600 +#define DAC_BUFFER_QUEUE_MINIMUM_LENGTH 2500 // static abuf_t audio_buffer[BUFFER_FRAMES]; #define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES) @@ -2214,7 +2214,7 @@ void *player_thread_func(void *arg) { } } - if ((current_delay < DAC_BUFFER_QUEUE_MINIMUM_LENGTH) || + if ((current_delay < (DAC_BUFFER_QUEUE_MINIMUM_LENGTH * conn->output_sample_ratio)) || (config.packet_stuffing == ST_basic)) { play_samples = stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format, From 459dd51a31fa088774cbbdb3466147d72c37d32e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 13:50:14 +0000 Subject: [PATCH 329/569] Use snd_pcm_recover only _after_ an XRUN or suspend has happened. Don't use it to remove one -- it doesn't work. --- audio_alsa.c | 191 ++++++++++++++++++++++++--------------------------- 1 file changed, 89 insertions(+), 102 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 0528c270..20c30bd1 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1020,82 +1020,51 @@ int delay_prep_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { if (ret == 0) { *state = snd_pcm_status_get_state(alsa_snd_pcm_status); - if ((*state == SND_PCM_STATE_SUSPENDED) || (*state == SND_PCM_STATE_XRUN)) { - debug(1, "alsa: recovering from SND_PCM_STATE_* %d in delay_prep_and_status.", *state); - ret = snd_pcm_recover(alsa_handle, ret, 1); + if ((*state == SND_PCM_STATE_RUNNING) || (*state == SND_PCM_STATE_DRAINING)) { + + snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); + *delay = snd_pcm_status_get_delay(alsa_snd_pcm_status); + + if (*state == SND_PCM_STATE_DRAINING) + debug(1, "alsa: draining with a delay of %d.", delay); + + clock_gettime(CLOCK_MONOTONIC, &tn); + uint64_t time_now_ns = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; + uint64_t update_timestamp_ns = + update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; + + // see if it's stalled + + if ((stall_monitor_start_time != 0) && (stall_monitor_frame_count == *delay)) { + // hasn't outputted anything since the last call to delay() + + if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) || + ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) { + ret = sps_extra_code_output_stalled; + } + } else { + stall_monitor_start_time = update_timestamp_ns; + stall_monitor_frame_count = *delay; + } + if (ret == 0) { - ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); - if (ret == 0) - *state = snd_pcm_status_get_state(alsa_snd_pcm_status); - if (*state != SND_PCM_STATE_PREPARED) { - debug(1, "alsa: snd_pcm_recover failed -- have to use snd_pcm_prepare."); - ret = snd_pcm_prepare(alsa_handle); - if (ret == 0) - ret = snd_pcm_recover(alsa_handle, ret, 1); - if (ret == 0) { - ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); - if (ret == 0) - *state = snd_pcm_status_get_state(alsa_snd_pcm_status); - } - } + uint64_t delta = time_now_ns - update_timestamp_ns; + + uint64_t frames_played_since_last_interrupt = + ((uint64_t)desired_sample_rate * delta) / 1000000000; + snd_pcm_sframes_t frames_played_since_last_interrupt_sized = + frames_played_since_last_interrupt; + + *delay = *delay - frames_played_since_last_interrupt_sized; } - } + } else { // not running, thus no delay information, thus can't check for stall + *delay = 0; + stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush + stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes - // here, the device must be either SND_PCM_STATE_RUNNING - // or SND_PCM_STATE_PREPARED or SND_PCM_STATE_DRAINING - - if (ret == 0) { - - if ((*state == SND_PCM_STATE_RUNNING) || (*state == SND_PCM_STATE_DRAINING)) { - - snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); - *delay = snd_pcm_status_get_delay(alsa_snd_pcm_status); - - if (*state == SND_PCM_STATE_DRAINING) - debug(1, "alsa: draining with a delay of %d.", delay); - - clock_gettime(CLOCK_MONOTONIC, &tn); - uint64_t time_now_ns = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; - uint64_t update_timestamp_ns = - update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; - - // see if it's stalled - - if ((stall_monitor_start_time != 0) && (stall_monitor_frame_count == *delay)) { - // hasn't outputted anything since the last call to delay() - - if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) || - ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) { - ret = sps_extra_code_output_stalled; - } - } else { - stall_monitor_start_time = update_timestamp_ns; - stall_monitor_frame_count = *delay; - } - - if (ret == 0) { - uint64_t delta = time_now_ns - update_timestamp_ns; - - uint64_t frames_played_since_last_interrupt = - ((uint64_t)desired_sample_rate * delta) / 1000000000; - snd_pcm_sframes_t frames_played_since_last_interrupt_sized = - frames_played_since_last_interrupt; - - *delay = *delay - frames_played_since_last_interrupt_sized; - } - } else { // not running, thus no delay information, thus can't check for stall - stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush - stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes - - // not running, thus no delay information, thus can't check for frame rates - frame_index = 0; // we'll be starting over... - measurement_data_is_valid = 0; - - if ((*state != SND_PCM_STATE_PREPARED)) { - debug(1, "alsa: can't get device into valid state in delay. State is %d.", *state); - ret = sps_extra_code_output_state_cannot_make_ready; - } - } + // not running, thus no delay information, thus can't check for frame rates + frame_index = 0; // we'll be starting over... + measurement_data_is_valid = 0; } } return ret; @@ -1180,41 +1149,59 @@ int untimed_play(void *buf, int samples) { ret = delay_prep_and_status(&state, &my_delay); if (ret == 0) { // will be non-zero if an error or a stall - if ((state == SND_PCM_STATE_PREPARED) || (state == SND_PCM_STATE_RUNNING)) { - if ((samples != 0) && (buf != NULL)) { + if ((samples != 0) && (buf != NULL)) { - // debug(3, "write %d frames.", samples); - ret = alsa_pcm_write(alsa_handle, buf, samples); - if (ret == samples) { - stall_monitor_frame_count += samples; + // debug(3, "write %d frames.", samples); + ret = alsa_pcm_write(alsa_handle, buf, samples); + if (ret == samples) { + stall_monitor_frame_count += samples; - if (frame_index == 0) { - frames_sent_for_playing = samples; - } else { - frames_sent_for_playing += samples; + if (frame_index == 0) { + frames_sent_for_playing = samples; + } else { + frames_sent_for_playing += samples; + } + + const uint64_t start_measurement_from_this_frame = + (2 * 44100) / 352; // two seconds of frames… + + frame_index++; + + if ((frame_index == start_measurement_from_this_frame) || + ((frame_index > start_measurement_from_this_frame) && (frame_index % 32 == 0))) { + + measurement_time = get_absolute_time_in_fp(); + frames_played_at_measurement_time = frames_sent_for_playing - my_delay - samples; + + if (frame_index == start_measurement_from_this_frame) { + // debug(1, "Start frame counting"); + frames_played_at_measurement_start_time = frames_played_at_measurement_time; + measurement_start_time = measurement_time; + measurement_data_is_valid = 1; } - - const uint64_t start_measurement_from_this_frame = - (2 * 44100) / 352; // two seconds of frames… - - frame_index++; - - if ((frame_index == start_measurement_from_this_frame) || - ((frame_index > start_measurement_from_this_frame) && (frame_index % 32 == 0))) { - - measurement_time = get_absolute_time_in_fp(); - frames_played_at_measurement_time = frames_sent_for_playing - my_delay - samples; - - if (frame_index == start_measurement_from_this_frame) { - // debug(1, "Start frame counting"); - frames_played_at_measurement_start_time = frames_played_at_measurement_time; - measurement_start_time = measurement_time; - measurement_data_is_valid = 1; + } + } else { + debug(1, "alsa: error %d writing %d samples to alsa device.", ret, samples); + if (ret == -EPIPE) { /* underrun */ + ret = snd_pcm_recover(alsa_handle, ret, debuglev > 0 ? 1 : 0); + if (ret < 0) { + ret = snd_pcm_prepare(alsa_handle); + if (ret < 0) + warn("alsa: can't recover from SND_PCM_STATE_XRUN, snd_pcm_recover() and " + "snd_pcm_prepare() failed: %s.", + snd_strerror(ret)); + } else if (ret == -ESTRPIPE) { /* suspended */ + while ((ret = snd_pcm_resume(alsa_handle)) == -EAGAIN) + sleep(1); /* wait until the suspend flag is released */ + if (ret < 0) { + ret = snd_pcm_prepare(alsa_handle); + if (ret < 0) + warn("alsa: can't recover from SND_PCM_STATE_SUSPENDED state, snd_pcm_prepare() " + "failed: %s.", + snd_strerror(ret)); } } - } else { - debug(1, "alsa: error writing to alsa device."); } } } else { From 5e38fcd50fe09e0f4c74a7b32a570809cbba3da5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 17:06:21 +0000 Subject: [PATCH 330/569] Add default values for the interpolation threshold --- audio_jack.c | 1 + audio_pa.c | 2 ++ audio_sndio.c | 1 + 3 files changed, 4 insertions(+) diff --git a/audio_jack.c b/audio_jack.c index 2f79bee1..9f9d0a0b 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -301,6 +301,7 @@ void *open_client_if_necessary_thread_function(void *arg) { int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) { config.audio_backend_latency_offset = 0; config.audio_backend_buffer_desired_length = 0.500; + config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.25; // below this, soxr interpolation will not occur -- it'll be basic interpolation instead. config.jack_auto_client_open_interval = 1; // check every second // get settings from settings file first, allow them to be overridden by diff --git a/audio_pa.c b/audio_pa.c index ca79b4dc..48e4579d 100644 --- a/audio_pa.c +++ b/audio_pa.c @@ -70,6 +70,8 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char * // set up default values first config.audio_backend_buffer_desired_length = 0.35; + config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.02; // below this, soxr interpolation will not occur -- it'll be basic interpolation instead. + config.audio_backend_latency_offset = 0; // get settings from settings file diff --git a/audio_sndio.c b/audio_sndio.c index d8037edb..511d90b3 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -99,6 +99,7 @@ static int init(int argc, char **argv) { devname = SIO_DEVANY; config.audio_backend_buffer_desired_length = 1.0; + config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.25; // below this, soxr interpolation will not occur -- it'll be basic interpolation instead. config.audio_backend_latency_offset = 0; // get settings from settings file From db0e718c12af37b8ca0018fbeacd6b6d2ef720d2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 17:07:21 +0000 Subject: [PATCH 331/569] Add code for getting and setting the interpolation threshold. --- audio.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/audio.c b/audio.c index a0b5f74d..4c3df7df 100644 --- a/audio.c +++ b/audio.c @@ -152,6 +152,19 @@ void parse_general_audio_options(void) { config.audio_backend_buffer_desired_length = dvalue; } } + + /* Get the minumum buffer size for fancy interpolation setting in seconds. */ + if (config_lookup_float(config.cfg, "general.audio_backend_buffer_interpolation_threshold_in_seconds", + &dvalue)) { + if ((dvalue < 0) || (dvalue > config.audio_backend_buffer_desired_length)) { + die("Invalid audio_backend_buffer_interpolation_threshold_in_seconds value: \"%f\". It " + "should be between 0 and " + "audio_backend_buffer_desired_length_in_seconds of %.3f, default is %.3f seconds", + dvalue, config.audio_backend_buffer_desired_length,config.audio_backend_buffer_interpolation_threshold_in_seconds); + } else { + config.audio_backend_buffer_interpolation_threshold_in_seconds = dvalue; + } + } /* Get the latency offset (deprecated). */ if (config_lookup_int(config.cfg, "general.audio_backend_latency_offset", &value)) { From 01dbf625fe215b426915832e71e5b336da8fd518 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 17:09:37 +0000 Subject: [PATCH 332/569] Add the interpolation threshold setting. --- common.h | 1 + scripts/shairport-sync.conf | 1 + 2 files changed, 2 insertions(+) diff --git a/common.h b/common.h index c42d76a3..4e2fb65a 100644 --- a/common.h +++ b/common.h @@ -168,6 +168,7 @@ typedef struct { int interface_index; // only valid if the interface string is non-NULL double audio_backend_buffer_desired_length; // this will be the length in seconds of the // audio backend buffer -- the DAC buffer for ALSA + double audio_backend_buffer_interpolation_threshold_in_seconds; // below this, soxr interpolation will not occur -- it'll be basic interpolation instead. double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any // fixed latency there might be in the audio path double audio_backend_silent_lead_in_time; // the length of the silence that should precede a play. diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 39db8077..b34c8455 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -41,6 +41,7 @@ general = // audio_backend_latency_offset_in_seconds = 0.0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1. // audio_backend_buffer_desired_length_in_seconds = 0.15; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response time to volume changes becomes annoying. Default is 0.15 seconds in the alsa backend, 0.35 seconds in the pa backend and 1.0 seconds otherwise. +// audio_backend_buffer_interpolation_threshold_in_seconds = 0.075; // Advanced feature. If the buffer size drops below this, stop using time-consuming interpolation like soxr to avoid dropouts due to underrun. // audio_backend_silent_lead_in_time = 2.0; // This optional advanced setting, from 0.0 and 4.0 seconds, sets the length of the period of silence that precedes the start of the audio. The default is the latency, usually 2.0 seconds. Values greater than the latency are ignored. Values that are too low will affect initial synchronisation. // dbus_service_bus = "system"; // The Shairport Sync dbus interface, if selected at compilation, will appear // as "org.gnome.ShairportSync" on the whichever bus you specify here: "system" (default) or "session". From 7b97ea4019da9675047ce37913f7590ff139f91c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 17:10:26 +0000 Subject: [PATCH 333/569] Add code for calculating and recognising the interpolation threshold. --- player.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/player.c b/player.c index 5885568b..f17cf685 100644 --- a/player.c +++ b/player.c @@ -1491,7 +1491,7 @@ void *player_thread_func(void *arg) { conn->first_packet_timestamp = 0; conn->flush_requested = 0; conn->fix_volume = 0x10000; - + if (conn->latency == 0) { debug(3, "No latency has (yet) been specified. Setting 88,200 (2 seconds) frames " "as a default."); @@ -1551,6 +1551,10 @@ void *player_thread_func(void *arg) { debug(3, "Output frame bytes is %d.", conn->output_bytes_per_frame); + conn->dac_buffer_queue_minimum_length = (int64_t)(config.audio_backend_buffer_interpolation_threshold_in_seconds * + config.output_rate); + debug(1,"dac_buffer_queue_minimum_length is %" PRId64 " frames.", conn->dac_buffer_queue_minimum_length); + conn->session_corrections = 0; // conn->play_segment_reference_frame = 0; // zero signals that we are not in a play segment @@ -2214,7 +2218,7 @@ void *player_thread_func(void *arg) { } } - if ((current_delay < (DAC_BUFFER_QUEUE_MINIMUM_LENGTH * conn->output_sample_ratio)) || + if ((current_delay < conn->dac_buffer_queue_minimum_length) || (config.packet_stuffing == ST_basic)) { play_samples = stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format, From 3434761517703e18104f7e333d25910d3abf8da0 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 17:10:57 +0000 Subject: [PATCH 334/569] Add code for calculating and recognising the interpolation threshold. --- player.h | 1 + 1 file changed, 1 insertion(+) diff --git a/player.h b/player.h index 7dd1c186..84521893 100644 --- a/player.h +++ b/player.h @@ -248,6 +248,7 @@ typedef struct { 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 + int64_t dac_buffer_queue_minimum_length; } rtsp_conn_info; uint32_t modulo_32_offset(uint32_t from, uint32_t to); From 7496364aaac9e640daeda566ce061f2709d80b23 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 17:20:15 +0000 Subject: [PATCH 335/569] continue to change delay() and play() to reflect use of snd_pcm_recover _after_ a XRUN has occurred -- fix rate calculations etc. --- audio_alsa.c | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 20c30bd1..f239d63b 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -637,7 +637,7 @@ static int init(int argc, char **argv) { config.audio_backend_latency_offset = 0; config.audio_backend_buffer_desired_length = 0.15; - + config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.050; // below this, basic interpolation will be used to save time. config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem // get settings from settings file first, allow them to be overridden by @@ -1009,7 +1009,7 @@ static void start(int i_sample_rate, int i_sample_format) { stall_monitor_error_threshold = (stall_monitor_error_threshold << 32) / 1000000; // now in fp form } -int delay_prep_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { +int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { snd_pcm_status_t *alsa_snd_pcm_status; snd_pcm_status_alloca(&alsa_snd_pcm_status); @@ -1091,7 +1091,7 @@ int delay(long *the_delay) { pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0); - ret = delay_prep_and_status(&state, &my_delay); + ret = delay_and_status(&state, &my_delay); ret = 0; @@ -1146,11 +1146,17 @@ int untimed_play(void *buf, int samples) { snd_pcm_state_t state; snd_pcm_sframes_t my_delay; - ret = delay_prep_and_status(&state, &my_delay); + ret = delay_and_status(&state, &my_delay); if (ret == 0) { // will be non-zero if an error or a stall if ((samples != 0) && (buf != NULL)) { + + // jut check the state of the DAC + + if ((state != SND_PCM_STATE_PREPARED) && (state != SND_PCM_STATE_RUNNING) && (state != SND_PCM_STATE_XRUN)) { + debug(1,"alsa: DAC in odd SND_PCM_STATE_* %d prior to writing.",state); + } // debug(3, "write %d frames.", samples); ret = alsa_pcm_write(alsa_handle, buf, samples); @@ -1164,7 +1170,7 @@ int untimed_play(void *buf, int samples) { } const uint64_t start_measurement_from_this_frame = - (2 * 44100) / 352; // two seconds of frames… + (2 * config.output_rate) / 352; // two seconds of frames… frame_index++; @@ -1183,16 +1189,20 @@ int untimed_play(void *buf, int samples) { } } else { debug(1, "alsa: error %d writing %d samples to alsa device.", ret, samples); + frame_index = 0; + measurement_data_is_valid = 0; if (ret == -EPIPE) { /* underrun */ ret = snd_pcm_recover(alsa_handle, ret, debuglev > 0 ? 1 : 0); if (ret < 0) { + debug(1, "alsa: failed to recover from SND_PCM_STATE_XRUN with snd_pcm_recover(); trying snd_pcm_prepare()."); ret = snd_pcm_prepare(alsa_handle); if (ret < 0) warn("alsa: can't recover from SND_PCM_STATE_XRUN, snd_pcm_recover() and " "snd_pcm_prepare() failed: %s.", snd_strerror(ret)); - } else if (ret == -ESTRPIPE) { /* suspended */ - while ((ret = snd_pcm_resume(alsa_handle)) == -EAGAIN) + } + } else if (ret == -ESTRPIPE) { /* suspended */ + while ((ret = snd_pcm_resume(alsa_handle)) == -EAGAIN) { sleep(1); /* wait until the suspend flag is released */ if (ret < 0) { ret = snd_pcm_prepare(alsa_handle); @@ -1204,12 +1214,13 @@ int untimed_play(void *buf, int samples) { } } } - } else { - debug(1, "alsa: device in incorrect state (%d) for play.", state); - frame_index = 0; - measurement_data_is_valid = 0; } + } else { + debug(1, "alsa: device status %d faulty for play.", state); + frame_index = 0; + measurement_data_is_valid = 0; } + debug_mutex_unlock(&alsa_mutex, 0); pthread_cleanup_pop(0); // release the mutex } From 9552b278b73c07d9bf2121881a8aac971e661fa4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 17:26:00 +0000 Subject: [PATCH 336/569] remove some debugging code from delay(). --- audio_alsa.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index f239d63b..0372dc53 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1093,12 +1093,6 @@ int delay(long *the_delay) { ret = delay_and_status(&state, &my_delay); - ret = 0; - - if ((ret == 0) && (state != SND_PCM_STATE_RUNNING) && (state != SND_PCM_STATE_DRAINING) && - (state != SND_PCM_STATE_PREPARED)) - ret = sps_extra_code_output_state_cannot_make_ready; - debug_mutex_unlock(&alsa_mutex, 0); pthread_cleanup_pop(0); pthread_setcancelstate(oldState, NULL); From 80f15e1f574f060d9f644eef176ecb37d775ddfd Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 17:26:55 +0000 Subject: [PATCH 337/569] clang format --- audio.c | 8 +++++--- audio_alsa.c | 21 ++++++++++++--------- audio_jack.c | 4 +++- audio_pa.c | 4 +++- audio_sndio.c | 4 +++- common.h | 4 +++- player.c | 10 +++++----- 7 files changed, 34 insertions(+), 21 deletions(-) diff --git a/audio.c b/audio.c index 4c3df7df..7d0f489f 100644 --- a/audio.c +++ b/audio.c @@ -152,15 +152,17 @@ void parse_general_audio_options(void) { config.audio_backend_buffer_desired_length = dvalue; } } - + /* Get the minumum buffer size for fancy interpolation setting in seconds. */ - if (config_lookup_float(config.cfg, "general.audio_backend_buffer_interpolation_threshold_in_seconds", + if (config_lookup_float(config.cfg, + "general.audio_backend_buffer_interpolation_threshold_in_seconds", &dvalue)) { if ((dvalue < 0) || (dvalue > config.audio_backend_buffer_desired_length)) { die("Invalid audio_backend_buffer_interpolation_threshold_in_seconds value: \"%f\". It " "should be between 0 and " "audio_backend_buffer_desired_length_in_seconds of %.3f, default is %.3f seconds", - dvalue, config.audio_backend_buffer_desired_length,config.audio_backend_buffer_interpolation_threshold_in_seconds); + dvalue, config.audio_backend_buffer_desired_length, + config.audio_backend_buffer_interpolation_threshold_in_seconds); } else { config.audio_backend_buffer_interpolation_threshold_in_seconds = dvalue; } diff --git a/audio_alsa.c b/audio_alsa.c index 0372dc53..2a453b1e 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -637,7 +637,8 @@ static int init(int argc, char **argv) { config.audio_backend_latency_offset = 0; config.audio_backend_buffer_desired_length = 0.15; - config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.050; // below this, basic interpolation will be used to save time. + config.audio_backend_buffer_interpolation_threshold_in_seconds = + 0.050; // below this, basic interpolation will be used to save time. config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem // get settings from settings file first, allow them to be overridden by @@ -1145,11 +1146,12 @@ int untimed_play(void *buf, int samples) { if (ret == 0) { // will be non-zero if an error or a stall if ((samples != 0) && (buf != NULL)) { - + // jut check the state of the DAC - - if ((state != SND_PCM_STATE_PREPARED) && (state != SND_PCM_STATE_RUNNING) && (state != SND_PCM_STATE_XRUN)) { - debug(1,"alsa: DAC in odd SND_PCM_STATE_* %d prior to writing.",state); + + if ((state != SND_PCM_STATE_PREPARED) && (state != SND_PCM_STATE_RUNNING) && + (state != SND_PCM_STATE_XRUN)) { + debug(1, "alsa: DAC in odd SND_PCM_STATE_* %d prior to writing.", state); } // debug(3, "write %d frames.", samples); @@ -1188,7 +1190,8 @@ int untimed_play(void *buf, int samples) { if (ret == -EPIPE) { /* underrun */ ret = snd_pcm_recover(alsa_handle, ret, debuglev > 0 ? 1 : 0); if (ret < 0) { - debug(1, "alsa: failed to recover from SND_PCM_STATE_XRUN with snd_pcm_recover(); trying snd_pcm_prepare()."); + debug(1, "alsa: failed to recover from SND_PCM_STATE_XRUN with snd_pcm_recover(); " + "trying snd_pcm_prepare()."); ret = snd_pcm_prepare(alsa_handle); if (ret < 0) warn("alsa: can't recover from SND_PCM_STATE_XRUN, snd_pcm_recover() and " @@ -1196,8 +1199,8 @@ int untimed_play(void *buf, int samples) { snd_strerror(ret)); } } else if (ret == -ESTRPIPE) { /* suspended */ - while ((ret = snd_pcm_resume(alsa_handle)) == -EAGAIN) { - sleep(1); /* wait until the suspend flag is released */ + while ((ret = snd_pcm_resume(alsa_handle)) == -EAGAIN) { + sleep(1); /* wait until the suspend flag is released */ if (ret < 0) { ret = snd_pcm_prepare(alsa_handle); if (ret < 0) @@ -1209,7 +1212,7 @@ int untimed_play(void *buf, int samples) { } } } - } else { + } else { debug(1, "alsa: device status %d faulty for play.", state); frame_index = 0; measurement_data_is_valid = 0; diff --git a/audio_jack.c b/audio_jack.c index 9f9d0a0b..11ae6072 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -301,7 +301,9 @@ void *open_client_if_necessary_thread_function(void *arg) { int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) { config.audio_backend_latency_offset = 0; config.audio_backend_buffer_desired_length = 0.500; - config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.25; // below this, soxr interpolation will not occur -- it'll be basic interpolation instead. + config.audio_backend_buffer_interpolation_threshold_in_seconds = + 0.25; // below this, soxr interpolation will not occur -- it'll be basic interpolation + // instead. config.jack_auto_client_open_interval = 1; // check every second // get settings from settings file first, allow them to be overridden by diff --git a/audio_pa.c b/audio_pa.c index 48e4579d..cd321bd9 100644 --- a/audio_pa.c +++ b/audio_pa.c @@ -70,7 +70,9 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char * // set up default values first config.audio_backend_buffer_desired_length = 0.35; - config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.02; // below this, soxr interpolation will not occur -- it'll be basic interpolation instead. + config.audio_backend_buffer_interpolation_threshold_in_seconds = + 0.02; // below this, soxr interpolation will not occur -- it'll be basic interpolation + // instead. config.audio_backend_latency_offset = 0; diff --git a/audio_sndio.c b/audio_sndio.c index 511d90b3..f22ed5af 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -99,7 +99,9 @@ static int init(int argc, char **argv) { devname = SIO_DEVANY; config.audio_backend_buffer_desired_length = 1.0; - config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.25; // below this, soxr interpolation will not occur -- it'll be basic interpolation instead. + config.audio_backend_buffer_interpolation_threshold_in_seconds = + 0.25; // below this, soxr interpolation will not occur -- it'll be basic interpolation + // instead. config.audio_backend_latency_offset = 0; // get settings from settings file diff --git a/common.h b/common.h index 4e2fb65a..35996fef 100644 --- a/common.h +++ b/common.h @@ -168,7 +168,9 @@ typedef struct { int interface_index; // only valid if the interface string is non-NULL double audio_backend_buffer_desired_length; // this will be the length in seconds of the // audio backend buffer -- the DAC buffer for ALSA - double audio_backend_buffer_interpolation_threshold_in_seconds; // below this, soxr interpolation will not occur -- it'll be basic interpolation instead. + double audio_backend_buffer_interpolation_threshold_in_seconds; // below this, soxr interpolation + // will not occur -- it'll be + // basic interpolation instead. double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any // fixed latency there might be in the audio path double audio_backend_silent_lead_in_time; // the length of the silence that should precede a play. diff --git a/player.c b/player.c index f17cf685..a085a636 100644 --- a/player.c +++ b/player.c @@ -1491,7 +1491,7 @@ void *player_thread_func(void *arg) { conn->first_packet_timestamp = 0; conn->flush_requested = 0; conn->fix_volume = 0x10000; - + if (conn->latency == 0) { debug(3, "No latency has (yet) been specified. Setting 88,200 (2 seconds) frames " "as a default."); @@ -1551,9 +1551,10 @@ void *player_thread_func(void *arg) { debug(3, "Output frame bytes is %d.", conn->output_bytes_per_frame); - conn->dac_buffer_queue_minimum_length = (int64_t)(config.audio_backend_buffer_interpolation_threshold_in_seconds * - config.output_rate); - debug(1,"dac_buffer_queue_minimum_length is %" PRId64 " frames.", conn->dac_buffer_queue_minimum_length); + conn->dac_buffer_queue_minimum_length = (int64_t)( + config.audio_backend_buffer_interpolation_threshold_in_seconds * config.output_rate); + debug(1, "dac_buffer_queue_minimum_length is %" PRId64 " frames.", + conn->dac_buffer_queue_minimum_length); conn->session_corrections = 0; // conn->play_segment_reference_frame = 0; // zero signals that we are not in a play segment @@ -2835,4 +2836,3 @@ int player_stop(rtsp_conn_info *conn) { return -1; } } - From 43a2813b8c12e96dc0e8d796bf51d857271b652a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 17:30:56 +0000 Subject: [PATCH 338/569] Extensive changes to alsa delay() and play() -- recover from XRUN _after_ sending frames; doesn't work otherwise. Update soxr buffer threshold code. Improve missing libsoxr message. Fix syntax of progress metadata. Stop dropping YouTube metadata -- the indication it was invalid is unsafe. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index f6a1c0f8..6f3d150e 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.3d43], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d44], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 48043a81ccdc5ce056e3af9a90ef8a261d18a7a4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 18:17:30 +0000 Subject: [PATCH 339/569] Move stall threshold calculation to init from start, duh. --- audio_alsa.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 2a453b1e..02e747c6 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -640,6 +640,15 @@ static int init(int argc, char **argv) { config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.050; // below this, basic interpolation will be used to save time. config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem + + + stall_monitor_error_threshold = + (uint64_t)1000000 * config.alsa_maximum_stall_time; // stall time max to microseconds; + stall_monitor_error_threshold = (stall_monitor_error_threshold << 32) / 1000000; // now in fp form + debug(1,"stall_monitor_error_threshold is %" PRIX64 ", with alsa_maximum_stall_time of %f sec.", stall_monitor_error_threshold, config.alsa_maximum_stall_time); + + stall_monitor_start_time = 0; + stall_monitor_frame_count = 0; // get settings from settings file first, allow them to be overridden by // command line options @@ -1004,10 +1013,6 @@ static void start(int i_sample_rate, int i_sample_format) { stall_monitor_start_time = 0; stall_monitor_frame_count = 0; - - stall_monitor_error_threshold = - (uint64_t)1000000 * config.alsa_maximum_stall_time; // stall time max to microseconds; - stall_monitor_error_threshold = (stall_monitor_error_threshold << 32) / 1000000; // now in fp form } int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { @@ -1041,6 +1046,7 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) || ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) { + debug(2, "DAC seems to have stalled with time_now_ns: %" PRIX64 ", update_timestamp_ns: %" PRIX64 ", stall_monitor_start_time %" PRIX64 ", stall_monitor_error_threshold %" PRIX64 ".", time_now_ns, update_timestamp_ns, stall_monitor_start_time, stall_monitor_error_threshold); ret = sps_extra_code_output_stalled; } } else { @@ -1067,6 +1073,8 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { frame_index = 0; // we'll be starting over... measurement_data_is_valid = 0; } + } else { + debug(1,"alsa: can't get device's status."); } return ret; } @@ -1213,7 +1221,7 @@ int untimed_play(void *buf, int samples) { } } } else { - debug(1, "alsa: device status %d faulty for play.", state); + debug(1, "alsa: device status returns fault status %d and SND_PCM_STATE_* %d for play.", ret, state); frame_index = 0; measurement_data_is_valid = 0; } From 6b42abab72ea38d619c746a5d24127b1c940971f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 20:57:18 +0000 Subject: [PATCH 340/569] Use driver htstamp rather than regular one. Use untimed_delay so that a regualr delay sets the standoff timer for silence. --- audio_alsa.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 02e747c6..2d5bd8c1 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1028,7 +1028,7 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { if ((*state == SND_PCM_STATE_RUNNING) || (*state == SND_PCM_STATE_DRAINING)) { - snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); + snd_pcm_status_get_driver_htstamp(alsa_snd_pcm_status, &update_timestamp); *delay = snd_pcm_status_get_delay(alsa_snd_pcm_status); if (*state == SND_PCM_STATE_DRAINING) @@ -1061,7 +1061,7 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { ((uint64_t)desired_sample_rate * delta) / 1000000000; snd_pcm_sframes_t frames_played_since_last_interrupt_sized = frames_played_since_last_interrupt; - + *delay = *delay - frames_played_since_last_interrupt_sized; } } else { // not running, thus no delay information, thus can't check for stall @@ -1079,7 +1079,7 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { return ret; } -int delay(long *the_delay) { +int untimed_delay(long *the_delay) { // returns 0 if the device is in a valid state -- SND_PCM_STATE_RUNNING or SND_PCM_STATE_PREPARED // or SND_PCM_STATE_DRAINING // and returns the actual delay if running or 0 if prepared in *the_delay @@ -1271,6 +1271,14 @@ static int play(void *buf, int samples) { return untimed_play(buf, samples); } +int delay(long *the_delay) { + uint64_t time_now = + get_absolute_time_in_fp(); // this is to regulate access by the silence filler thread + most_recent_write_time = time_now; + return untimed_delay(the_delay); +} + + static void stop(void) { // debug(2,"audio_alsa stop called."); // when we want to stop, we want the alsa device @@ -1470,7 +1478,7 @@ void *alsa_buffer_monitor_thread_code(void *arg) { if ((most_recent_write_time == 0) || ((present_time > most_recent_write_time) && ((present_time - most_recent_write_time) > (sleep_time_in_fp)))) { - reply = delay(&buffer_size); + reply = untimed_delay(&buffer_size); if (reply != 0) { buffer_size = 0; char errorstring[1024]; @@ -1491,7 +1499,9 @@ void *alsa_buffer_monitor_thread_code(void *arg) { // frames_of_silence,most_recent_write_time); untimed_play(silence, frames_of_silence); } - } + } else { + // debug(2,"Skipping sending silence"); + } } usleep(sleep_time_ms * 1000); // has a cancellation point in it // pthread_testcancel(); From 0e37f7e78d40bda6369a6bda3c6b0a9f010ab3b0 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 21:52:36 +0000 Subject: [PATCH 341/569] Only look for snd_pcm_status_get_driver_htstamp is alsa version 1.1 or later... --- audio_alsa.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/audio_alsa.c b/audio_alsa.c index 2d5bd8c1..ea934719 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1028,7 +1028,13 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { if ((*state == SND_PCM_STATE_RUNNING) || (*state == SND_PCM_STATE_DRAINING)) { +// must be 1.1 or later to use snd_pcm_status_get_driver_htstamp +#if SND_LIB_MINOR==0 + snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); +#else snd_pcm_status_get_driver_htstamp(alsa_snd_pcm_status, &update_timestamp); +#endif + *delay = snd_pcm_status_get_delay(alsa_snd_pcm_status); if (*state == SND_PCM_STATE_DRAINING) From 67a19bf3c9e0a54515138f1bfeb7205bcf5378d5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 7 Jan 2019 15:59:58 +0000 Subject: [PATCH 342/569] Make the wait time less than the packet interval time to enable SPS to catch up if it gets behind. --- player.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/player.c b/player.c index a085a636..3bab2428 100644 --- a/player.c +++ b/player.c @@ -1198,8 +1198,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (wait) { uint64_t time_to_wait_for_wakeup_fp = ((uint64_t)1 << 32) / conn->input_rate; // this is time period of one frame - time_to_wait_for_wakeup_fp *= 4 * 352; // four full 352-frame packets - time_to_wait_for_wakeup_fp /= 3; // four thirds of a packet time + time_to_wait_for_wakeup_fp *= 2 * 352; // two full 352-frame packets + time_to_wait_for_wakeup_fp /= 3; // two thirds of a packet time #ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD uint64_t time_of_wakeup_fp = local_time_now + time_to_wait_for_wakeup_fp; From 4613ee6bcbc094e7f3e38f7a70684a017e3fe528 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 7 Jan 2019 16:00:58 +0000 Subject: [PATCH 343/569] Make buffer size threashold 100 ms instead of 50 ms. Remove some surplus snd_pcm_prepare calls. --- audio_alsa.c | 51 ++++++++++++++++++++------------------------------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index ea934719..b1c7ae7c 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -600,14 +600,6 @@ int actual_open_alsa_device(void) { break; } } - - // now open the device - ret = snd_pcm_prepare(alsa_handle); - if (ret) { - warn("alsa: can't prepare device."); - return ret; - } - return 0; } @@ -638,14 +630,14 @@ static int init(int argc, char **argv) { config.audio_backend_latency_offset = 0; config.audio_backend_buffer_desired_length = 0.15; config.audio_backend_buffer_interpolation_threshold_in_seconds = - 0.050; // below this, basic interpolation will be used to save time. + 0.100; // below this, basic interpolation will be used to save time. config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem - - + stall_monitor_error_threshold = (uint64_t)1000000 * config.alsa_maximum_stall_time; // stall time max to microseconds; stall_monitor_error_threshold = (stall_monitor_error_threshold << 32) / 1000000; // now in fp form - debug(1,"stall_monitor_error_threshold is %" PRIX64 ", with alsa_maximum_stall_time of %f sec.", stall_monitor_error_threshold, config.alsa_maximum_stall_time); + debug(1, "stall_monitor_error_threshold is %" PRIX64 ", with alsa_maximum_stall_time of %f sec.", + stall_monitor_error_threshold, config.alsa_maximum_stall_time); stall_monitor_start_time = 0; stall_monitor_frame_count = 0; @@ -1029,7 +1021,7 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { if ((*state == SND_PCM_STATE_RUNNING) || (*state == SND_PCM_STATE_DRAINING)) { // must be 1.1 or later to use snd_pcm_status_get_driver_htstamp -#if SND_LIB_MINOR==0 +#if SND_LIB_MINOR == 0 snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); #else snd_pcm_status_get_driver_htstamp(alsa_snd_pcm_status, &update_timestamp); @@ -1052,7 +1044,11 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) || ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) { - debug(2, "DAC seems to have stalled with time_now_ns: %" PRIX64 ", update_timestamp_ns: %" PRIX64 ", stall_monitor_start_time %" PRIX64 ", stall_monitor_error_threshold %" PRIX64 ".", time_now_ns, update_timestamp_ns, stall_monitor_start_time, stall_monitor_error_threshold); + debug(2, "DAC seems to have stalled with time_now_ns: %" PRIX64 + ", update_timestamp_ns: %" PRIX64 ", stall_monitor_start_time %" PRIX64 + ", stall_monitor_error_threshold %" PRIX64 ".", + time_now_ns, update_timestamp_ns, stall_monitor_start_time, + stall_monitor_error_threshold); ret = sps_extra_code_output_stalled; } } else { @@ -1067,7 +1063,7 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { ((uint64_t)desired_sample_rate * delta) / 1000000000; snd_pcm_sframes_t frames_played_since_last_interrupt_sized = frames_played_since_last_interrupt; - + *delay = *delay - frames_played_since_last_interrupt_sized; } } else { // not running, thus no delay information, thus can't check for stall @@ -1080,7 +1076,7 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { measurement_data_is_valid = 0; } } else { - debug(1,"alsa: can't get device's status."); + debug(1, "alsa: can't get device's status."); } return ret; } @@ -1198,36 +1194,30 @@ int untimed_play(void *buf, int samples) { } } } else { + debug(1, "alsa: error %d writing %d samples to alsa device.", ret, samples); frame_index = 0; measurement_data_is_valid = 0; if (ret == -EPIPE) { /* underrun */ ret = snd_pcm_recover(alsa_handle, ret, debuglev > 0 ? 1 : 0); if (ret < 0) { - debug(1, "alsa: failed to recover from SND_PCM_STATE_XRUN with snd_pcm_recover(); " - "trying snd_pcm_prepare()."); - ret = snd_pcm_prepare(alsa_handle); - if (ret < 0) - warn("alsa: can't recover from SND_PCM_STATE_XRUN, snd_pcm_recover() and " - "snd_pcm_prepare() failed: %s.", - snd_strerror(ret)); + warn("alsa: can't recover from SND_PCM_STATE_XRUN: %s.", snd_strerror(ret)); } } else if (ret == -ESTRPIPE) { /* suspended */ while ((ret = snd_pcm_resume(alsa_handle)) == -EAGAIN) { sleep(1); /* wait until the suspend flag is released */ if (ret < 0) { - ret = snd_pcm_prepare(alsa_handle); - if (ret < 0) - warn("alsa: can't recover from SND_PCM_STATE_SUSPENDED state, snd_pcm_prepare() " - "failed: %s.", - snd_strerror(ret)); + warn("alsa: can't recover from SND_PCM_STATE_SUSPENDED state, snd_pcm_prepare() " + "failed: %s.", + snd_strerror(ret)); } } } } } } else { - debug(1, "alsa: device status returns fault status %d and SND_PCM_STATE_* %d for play.", ret, state); + debug(1, "alsa: device status returns fault status %d and SND_PCM_STATE_* %d for play.", ret, + state); frame_index = 0; measurement_data_is_valid = 0; } @@ -1284,7 +1274,6 @@ int delay(long *the_delay) { return untimed_delay(the_delay); } - static void stop(void) { // debug(2,"audio_alsa stop called."); // when we want to stop, we want the alsa device @@ -1507,7 +1496,7 @@ void *alsa_buffer_monitor_thread_code(void *arg) { } } else { // debug(2,"Skipping sending silence"); - } + } } usleep(sleep_time_ms * 1000); // has a cancellation point in it // pthread_testcancel(); From caf06c5aba0925860791e36855545f9f4a4e00a8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 7 Jan 2019 17:34:50 +0000 Subject: [PATCH 344/569] Add the idea of a preflight message to tell the backend that frames will be coming... --- audio.h | 1 + audio_alsa.c | 37 +++++++++++++++++++++++++++---------- audio_ao.c | 1 + audio_dummy.c | 1 + audio_jack.c | 1 + audio_pa.c | 1 + audio_pipe.c | 1 + audio_sndio.c | 1 + audio_soundio.c | 1 + audio_stdout.c | 1 + common.h | 3 +++ 11 files changed, 39 insertions(+), 10 deletions(-) diff --git a/audio.h b/audio.h index a4f0f178..b8ecf958 100644 --- a/audio.h +++ b/audio.h @@ -22,6 +22,7 @@ typedef struct { void (*start)(int sample_rate, int sample_format); // block of samples + int (*preflight)(void *buf, int samples); // say you are about to send these samples (before interpolation) int (*play)(void *buf, int samples); void (*stop)(void); diff --git a/audio_alsa.c b/audio_alsa.c index b1c7ae7c..b7a5de8d 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -42,6 +42,7 @@ static int init(int argc, char **argv); static void deinit(void); static void start(int i_sample_rate, int i_sample_format); static int play(void *buf, int samples); +int preflight(void *buf, int samples); static void stop(void); static void flush(void); int delay(long *the_delay); @@ -632,6 +633,8 @@ static int init(int argc, char **argv) { config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.100; // below this, basic interpolation will be used to save time. config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem + config.audio_backend_silence_threshold = 0.050; //start sending silent frames if the delay goes below this time + config.audio_backend_silence_scan_interval = 0.005; //check silence threshold this often stall_monitor_error_threshold = (uint64_t)1000000 * config.alsa_maximum_stall_time; // stall time max to microseconds; @@ -1081,7 +1084,7 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { return ret; } -int untimed_delay(long *the_delay) { +int delay(long *the_delay) { // returns 0 if the device is in a valid state -- SND_PCM_STATE_RUNNING or SND_PCM_STATE_PREPARED // or SND_PCM_STATE_DRAINING // and returns the actual delay if running or 0 if prepared in *the_delay @@ -1126,7 +1129,7 @@ int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) { return response; } -int untimed_play(void *buf, int samples) { +int play(void *buf, int samples) { // debug(3,"audio_alsa play called."); int oldState; @@ -1258,6 +1261,16 @@ static void flush(void) { pthread_setcancelstate(oldState, NULL); } +int preflight(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) { + uint64_t time_now = + get_absolute_time_in_fp(); // this is to regulate access by the silence filler thread + uint64_t standoff_time = 100; // milliseconds + standoff_time = (standoff_time << 32)/1000; + most_recent_write_time = time_now + standoff_time; + return 0; +} + +/* static int play(void *buf, int samples) { uint64_t time_now = get_absolute_time_in_fp(); // this is to regulate access by the silence filler thread @@ -1273,6 +1286,7 @@ int delay(long *the_delay) { most_recent_write_time = time_now; return untimed_delay(the_delay); } +*/ static void stop(void) { // debug(2,"audio_alsa stop called."); @@ -1451,12 +1465,14 @@ void *alsa_buffer_monitor_thread_code(void *arg) { debug(1, "alsa: dither will %sbe added to inter-session silence.", use_dither ? "" : "not "); - int sleep_time_ms = 30; + int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000); + long buffer_size_threshold = (long)(config.audio_backend_silence_threshold * desired_sample_rate); + uint64_t sleep_time_in_fp = sleep_time_ms; sleep_time_in_fp = sleep_time_in_fp << 32; sleep_time_in_fp = sleep_time_in_fp / 1000; // debug(1,"alsa: sleep_time: %d ms or 0x%" PRIx64 " in fp form.",sleep_time_ms,sleep_time_in_fp); - int frames_of_silence = (desired_sample_rate * sleep_time_ms * 2) / 1000; + int frames_of_silence = (desired_sample_rate * sleep_time_ms * 4) / 1000; size_t size_of_silence_buffer = frames_of_silence * frame_size; // debug(1,"Silence buffer length: %u bytes.",size_of_silence_buffer); void *silence = malloc(size_of_silence_buffer); @@ -1470,10 +1486,11 @@ void *alsa_buffer_monitor_thread_code(void *arg) { if (config.keep_dac_busy != 0) { uint64_t present_time = get_absolute_time_in_fp(); - if ((most_recent_write_time == 0) || - ((present_time > most_recent_write_time) && - ((present_time - most_recent_write_time) > (sleep_time_in_fp)))) { - reply = untimed_delay(&buffer_size); + if ((most_recent_write_time == 0) || (present_time > most_recent_write_time)) { + +// ((present_time > most_recent_write_time) && +// ((present_time - most_recent_write_time) > (sleep_time_in_fp)))) { + reply = delay(&buffer_size); if (reply != 0) { buffer_size = 0; char errorstring[1024]; @@ -1481,7 +1498,7 @@ void *alsa_buffer_monitor_thread_code(void *arg) { debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply, (char *)errorstring); } - if (buffer_size < frames_of_silence) { + if (buffer_size < buffer_size_threshold) { if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && (config.airplay_volume != 0.0)) use_dither = 1; @@ -1492,7 +1509,7 @@ void *alsa_buffer_monitor_thread_code(void *arg) { dither_random_number_store); // debug(1,"Play %d frames of silence with most_recent_write_time of %" PRIx64 ".", // frames_of_silence,most_recent_write_time); - untimed_play(silence, frames_of_silence); + play(silence, frames_of_silence); } } else { // debug(2,"Skipping sending silence"); diff --git a/audio_ao.c b/audio_ao.c index 42147bab..30f80d8d 100644 --- a/audio_ao.c +++ b/audio_ao.c @@ -134,6 +134,7 @@ audio_output audio_ao = {.name = "ao", .is_running = NULL, .flush = NULL, .delay = NULL, + .preflight = NULL, .play = &play, .volume = NULL, .parameters = NULL, diff --git a/audio_dummy.c b/audio_dummy.c index d8dea87f..69518cd0 100644 --- a/audio_dummy.c +++ b/audio_dummy.c @@ -65,6 +65,7 @@ audio_output audio_dummy = {.name = "dummy", .is_running = NULL, .flush = NULL, .delay = NULL, + .preflight = NULL, .play = &play, .volume = NULL, .parameters = NULL, diff --git a/audio_jack.c b/audio_jack.c index 11ae6072..90f08ac4 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -67,6 +67,7 @@ audio_output audio_jack = {.name = "jack", .is_running = &jack_is_running, .flush = &jack_flush, .delay = &jack_delay, + .preflight = NULL, .play = &play, .volume = NULL, .parameters = NULL, diff --git a/audio_pa.c b/audio_pa.c index cd321bd9..4ee20c75 100644 --- a/audio_pa.c +++ b/audio_pa.c @@ -312,6 +312,7 @@ audio_output audio_pa = {.name = "pa", .flush = &flush, .delay = &pa_delay, .play = &play, + .preflight = NULL, .volume = NULL, .parameters = NULL, .mute = NULL}; diff --git a/audio_pipe.c b/audio_pipe.c index 89a09a0c..0d4a42fd 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -139,6 +139,7 @@ audio_output audio_pipe = {.name = "pipe", .flush = NULL, .delay = NULL, .play = &play, + .preflight = NULL, .volume = NULL, .parameters = NULL, .mute = NULL}; diff --git a/audio_sndio.c b/audio_sndio.c index f22ed5af..e106b15d 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -49,6 +49,7 @@ audio_output audio_sndio = {.name = "sndio", .flush = &flush, .delay = &delay, .play = &play, + .preflight = NULL, .volume = NULL, .parameters = NULL, .mute = NULL}; diff --git a/audio_soundio.c b/audio_soundio.c index 7d3b48ad..0449895b 100644 --- a/audio_soundio.c +++ b/audio_soundio.c @@ -221,6 +221,7 @@ audio_output audio_soundio = {.name = "soundio", .flush = &flush, .delay = NULL, .play = &play, + .preflight = NULL, .volume = NULL, .parameters = ¶meters, .mute = NULL}; diff --git a/audio_stdout.c b/audio_stdout.c index 0438b1af..37d6f781 100644 --- a/audio_stdout.c +++ b/audio_stdout.c @@ -86,6 +86,7 @@ audio_output audio_stdout = {.name = "stdout", .flush = NULL, .delay = NULL, .play = &play, + .preflight = NULL, .volume = NULL, .parameters = NULL, .mute = NULL}; diff --git a/common.h b/common.h index 35996fef..6f07f6d3 100644 --- a/common.h +++ b/common.h @@ -171,6 +171,9 @@ typedef struct { double audio_backend_buffer_interpolation_threshold_in_seconds; // below this, soxr interpolation // will not occur -- it'll be // basic interpolation instead. + double audio_backend_silence_threshold; // below this, silence will be added to the output buffer + double audio_backend_silence_scan_interval; // check the threshold this often + double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any // fixed latency there might be in the audio path double audio_backend_silent_lead_in_time; // the length of the silence that should precede a play. From 1b3d64ce85845c21ef40335d8803dbf9885369c1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 8 Jan 2019 12:59:04 +0000 Subject: [PATCH 345/569] Quieten a debug message. --- rtp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtp.c b/rtp.c index 07f19169..157bc77a 100644 --- a/rtp.c +++ b/rtp.c @@ -1098,7 +1098,7 @@ int sanitised_source_rate_information(uint32_t *frames, uint64_t *time, rtsp_con double calculated_frame_rate = ((1.0 * local_frames) / local_time) * one_fp; if (((calculated_frame_rate / conn->input_rate) > 1.002) || ((calculated_frame_rate / conn->input_rate) < 0.998)) { - debug(2, "input frame rate out of bounds at %.2f fps.", calculated_frame_rate); + debug(3, "input frame rate out of bounds at %.2f fps.", calculated_frame_rate); result = 1; } else { *frames = local_frames; From c3668b3fc023b5d8f8026dbe5f067a784e9b366c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 8 Jan 2019 13:00:00 +0000 Subject: [PATCH 346/569] INcrease the delay between reading chunks of the RTSP message, may it less likely to overwhelm the CPU. --- rtsp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtsp.c b/rtsp.c index 74d1c54f..a90c5aa0 100644 --- a/rtsp.c +++ b/rtsp.c @@ -674,7 +674,7 @@ do { size_t read_chunk = msg_size - inbuf; if (read_chunk > max_read_chunk) read_chunk = max_read_chunk; - usleep(40000); // wait about 40 milliseconds between reads of up to about 64 kB + usleep(80000); // wait about 80 milliseconds between reads of up to about 64 kB nread = read(conn->fd, buf + inbuf, read_chunk); if (!nread) { reply = rtsp_read_request_response_error; From 2e3be271fdc26a0138d78848b36b9b2156e22a73 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 8 Jan 2019 13:01:22 +0000 Subject: [PATCH 347/569] Preflight all audio material playing (not lead-in silences though...) --- player.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/player.c b/player.c index 3bab2428..724b5cab 100644 --- a/player.c +++ b/player.c @@ -1418,6 +1418,9 @@ void player_thread_cleanup_handler(void *arg) { debug(3, "Connection %d: player thread main loop exit via player_thread_cleanup_handler.", conn->connection_number); + if (config.output->stop) + config.output->stop(); + if (config.statistics_requested) { int rawSeconds = (int)difftime(time(NULL), conn->playstart); int elapsedHours = rawSeconds / 3600; @@ -1470,8 +1473,6 @@ void player_thread_cleanup_handler(void *arg) { } free_audio_buffers(conn); terminate_decoders(conn); - if (config.output->stop) - config.output->stop(); clear_reference_timestamp(conn); conn->rtp_running = 0; @@ -2219,7 +2220,10 @@ void *player_thread_func(void *arg) { } } - if ((current_delay < conn->dac_buffer_queue_minimum_length) || + if (config.output->preflight) + config.output->preflight(conn->outbuf,play_samples); + + if ((current_delay < conn->dac_buffer_queue_minimum_length) || (config.packet_stuffing == ST_basic)) { play_samples = stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format, From 56f4b124bd5110dadd00a8bfe7adab28cb82f425 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 8 Jan 2019 13:06:46 +0000 Subject: [PATCH 348/569] Adjust thresholds and timings to suit pi zero. --- audio_alsa.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index b7a5de8d..4ae2d029 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -633,8 +633,8 @@ static int init(int argc, char **argv) { config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.100; // below this, basic interpolation will be used to save time. config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem - config.audio_backend_silence_threshold = 0.050; //start sending silent frames if the delay goes below this time - config.audio_backend_silence_scan_interval = 0.005; //check silence threshold this often + config.audio_backend_silence_threshold = 0.040; //start sending silent frames if the delay goes below this time + config.audio_backend_silence_scan_interval = 0.003; //check silence threshold this often stall_monitor_error_threshold = (uint64_t)1000000 * config.alsa_maximum_stall_time; // stall time max to microseconds; @@ -1264,7 +1264,7 @@ static void flush(void) { int preflight(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) { uint64_t time_now = get_absolute_time_in_fp(); // this is to regulate access by the silence filler thread - uint64_t standoff_time = 100; // milliseconds + uint64_t standoff_time = 60; // milliseconds standoff_time = (standoff_time << 32)/1000; most_recent_write_time = time_now + standoff_time; return 0; @@ -1472,7 +1472,7 @@ void *alsa_buffer_monitor_thread_code(void *arg) { sleep_time_in_fp = sleep_time_in_fp << 32; sleep_time_in_fp = sleep_time_in_fp / 1000; // debug(1,"alsa: sleep_time: %d ms or 0x%" PRIx64 " in fp form.",sleep_time_ms,sleep_time_in_fp); - int frames_of_silence = (desired_sample_rate * sleep_time_ms * 4) / 1000; + int frames_of_silence = (desired_sample_rate * sleep_time_ms * 2) / 1000; size_t size_of_silence_buffer = frames_of_silence * frame_size; // debug(1,"Silence buffer length: %u bytes.",size_of_silence_buffer); void *silence = malloc(size_of_silence_buffer); From e9623faa44b2e652e2e94665c7128ee1dfd6cd02 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 8 Jan 2019 17:28:38 +0000 Subject: [PATCH 349/569] Go for a 0.250 second buffer length. --- audio_alsa.c | 11 ++++++----- player.c | 17 ++++++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 4ae2d029..b7b3e7e1 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -629,12 +629,12 @@ static int init(int argc, char **argv) { config.alsa_use_hardware_mute = 0; // don't use it by default config.audio_backend_latency_offset = 0; - config.audio_backend_buffer_desired_length = 0.15; + config.audio_backend_buffer_desired_length = 0.25; config.audio_backend_buffer_interpolation_threshold_in_seconds = - 0.100; // below this, basic interpolation will be used to save time. + 0.175; // below this, basic interpolation will be used to save time. config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem - config.audio_backend_silence_threshold = 0.040; //start sending silent frames if the delay goes below this time - config.audio_backend_silence_scan_interval = 0.003; //check silence threshold this often + config.audio_backend_silence_threshold = 0.60; //start sending silent frames if the delay goes below this time + config.audio_backend_silence_scan_interval = 0.004; //check silence threshold this often stall_monitor_error_threshold = (uint64_t)1000000 * config.alsa_maximum_stall_time; // stall time max to microseconds; @@ -1472,7 +1472,8 @@ void *alsa_buffer_monitor_thread_code(void *arg) { sleep_time_in_fp = sleep_time_in_fp << 32; sleep_time_in_fp = sleep_time_in_fp / 1000; // debug(1,"alsa: sleep_time: %d ms or 0x%" PRIx64 " in fp form.",sleep_time_ms,sleep_time_in_fp); - int frames_of_silence = (desired_sample_rate * sleep_time_ms * 2) / 1000; + // int frames_of_silence = (desired_sample_rate * sleep_time_ms * 2) / 1000; + int frames_of_silence = 1024; size_t size_of_silence_buffer = frames_of_silence * frame_size; // debug(1,"Silence buffer length: %u bytes.",size_of_silence_buffer); void *silence = malloc(size_of_silence_buffer); diff --git a/player.c b/player.c index 724b5cab..ac2226d9 100644 --- a/player.c +++ b/player.c @@ -4,7 +4,7 @@ * All rights reserved. * * Modifications for audio synchronisation - * and related work, copyright (c) Mike Brady 2014 -- 2018 + * and related work, copyright (c) Mike Brady 2014 -- 2019 * All rights reserved. * * Permission is hereby granted, free of charge, to any person @@ -624,6 +624,16 @@ int32_t rand_in_range(int32_t exclusive_range_limit) { static inline void process_sample(int32_t sample, char **outp, enum sps_format_t format, int volume, int dither, rtsp_conn_info *conn) { + /* + { + static int old_volume = 0; + if (volume != old_volume) { + debug(1,"Volume is now %d.",volume); + old_volume = volume; + } + } + */ + int64_t hyper_sample = sample; int result = 0; @@ -1554,7 +1564,7 @@ void *player_thread_func(void *arg) { conn->dac_buffer_queue_minimum_length = (int64_t)( config.audio_backend_buffer_interpolation_threshold_in_seconds * config.output_rate); - debug(1, "dac_buffer_queue_minimum_length is %" PRId64 " frames.", + debug(3, "dac_buffer_queue_minimum_length is %" PRId64 " frames.", conn->dac_buffer_queue_minimum_length); conn->session_corrections = 0; @@ -2220,9 +2230,6 @@ void *player_thread_func(void *arg) { } } - if (config.output->preflight) - config.output->preflight(conn->outbuf,play_samples); - if ((current_delay < conn->dac_buffer_queue_minimum_length) || (config.packet_stuffing == ST_basic)) { play_samples = From 34d6d89707e2cab7d08caa55e4d5722db1570a50 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 9 Jan 2019 10:09:29 +0000 Subject: [PATCH 350/569] Don't use full flush to purge frames after an overrun -- flush will reset the buffers as well. Fix a potential bug if a flush actually occurs. --- player.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/player.c b/player.c index ac2226d9..64aa3e56 100644 --- a/player.c +++ b/player.c @@ -856,8 +856,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { curframe = NULL; // this will be returned and will cause the loop to go around again conn->initial_reference_time = 0; conn->initial_reference_timestamp = 0; - } - if ((conn->flush_rtp_timestamp != 0) && + } else if ((conn->flush_rtp_timestamp != 0) && (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) > conn->input_rate / 5) && (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) < @@ -2093,8 +2092,12 @@ void *player_thread_func(void *arg) { debug(2, "Large positive sync error: %" PRId64 ".", sync_error); int64_t local_frames_to_drop = sync_error / conn->output_sample_ratio; uint32_t frames_to_drop_sized = local_frames_to_drop; - do_flush(inframe->given_timestamp + frames_to_drop_sized, - conn); // this will reset_input_flow_metrics anyway + + debug_mutex_lock(&conn->flush_mutex, 1000, 1); + conn->flush_rtp_timestamp = inframe->given_timestamp + frames_to_drop_sized; // flush all packets up to (and including?) this + reset_input_flow_metrics(conn); + debug_mutex_unlock(&conn->flush_mutex, 3); + } else if ((sync_error < 0) && ((-sync_error) > filler_length)) { debug(2, "Large negative sync error: %" PRId64 " with should_be_frame_32 of %" PRIu32 From 2a9d46afedb4539b2c273052cf2046b9236746df Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 9 Jan 2019 10:10:04 +0000 Subject: [PATCH 351/569] backend silence threshold should be 0.060 not 0.6! --- audio_alsa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_alsa.c b/audio_alsa.c index b7b3e7e1..3411c160 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -633,7 +633,7 @@ static int init(int argc, char **argv) { config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.175; // below this, basic interpolation will be used to save time. config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem - config.audio_backend_silence_threshold = 0.60; //start sending silent frames if the delay goes below this time + config.audio_backend_silence_threshold = 0.060; //start sending silent frames if the delay goes below this time config.audio_backend_silence_scan_interval = 0.004; //check silence threshold this often stall_monitor_error_threshold = From c958d506b1791cb2a745d16ec1811996b925fc2d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 9 Jan 2019 14:15:05 +0000 Subject: [PATCH 352/569] reduce thresholds towards a buffer time of 0.2 seconds rather than the 0.25 seconds before. --- audio_alsa.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 3411c160..2f491226 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -629,17 +629,17 @@ static int init(int argc, char **argv) { config.alsa_use_hardware_mute = 0; // don't use it by default config.audio_backend_latency_offset = 0; - config.audio_backend_buffer_desired_length = 0.25; + config.audio_backend_buffer_desired_length = 0.200; config.audio_backend_buffer_interpolation_threshold_in_seconds = - 0.175; // below this, basic interpolation will be used to save time. + 0.120; // below this, basic interpolation will be used to save time. config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem - config.audio_backend_silence_threshold = 0.060; //start sending silent frames if the delay goes below this time + config.audio_backend_silence_threshold = 0.040; //start sending silent frames if the delay goes below this time config.audio_backend_silence_scan_interval = 0.004; //check silence threshold this often stall_monitor_error_threshold = (uint64_t)1000000 * config.alsa_maximum_stall_time; // stall time max to microseconds; stall_monitor_error_threshold = (stall_monitor_error_threshold << 32) / 1000000; // now in fp form - debug(1, "stall_monitor_error_threshold is %" PRIX64 ", with alsa_maximum_stall_time of %f sec.", + debug(1, "stall_monitor_error_threshold is 0x%" PRIx64 ", with alsa_maximum_stall_time of %f sec.", stall_monitor_error_threshold, config.alsa_maximum_stall_time); stall_monitor_start_time = 0; From 9f8b8a25c947868a343f11412d42ac88f8b31dba Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 10 Jan 2019 16:09:52 +0000 Subject: [PATCH 353/569] add actual waiting time for a failing timed mutex. --- common.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/common.c b/common.c index aa38007f..ff8da65f 100644 --- a/common.c +++ b/common.c @@ -1160,15 +1160,17 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, timeoutTime.tv_sec = time_then; timeoutTime.tv_nsec = time_then_nsec; - + int64_t start_time = get_absolute_time_in_fp(); int r = pthread_mutex_timedlock(mutex, &timeoutTime); + int64_t et = get_absolute_time_in_fp() - start_time; if ((debuglevel != 0) && (r != 0) && (debugmessage != NULL)) { + et = (et * 1000000) >> 32; // microseconds char errstr[1000]; if (r == ETIMEDOUT) debug(debuglevel, - "waiting for a mutex, maximum expected time of %d microseconds exceeded \"%s\".", - dally_time, debugmessage); + "timed out waiting for a mutex, having waiting %f seconds, with a maximum waiting time of %d microseconds. \"%s\".", + (1.0 * et) / 1000000, dally_time, debugmessage); else debug(debuglevel, "error %d: \"%s\" waiting for a mutex: \"%s\".", r, strerror_r(r, errstr, sizeof(errstr)), debugmessage); From 8e72574a579322d976e77c17f73d06864e6bdf1d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 10 Jan 2019 16:10:53 +0000 Subject: [PATCH 354/569] Add some diagnostics, shorten timeouts and set so_linger of zero whenever a write or read error occurs. --- dacp.c | 53 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/dacp.c b/dacp.c index 04821650..f1c91e48 100644 --- a/dacp.c +++ b/dacp.c @@ -127,9 +127,9 @@ void addrinfo_cleanup(void *arg) { } void mutex_lock_cleanup(void *arg) { - // debug(1, "mutex lock cleanup called."); pthread_mutex_t *m = (pthread_mutex_t *)arg; - pthread_mutex_unlock(m); + if (pthread_mutex_unlock(m)) + debug(1,"Error releasing mutex."); } void connect_cleanup(void *arg) { @@ -193,6 +193,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // debug(1,"Error %d \"%s\" at getaddrinfo.",ires,gai_strerror(ires)); response.code = 498; // Bad Address information for the DACP server } else { + uint64_t start_time = get_absolute_time_in_fp(); pthread_cleanup_push(addrinfo_cleanup, (void *)&res); // only do this one at a time -- not sure it is necessary, but better safe than sorry @@ -200,7 +201,6 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // int mutex_reply = pthread_mutex_lock(&dacp_conversation_lock); if (mutex_reply == 0) { pthread_cleanup_push(mutex_lock_cleanup, (void *)&dacp_conversation_lock); - // debug(1,"dacp_conversation_lock acquired for command \"%s\".",command); // make a socket: sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); @@ -213,8 +213,8 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // debug(2, "dacp_send_command: open socket %d.",sockfd); struct timeval tv; - tv.tv_sec = 2; - tv.tv_usec = 0; + tv.tv_sec = 0; + tv.tv_usec = 80000; 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) @@ -223,7 +223,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // connect! // debug(1, "DACP socket created."); if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) { - // debug(2, "dacp_send_command: connect failed with errno %d.", errno); + debug(3, "dacp_send_command: connect failed with errno %d.", errno); response.code = 496; // Can't connect to the DACP server } else { // debug(1,"DACP connect succeeded."); @@ -233,13 +233,26 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { command, dacp_server.ip_string, dacp_server.port, dacp_server.active_remote_id); // Send command - // debug(3,"dacp_send_command: connect message: \"%s\".",message); - if (send(sockfd, message, strlen(message), 0) != (ssize_t)strlen(message)) { - debug(1, "dacp_send_command: send failed."); + debug(3,"dacp_send_command: \"%s\".",command); + ssize_t wresp = send(sockfd, message, strlen(message), 0); + if (wresp == -1) { + char errorstring[1024]; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + debug(2, "dacp_send_command: write error %d: \"%s\".", errno, (char *)errorstring); + struct linger so_linger; + so_linger.l_onoff = 1; // "true" + so_linger.l_linger = 0; + int err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger); + if (err) + debug(1, "Could not set the dacp socket to abort due to a write error on closing."); + } + if (wresp != (ssize_t)strlen(message)) { + // debug(1, "dacp_send_command: send failed."); response.code = 493; // Client failed to send a message } else { + response.body = malloc(2048); // it can resize this if necessary response.malloced_size = 2048; pthread_cleanup_push(malloc_cleanup, response.body); @@ -256,11 +269,21 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { const char *data = buffer; if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1) debug(1, "dacp_send_command: error %d setting receive timeout.", errno); - int ndata = recv(sockfd, buffer, sizeof(buffer), 0); + ssize_t ndata = recv(sockfd, buffer, sizeof(buffer), 0); // debug(3, "Received %d bytes: \"%s\".", ndata, buffer); if (ndata <= 0) { - // debug(1, "dacp_send_command -- error receiving response for command \"%s\".", - // command); + if (ndata == -1) { + char errorstring[1024]; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + debug(2, "dacp_send_command: receiving error %d: \"%s\".", errno,(char *)errorstring); + struct linger so_linger; + so_linger.l_onoff = 1; // "true" + so_linger.l_linger = 0; + int err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger); + if (err) + debug(1, "Could not set the dacp socket to abort due to a read error on closing."); + } + free(response.body); response.body = NULL; response.malloced_size = 0; @@ -308,6 +331,9 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { } pthread_cleanup_pop(1); // this should free the addrinfo // freeaddrinfo(res); + uint64_t et = get_absolute_time_in_fp() - start_time; + et = (et * 1000000) >> 32; // microseconds + debug(2,"dacp_send_command: %f seconds, response code %d, command \"%s\".",(1.0 * et) / 1000000, response.code, command); } *body = response.body; *bodysize = response.size; @@ -829,6 +855,8 @@ void dacp_monitor_start() { rc = pthread_mutex_init(&dacp_conversation_lock, &mta); if (rc) debug(1, "Error creating the DACP Conversation Lock Mutex Init"); + else + debug(1, "DACP Conversation Lock Mutex Init"); rc = pthread_mutexattr_destroy(&mta); if (rc) @@ -864,6 +892,7 @@ void dacp_monitor_stop() { pthread_cancel(dacp_monitor_thread); pthread_join(dacp_monitor_thread, NULL); pthread_mutex_destroy(&dacp_server_information_lock); + debug(1, "DACP Conversation Lock Mutex Destroyed"); pthread_mutex_destroy(&dacp_conversation_lock); } From 1c40176468fb62a58456101db364044b321a526d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 10 Jan 2019 16:11:56 +0000 Subject: [PATCH 355/569] CLean up timings, clean up DACP interface network timings and error behaviours. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 6f3d150e..4b1f2915 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.3d44], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d45], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 8f20e5808ba8ff4ca9ffd5fab0dc794ef082c453 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 10 Jan 2019 16:12:57 +0000 Subject: [PATCH 356/569] clang format --- common.c | 8 ++++---- dacp.c | 60 +++++++++++++++++++++++++++++--------------------------- 2 files changed, 35 insertions(+), 33 deletions(-) diff --git a/common.c b/common.c index ff8da65f..df3c2ca6 100644 --- a/common.c +++ b/common.c @@ -1160,16 +1160,16 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, timeoutTime.tv_sec = time_then; timeoutTime.tv_nsec = time_then_nsec; - int64_t start_time = get_absolute_time_in_fp(); + int64_t start_time = get_absolute_time_in_fp(); int r = pthread_mutex_timedlock(mutex, &timeoutTime); int64_t et = get_absolute_time_in_fp() - start_time; if ((debuglevel != 0) && (r != 0) && (debugmessage != NULL)) { - et = (et * 1000000) >> 32; // microseconds + et = (et * 1000000) >> 32; // microseconds char errstr[1000]; if (r == ETIMEDOUT) - debug(debuglevel, - "timed out waiting for a mutex, having waiting %f seconds, with a maximum waiting time of %d microseconds. \"%s\".", + debug(debuglevel, "timed out waiting for a mutex, having waiting %f seconds, with a maximum " + "waiting time of %d microseconds. \"%s\".", (1.0 * et) / 1000000, dally_time, debugmessage); else debug(debuglevel, "error %d: \"%s\" waiting for a mutex: \"%s\".", r, diff --git a/dacp.c b/dacp.c index f1c91e48..c0c24421 100644 --- a/dacp.c +++ b/dacp.c @@ -129,7 +129,7 @@ void addrinfo_cleanup(void *arg) { void mutex_lock_cleanup(void *arg) { pthread_mutex_t *m = (pthread_mutex_t *)arg; if (pthread_mutex_unlock(m)) - debug(1,"Error releasing mutex."); + debug(1, "Error releasing mutex."); } void connect_cleanup(void *arg) { @@ -233,26 +233,25 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { command, dacp_server.ip_string, dacp_server.port, dacp_server.active_remote_id); // Send command - debug(3,"dacp_send_command: \"%s\".",command); + debug(3, "dacp_send_command: \"%s\".", command); ssize_t wresp = send(sockfd, message, strlen(message), 0); if (wresp == -1) { - char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(2, "dacp_send_command: write error %d: \"%s\".", errno, (char *)errorstring); - struct linger so_linger; - so_linger.l_onoff = 1; // "true" - so_linger.l_linger = 0; - int err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger); - if (err) - debug(1, "Could not set the dacp socket to abort due to a write error on closing."); - } + char errorstring[1024]; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + debug(2, "dacp_send_command: write error %d: \"%s\".", errno, (char *)errorstring); + struct linger so_linger; + so_linger.l_onoff = 1; // "true" + so_linger.l_linger = 0; + int err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger); + if (err) + debug(1, "Could not set the dacp socket to abort due to a write error on closing."); + } if (wresp != (ssize_t)strlen(message)) { // debug(1, "dacp_send_command: send failed."); response.code = 493; // Client failed to send a message } else { - response.body = malloc(2048); // it can resize this if necessary response.malloced_size = 2048; pthread_cleanup_push(malloc_cleanup, response.body); @@ -272,18 +271,20 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { ssize_t ndata = recv(sockfd, buffer, sizeof(buffer), 0); // debug(3, "Received %d bytes: \"%s\".", ndata, buffer); if (ndata <= 0) { - if (ndata == -1) { - char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(2, "dacp_send_command: receiving error %d: \"%s\".", errno,(char *)errorstring); - struct linger so_linger; - so_linger.l_onoff = 1; // "true" - so_linger.l_linger = 0; - int err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger); - if (err) - debug(1, "Could not set the dacp socket to abort due to a read error on closing."); - } - + if (ndata == -1) { + char errorstring[1024]; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + debug(2, "dacp_send_command: receiving error %d: \"%s\".", errno, + (char *)errorstring); + struct linger so_linger; + so_linger.l_onoff = 1; // "true" + so_linger.l_linger = 0; + int err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger); + if (err) + debug(1, + "Could not set the dacp socket to abort due to a read error on closing."); + } + free(response.body); response.body = NULL; response.malloced_size = 0; @@ -331,9 +332,10 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { } pthread_cleanup_pop(1); // this should free the addrinfo // freeaddrinfo(res); - uint64_t et = get_absolute_time_in_fp() - start_time; - et = (et * 1000000) >> 32; // microseconds - debug(2,"dacp_send_command: %f seconds, response code %d, command \"%s\".",(1.0 * et) / 1000000, response.code, command); + uint64_t et = get_absolute_time_in_fp() - start_time; + et = (et * 1000000) >> 32; // microseconds + debug(2, "dacp_send_command: %f seconds, response code %d, command \"%s\".", + (1.0 * et) / 1000000, response.code, command); } *body = response.body; *bodysize = response.size; @@ -856,7 +858,7 @@ void dacp_monitor_start() { if (rc) debug(1, "Error creating the DACP Conversation Lock Mutex Init"); else - debug(1, "DACP Conversation Lock Mutex Init"); + debug(1, "DACP Conversation Lock Mutex Init"); rc = pthread_mutexattr_destroy(&mta); if (rc) From fbd8377519466542653c1856a326871da4b5e01d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 10 Jan 2019 16:57:40 +0000 Subject: [PATCH 357/569] Always add dither if no hardware mixer and volume control is not ignored. --- audio_alsa.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 2f491226..a1b81e4c 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1500,8 +1500,7 @@ void *alsa_buffer_monitor_thread_code(void *arg) { (char *)errorstring); } if (buffer_size < buffer_size_threshold) { - if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && - (config.airplay_volume != 0.0)) + if ((hardware_mixer == 0) && (config.ignore_volume_control == 0)) use_dither = 1; else use_dither = 0; From 2ca6e42b626ad22f27fe319e0388ce8de2953325 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 10 Jan 2019 17:00:10 +0000 Subject: [PATCH 358/569] Do not add dither if at full volume or ignoring volume control --- player.c | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/player.c b/player.c index 64aa3e56..36461c45 100644 --- a/player.c +++ b/player.c @@ -625,15 +625,15 @@ int32_t rand_in_range(int32_t exclusive_range_limit) { static inline void process_sample(int32_t sample, char **outp, enum sps_format_t format, int volume, int dither, rtsp_conn_info *conn) { /* - { - static int old_volume = 0; - if (volume != old_volume) { - debug(1,"Volume is now %d.",volume); - old_volume = volume; - } + { + static int old_volume = 0; + if (volume != old_volume) { + debug(1,"Volume is now %d.",volume); + old_volume = volume; + } } */ - + int64_t hyper_sample = sample; int result = 0; @@ -857,10 +857,10 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { conn->initial_reference_time = 0; conn->initial_reference_timestamp = 0; } else if ((conn->flush_rtp_timestamp != 0) && - (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) > - conn->input_rate / 5) && - (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) < - conn->input_rate * 10)) { + (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) > + conn->input_rate / 5) && + (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) < + conn->input_rate * 10)) { debug(3, "Dropping flush request in buffer_get_frame"); conn->flush_rtp_timestamp = 0; } @@ -1827,8 +1827,9 @@ void *player_thread_func(void *arg) { } } else { - if ((config.output->parameters == NULL) || (conn->input_bit_depth > output_bit_depth) || - (config.playback_mode == ST_mono)) + if (((config.output->parameters == NULL) && (config.ignore_volume_control == 0) && + (config.airplay_volume != 0.0)) || + (conn->input_bit_depth > output_bit_depth) || (config.playback_mode == ST_mono)) conn->enable_dither = 1; else conn->enable_dither = 0; @@ -2092,9 +2093,11 @@ void *player_thread_func(void *arg) { debug(2, "Large positive sync error: %" PRId64 ".", sync_error); int64_t local_frames_to_drop = sync_error / conn->output_sample_ratio; uint32_t frames_to_drop_sized = local_frames_to_drop; - + debug_mutex_lock(&conn->flush_mutex, 1000, 1); - conn->flush_rtp_timestamp = inframe->given_timestamp + frames_to_drop_sized; // flush all packets up to (and including?) this + conn->flush_rtp_timestamp = + inframe->given_timestamp + + frames_to_drop_sized; // flush all packets up to (and including?) this reset_input_flow_metrics(conn); debug_mutex_unlock(&conn->flush_mutex, 3); @@ -2233,7 +2236,7 @@ void *player_thread_func(void *arg) { } } - if ((current_delay < conn->dac_buffer_queue_minimum_length) || + if ((current_delay < conn->dac_buffer_queue_minimum_length) || (config.packet_stuffing == ST_basic)) { play_samples = stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format, From b9d78c8af64f5e5af4a92fba3e616e3573c20142 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 11 Jan 2019 10:42:17 +0000 Subject: [PATCH 359/569] Avoid wrapping back to the first three UDP ports when assigning ports using nextFreeUDPPort because the call resetFreeUDPPorts, called when not interrupting an existing session, will assign the first three ports without checking (or knowing) if they were recently in use. --- common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.c b/common.c index df3c2ca6..05edae98 100644 --- a/common.c +++ b/common.c @@ -114,7 +114,7 @@ uint16_t nextFreeUDPPort() { if (UDPPortIndex == 0) UDPPortIndex = config.udp_port_base; else if (UDPPortIndex == (config.udp_port_base + config.udp_port_range - 1)) - UDPPortIndex = config.udp_port_base; + UDPPortIndex = config.udp_port_base + 3; // avoid wrapping back to the first three, as they can be assigned by resetFreeUDPPort without checking else UDPPortIndex++; return UDPPortIndex; From a74261bbd35e2821e5ef4f22496ff6dafff4181f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 11 Jan 2019 14:48:00 +0000 Subject: [PATCH 360/569] Add a D-Bus property "DisableStandby" to turn standby oo or off. --- dbus-service.c | 37 +++++++++++++++++++++++++++++-------- org.gnome.ShairportSync.xml | 1 + 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/dbus-service.c b/dbus-service.c index c163ddb9..9ae1d0aa 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -378,14 +378,27 @@ gboolean notify_verbosity_callback(ShairportSyncDiagnostics *skeleton, return TRUE; } +gboolean notify_disable_standby_callback(ShairportSync *skeleton, + __attribute__((unused)) gpointer user_data) { + // debug(1, "\"notify_disable_standby_callback\" called."); + if (shairport_sync_get_disable_standby(skeleton)) { + debug(1, ">> activating disable standby"); + config.keep_dac_busy = 1; + } else { + debug(1, ">> deactivating disable standby"); + config.keep_dac_busy = 0; + } + return TRUE; +} + gboolean notify_loudness_filter_active_callback(ShairportSync *skeleton, __attribute__((unused)) gpointer user_data) { - debug(1, "\"notify_loudness_filter_active_callback\" called."); + // debug(1, "\"notify_loudness_filter_active_callback\" called."); if (shairport_sync_get_loudness_filter_active(skeleton)) { - debug(1, "activating loudness filter"); + debug(1, ">> activating loudness filter"); config.loudness = 1; } else { - debug(1, "deactivating loudness filter"); + debug(1, ">> deactivating loudness filter"); config.loudness = 0; } return TRUE; @@ -395,10 +408,10 @@ gboolean notify_loudness_threshold_callback(ShairportSync *skeleton, __attribute__((unused)) gpointer user_data) { gdouble th = shairport_sync_get_loudness_threshold(skeleton); if ((th <= 0.0) && (th >= -100.0)) { - debug(1, "Setting loudness threshhold to %f.", th); + debug(1, ">> setting loudness threshhold to %f.", th); config.loudness_reference_volume_db = th; } else { - debug(1, "Invalid loudness threshhold: %f. Ignored.", th); + debug(1, ">> invalid loudness threshhold: %f. Ignored.", th); shairport_sync_set_loudness_threshold(skeleton, config.loudness_reference_volume_db); } return TRUE; @@ -408,10 +421,10 @@ gboolean notify_drift_tolerance_callback(ShairportSync *skeleton, __attribute__((unused)) gpointer user_data) { gdouble dt = shairport_sync_get_drift_tolerance(skeleton); if ((dt >= 0.0) && (dt <= 2.0)) { - debug(1, "Setting drift tolerance to %f.", dt); + debug(1, ">> setting drift tolerance to %f seconds", dt); config.tolerance = dt; } else { - debug(1, "Invalid drift tolerance: %f. Ignored.", dt); + debug(1, ">> invalid drift tolerance: %f seconds. Ignored.", dt); shairport_sync_set_drift_tolerance(skeleton, config.tolerance); } return TRUE; @@ -611,7 +624,9 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name G_CALLBACK(notify_alacdecoder_callback), NULL); g_signal_connect(shairportSyncSkeleton, "notify::volume-control-profile", G_CALLBACK(notify_volume_control_profile_callback), NULL); - g_signal_connect(shairportSyncSkeleton, "notify::loudness-filter-active", + g_signal_connect(shairportSyncSkeleton, "notify::disable-standby", + G_CALLBACK(notify_disable_standby_callback), NULL); +g_signal_connect(shairportSyncSkeleton, "notify::loudness-filter-active", G_CALLBACK(notify_loudness_filter_active_callback), NULL); g_signal_connect(shairportSyncSkeleton, "notify::loudness-threshold", G_CALLBACK(notify_loudness_threshold_callback), NULL); @@ -710,6 +725,12 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name else shairport_sync_set_volume_control_profile(SHAIRPORT_SYNC(shairportSyncSkeleton), "flat"); + if (config.keep_dac_busy == 0) { + shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE); + } else { + shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE); + } + if (config.loudness == 0) { shairport_sync_set_loudness_filter_active(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE); } else { diff --git a/org.gnome.ShairportSync.xml b/org.gnome.ShairportSync.xml index 44d90a27..279e4f0a 100644 --- a/org.gnome.ShairportSync.xml +++ b/org.gnome.ShairportSync.xml @@ -2,6 +2,7 @@ + From 049114459d8cecf7d91e45a243ff87c6bc5fa884 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 11 Jan 2019 15:17:01 +0000 Subject: [PATCH 361/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 277ee530..46956815 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -15,7 +15,7 @@ A downside to this feature is that the output device totally dedicated to Shairp Note -- this setting is likely to change. It will probably be necessary to modify it somehow to enable it to be used in integrated systems. Your feedback would be welcome. -**Enhanccements and Bug Fixes** +**Enhancements and Bug Fixes** At present, Shairport Sync adds dither to the audio if (a) the built-in software-based volume control is used, (b) if the audio is mixed to mono or (3) if there is a change in sample size, say from 16- to 24-bit. The code for generating dither has been much improved. Due to a poor pseudo-random number arrangement, the dither noise didn't sound like white noise -- now it does. From 50cd672130d3e79c45675107ca6ecd807b6231c9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 11 Jan 2019 15:17:17 +0000 Subject: [PATCH 362/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 46956815..fb57de37 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -7,7 +7,7 @@ This important new feature helps with removing some annoying clicking / popping This is an ALSA-specific attempt to remove the annoying low-level clicking sounds that some output devices make just when they start processing audio and sometimes when they stop. Typically a faint click might be heard just before a play session starts or just before audio resumes after a pause. Similarly, a faint click can sometimes be heard just after a play session ends. -It is extremely difficuly to remove these clicks completely from the hardware, so this new feature ensures that — once Shairport Sync has started — the output device avoids situations where these clicks might be generated by *always* playing audio. To accomplish this, if Shairport Sync isn't actually playing anything, audio frames consisting of silence are sent to the output device. Apart from the initial startup transition, the output device never stops playing, avoiding both starting and stopping transitions. +It is extremely difficult to remove these clicks completely from the hardware, so this new feature ensures that — once Shairport Sync has started — the output device avoids situations where these clicks might be generated by *always* playing audio. To accomplish this, if Shairport Sync isn't actually playing anything, audio frames consisting of silence are sent to the output device. Apart from the initial startup transition, the output device never stops playing, avoiding both starting and stopping transitions. To enable this feature, a new `alsa` group setting with the name `disable_standby_mode` is available. (If you do a full `$ sudo make install`, a new sample configuration file with this setting in it is installed at `/etc/shairport-sync.conf.sample` or `/usr/local/etc/shairport-sync.conf.sample`.) The default for the setting is `"no"`. From c75944d6168d8caa5e65ffd05933798736c30214 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 11 Jan 2019 15:18:50 +0000 Subject: [PATCH 363/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index fb57de37..9a424de7 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -19,7 +19,7 @@ Note -- this setting is likely to change. It will probably be necessary to modif At present, Shairport Sync adds dither to the audio if (a) the built-in software-based volume control is used, (b) if the audio is mixed to mono or (3) if there is a change in sample size, say from 16- to 24-bit. The code for generating dither has been much improved. Due to a poor pseudo-random number arrangement, the dither noise didn't sound like white noise -- now it does. -In addition, if dither is to be added, it is also added to the silence inserted just prior to the start of play, and is also added tthe silent frames used to prevent the output device from going into standby mode, if selected. +In addition, if dither is to be added, it is also added to the silence inserted just prior to the start of play, and is also added to the silent frames used to prevent the output device from going into standby mode, if selected. Version 3.3d37 to Version 3.3d22 ==== From ca72645cb84999895a834e420e598939251ecc11 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 12 Jan 2019 19:17:43 +0000 Subject: [PATCH 364/569] If a message is dropped because the pc queue is busy, free any data that might be associated with it as well as just the rtsp message. --- rtsp.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/rtsp.c b/rtsp.c index a90c5aa0..011d2ae3 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1478,10 +1478,13 @@ int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rts msg_retain(carrier); pack.carrier = carrier; int rc = pc_queue_add_item(&metadata_queue, &pack, block); - if ((rc == EBUSY) && (carrier)) - msg_free(carrier); - if (rc == EBUSY) - warn("Metadata queue is busy, dropping message of type 0x%08X, code 0x%08X.", type, code); + if (rc == EBUSY) { + if (carrier) + msg_free(carrier); + else if (data) + free(data); + warn("Metadata queue is busy, discarding message of type 0x%08X, code 0x%08X.", type, code); + } return rc; } From 9fbdc062b404f48a369a435a92399a369ee423a3 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 12 Jan 2019 21:44:52 +0000 Subject: [PATCH 365/569] Change how rtsp_messages are memory managed. Add some diagnostics. --- rtsp.c | 87 ++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 24 deletions(-) diff --git a/rtsp.c b/rtsp.c index 011d2ae3..d869c85b 100644 --- a/rtsp.c +++ b/rtsp.c @@ -119,7 +119,10 @@ typedef struct { } pc_queue; // producer-consumer queue #endif +static int msg_indexes = 1; + typedef struct { + int index_number; uint32_t referenceCount; // we might start using this... unsigned int nheaders; char *name[16]; @@ -401,16 +404,17 @@ static char *nextline(char *in, int inbuf) { } void msg_retain(rtsp_message *msg) { - if (msg) { int rc = pthread_mutex_lock(&reference_counter_lock); if (rc) debug(1, "Error %d locking reference counter lock"); + if (msg > (rtsp_message *) 0x00010000) { 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) debug(1, "Error %d unlocking reference counter lock"); } else { - debug(1, "null rtsp_message pointer passed to retain"); + debug(1, "invalid rtsp_message pointer %d passed to retain", (int) msg); } } @@ -419,9 +423,11 @@ rtsp_message *msg_init(void) { if (msg) { 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++; } else { - die("can not allocate memory for an rtsp_message."); + die("msg_init -- can not allocate memory for rtsp_message %d.", msg_indexes); } + debug(1,"msg_init -- create item %d.", msg->index_number); return msg; } @@ -476,6 +482,37 @@ static void debug_print_msg_content(int level, rtsp_message *msg) { } */ +void msg_free(rtsp_message **msgh) { + debug_mutex_lock(&reference_counter_lock, 1000, 0); + if (*msgh > (rtsp_message *)0x00010000) { + rtsp_message *msg = *msgh; + msg->referenceCount--; + if (msg->referenceCount == 0) { + unsigned int i; + for (i = 0; i < msg->nheaders; i++) { + free(msg->name[i]); + free(msg->value[i]); + } + if (msg->content) + free(msg->content); + debug(1,"msg_free item %d -- free.",msg->index_number); + *msgh = (rtsp_message *)(msg->index_number & 0xFFFF); // put the index number of the freed message in here + free(msg); + } else { + debug(1,"msg_free item %d -- decrement reference to %d.",msg->index_number,msg->referenceCount); + } + + // else { + // debug(1,"rtsp_message reference count non-zero: + // %d!",msg->referenceCount); + //} + } else if (*msgh != NULL) { + debug(1, "msg_free: error attempting to free an allocated but already-freed rtsp_message %d.",(int)*msgh); + } + debug_mutex_unlock(&reference_counter_lock, 0); +} + +/* void msg_free(rtsp_message *msg) { if (msg) { @@ -499,6 +536,7 @@ void msg_free(rtsp_message *msg) { debug(1, "null rtsp_message pointer passed to msg_free()"); } } +*/ int msg_handle_line(rtsp_message **pmsg, char *line) { rtsp_message *msg = *pmsg; @@ -550,17 +588,22 @@ int msg_handle_line(rtsp_message **pmsg, char *line) { } fail: - *pmsg = NULL; - msg_free(msg); + msg_free(pmsg); return 0; } enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) { + + *the_packet = NULL; // need this for erro handling + enum rtsp_read_request_response reply = rtsp_read_request_response_ok; ssize_t buflen = 4096; char *buf = malloc(buflen + 1); // add a NUL at the end - - rtsp_message *msg = NULL; + if (!buf) { + warn("rtsp_read_request: can't get a buffer."); + reply = rtsp_read_request_response_error; + goto shutdown; + } ssize_t nread; ssize_t inbuf = 0; @@ -607,9 +650,9 @@ do { char *next; while (msg_size < 0 && (next = nextline(buf, inbuf))) { - msg_size = msg_handle_line(&msg, buf); + msg_size = msg_handle_line(the_packet, buf); - if (!msg) { + if (!(*the_packet)) { warn("no RTSP header received"); reply = rtsp_read_request_response_bad_packet; goto shutdown; @@ -690,6 +733,7 @@ do { inbuf += nread; } + rtsp_message *msg = *the_packet; msg->contentlength = inbuf; msg->content = buf; char *jp = inbuf + buf; @@ -698,13 +742,8 @@ do { return reply; shutdown: - if (msg) { - msg_free(msg); // which will free the content and everything else - } - // in case the message wasn't formed or wasn't fully initialised - if ((msg && (msg->content == NULL)) || (!msg)) - free(buf); - *the_packet = NULL; + msg_free(the_packet); + free(buf); return reply; } @@ -1396,7 +1435,7 @@ void metadata_pack_cleanup_function(void *arg) { // debug(1, "metadata_pack_cleanup_function called"); metadata_package *pack = (metadata_package *)arg; if (pack->carrier) - msg_free(pack->carrier); // release the message + msg_free(&pack->carrier); // release the message else if (pack->data) free(pack->data); } @@ -1474,13 +1513,13 @@ int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rts pack.code = code; pack.data = data; pack.length = length; - if (carrier) - msg_retain(carrier); pack.carrier = carrier; + if (pack.carrier) + msg_retain(pack.carrier); int rc = pc_queue_add_item(&metadata_queue, &pack, block); if (rc == EBUSY) { - if (carrier) - msg_free(carrier); + if (pack.carrier) + msg_free(&pack.carrier); else if (data) free(data); warn("Metadata queue is busy, discarding message of type 0x%08X, code 0x%08X.", type, code); @@ -2171,7 +2210,7 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { void msg_cleanup_function(void *arg) { // debug(3, "msg_cleanup_function called."); - msg_free((rtsp_message *)arg); + msg_free((rtsp_message **)arg); } static void *rtsp_conversation_thread_func(void *pconn) { @@ -2217,9 +2256,9 @@ static void *rtsp_conversation_thread_func(void *pconn) { int debug_level = 3; // for printing the request and response reply = rtsp_read_request(conn, &req); if (reply == rtsp_read_request_response_ok) { - pthread_cleanup_push(msg_cleanup_function, (void *)req); + pthread_cleanup_push(msg_cleanup_function, (void *)&req); resp = msg_init(); - pthread_cleanup_push(msg_cleanup_function, (void *)resp); + pthread_cleanup_push(msg_cleanup_function, (void *)&resp); resp->respcode = 400; if (strcmp(req->method, "OPTIONS") != From 6a27712e94e7574f9a04dfb94b5694b9f43b4c7e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 13 Jan 2019 10:00:11 +0000 Subject: [PATCH 366/569] Quieten a debug message. --- dacp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dacp.c b/dacp.c index c0c24421..d82f0594 100644 --- a/dacp.c +++ b/dacp.c @@ -334,7 +334,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // freeaddrinfo(res); uint64_t et = get_absolute_time_in_fp() - start_time; et = (et * 1000000) >> 32; // microseconds - debug(2, "dacp_send_command: %f seconds, response code %d, command \"%s\".", + debug(3, "dacp_send_command: %f seconds, response code %d, command \"%s\".", (1.0 * et) / 1000000, response.code, command); } *body = response.body; From 93cae8a5d1bbe80532892405a5e6d4e8e5888bc9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 13 Jan 2019 10:17:34 +0000 Subject: [PATCH 367/569] Quieten the msg_ debug messages. --- rtsp.c | 50 +++++++++++--------------------------------------- 1 file changed, 11 insertions(+), 39 deletions(-) diff --git a/rtsp.c b/rtsp.c index d869c85b..d44ae0f0 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-2018 + * metadata handling copyright (c) Mike Brady 2014-2019 * All rights reserved. * * Permission is hereby granted, free of charge, to any person @@ -409,12 +409,12 @@ void msg_retain(rtsp_message *msg) { debug(1, "Error %d locking reference counter lock"); if (msg > (rtsp_message *) 0x00010000) { msg->referenceCount++; - debug(1,"msg_retain -- item %d reference count %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) debug(1, "Error %d unlocking reference counter lock"); } else { - debug(1, "invalid rtsp_message pointer %d passed to retain", (int) msg); + debug(1, "invalid rtsp_message pointer 0x%x passed to retain", (uintptr_t) msg); } } @@ -427,7 +427,7 @@ rtsp_message *msg_init(void) { } else { die("msg_init -- can not allocate memory for rtsp_message %d.", msg_indexes); } - debug(1,"msg_init -- create item %d.", msg->index_number); + // debug(1,"msg_init -- create item %d.", msg->index_number); return msg; } @@ -495,49 +495,21 @@ void msg_free(rtsp_message **msgh) { } if (msg->content) free(msg->content); - debug(1,"msg_free item %d -- free.",msg->index_number); - *msgh = (rtsp_message *)(msg->index_number & 0xFFFF); // put the index number of the freed message in here + // debug(1,"msg_free item %d -- free.",msg->index_number); + uintptr_t index = (msg->index_number) & 0xFFFF; + if (index == 0) + 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 free(msg); } else { - debug(1,"msg_free item %d -- decrement reference to %d.",msg->index_number,msg->referenceCount); + // debug(1,"msg_free item %d -- decrement reference to %d.",msg->index_number,msg->referenceCount); } - - // else { - // debug(1,"rtsp_message reference count non-zero: - // %d!",msg->referenceCount); - //} } else if (*msgh != NULL) { - debug(1, "msg_free: error attempting to free an allocated but already-freed rtsp_message %d.",(int)*msgh); + debug(1, "msg_free: error attempting to free an allocated but already-freed rtsp_message, number %d.",(uintptr_t)*msgh); } debug_mutex_unlock(&reference_counter_lock, 0); } -/* -void msg_free(rtsp_message *msg) { - - if (msg) { - debug_mutex_lock(&reference_counter_lock, 1000, 0); - msg->referenceCount--; - debug_mutex_unlock(&reference_counter_lock, 0); - if (msg->referenceCount == 0) { - unsigned int i; - for (i = 0; i < msg->nheaders; i++) { - free(msg->name[i]); - free(msg->value[i]); - } - if (msg->content) - free(msg->content); - free(msg); - } // else { - // debug(1,"rtsp_message reference count non-zero: - // %d!",msg->referenceCount); - //} - } else { - debug(1, "null rtsp_message pointer passed to msg_free()"); - } -} -*/ - int msg_handle_line(rtsp_message **pmsg, char *line) { rtsp_message *msg = *pmsg; From 6c912d6ce53c1762734bf30f461437f2d7d91b4c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 13 Jan 2019 10:20:26 +0000 Subject: [PATCH 368/569] Revise the code for retaining and freeing rtsp message memory allocations. Be more careful about cleaning up a possible message allocation when the rtsp channel is closed. Watch for overreleasing a message. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 4b1f2915..dfeb1147 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.3d45], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d46], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 68f20fe8db92b8d42176139cfaac5d8b2c117cb1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 13 Jan 2019 13:14:39 +0000 Subject: [PATCH 369/569] Add an error message if an RTSP thread can not be created. --- rtsp.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/rtsp.c b/rtsp.c index d44ae0f0..6e98944f 100644 --- a/rtsp.c +++ b/rtsp.c @@ -2590,8 +2590,12 @@ void rtsp_listen_loop(void) { ret = pthread_create(&conn->thread, NULL, rtsp_conversation_thread_func, conn); // also acts as a memory barrier - if (ret) - die("Failed to create RTSP receiver thread %d!", conn->connection_number); + if (ret) { + char errorstring[1024]; + strerror_r(ret, (char *)errorstring, sizeof(errorstring)); + die("Connection %d: cannot create an RTSP conversation thread. Error %d: \"%s\".", + conn->connection_number, ret, (char *)errorstring); + } debug(3, "Successfully created RTSP receiver thread %d.", conn->connection_number); conn->running = 1; // this must happen before the thread is tracked track_thread(conn); From 44830377193c341ec7aff8e0320ff07fd9bbd519 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 15 Jan 2019 08:30:00 +0000 Subject: [PATCH 370/569] Block on command_execute for unfixable errors. --- common.c | 4 ++-- common.h | 2 +- player.c | 4 ++-- rtsp.c | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/common.c b/common.c index 05edae98..729bcacd 100644 --- a/common.c +++ b/common.c @@ -739,7 +739,7 @@ void command_start(void) { } } } -void command_execute(const char *command, const char *extra_argument) { +void command_execute(const char *command, const char *extra_argument, const int block) { // this has a cancellation point if waiting is enabled if (command) { char new_command_buffer[1024]; @@ -767,7 +767,7 @@ void command_execute(const char *command, const char *extra_argument) { exit(127); /* only if execv fails */ } } else { - if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for + if (block) { /* pid!=0 means parent process and if blocking is true, wait for process to finish */ pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */ if (rc != pid) { diff --git a/common.h b/common.h index 6f07f6d3..c6d5ae3a 100644 --- a/common.h +++ b/common.h @@ -309,7 +309,7 @@ int config_set_lookup_bool(config_t *cfg, char *where, int *dst); void command_start(void); void command_stop(void); -void command_execute(const char *command, const char *extra_argument); +void command_execute(const char *command, const char *extra_argument, const int block); void command_set_volume(double volume); int mkpath(const char *path, mode_t mode); diff --git a/player.c b/player.c index 36461c45..ba40bf5b 100644 --- a/player.c +++ b/player.c @@ -1098,7 +1098,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { (conn->unfixable_error_reported == 0)) { conn->unfixable_error_reported = 1; if (config.cmd_unfixable) { - command_execute(config.cmd_unfixable, "output_device_stalled"); + command_execute(config.cmd_unfixable, "output_device_stalled", 1); } else { warn( "an unrecoverable error, \"output_device_stalled\", has been detected.", @@ -2010,7 +2010,7 @@ void *player_thread_func(void *arg) { "stalled. Executing the " "\"run_this_if_an_unfixable_error_is_detected\" command.", conn->connection_number); - command_execute(config.cmd_unfixable, "output_device_stalled"); + command_execute(config.cmd_unfixable, "output_device_stalled", 1); } else { warn("Connection %d: An unfixable error has been detected -- output device is " "stalled. \"No " diff --git a/rtsp.c b/rtsp.c index 6e98944f..4a0be9ba 100644 --- a/rtsp.c +++ b/rtsp.c @@ -296,7 +296,7 @@ void *player_watchdog_thread_code(void *arg) { } else if (conn->watchdog_barks == 3) { if ((config.cmd_unfixable) && (conn->unfixable_error_reported == 0)) { conn->unfixable_error_reported = 1; - command_execute(config.cmd_unfixable, "unable_to_cancel_play_session"); + command_execute(config.cmd_unfixable, "unable_to_cancel_play_session",1); } else { warn("an unrecoverable error, \"unable_to_cancel_play_session\", has been detected.", conn->connection_number); From 020a69dfb9be8099138de580cb98578196ffeca9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 18 Jan 2019 16:27:29 +0000 Subject: [PATCH 371/569] remove the cause of a CLANG warning --- audio_alsa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_alsa.c b/audio_alsa.c index a1b81e4c..49500038 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1100,7 +1100,7 @@ int delay(long *the_delay) { int oldState; snd_pcm_state_t state; - snd_pcm_sframes_t my_delay; + snd_pcm_sframes_t my_delay = 0; // this initialisation is to silence a clang warning pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0); From f6d6700d6efd117ad87fcb4c62b81567f7ccc4a7 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 18 Jan 2019 16:28:02 +0000 Subject: [PATCH 372/569] Quieten a debug message. --- dacp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dacp.c b/dacp.c index d82f0594..a4a04df1 100644 --- a/dacp.c +++ b/dacp.c @@ -857,8 +857,8 @@ void dacp_monitor_start() { rc = pthread_mutex_init(&dacp_conversation_lock, &mta); if (rc) debug(1, "Error creating the DACP Conversation Lock Mutex Init"); - else - debug(1, "DACP Conversation Lock Mutex Init"); + // else + // debug(1, "DACP Conversation Lock Mutex Init"); rc = pthread_mutexattr_destroy(&mta); if (rc) From 3566cf0caf5eefb1cbabed14fc7e2c22074d6c01 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 18 Jan 2019 16:30:21 +0000 Subject: [PATCH 373/569] remove unnecessary file includes --- metadata_hub.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/metadata_hub.c b/metadata_hub.c index aacfb3a3..84cd8076 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -45,18 +45,6 @@ #include "dacp.h" #include "metadata_hub.h" -#ifdef CONFIG_MBEDTLS -#include -#endif - -#ifdef CONFIG_POLARSSL -#include -#endif - -#ifdef CONFIG_OPENSSL -#include -#endif - pthread_rwlock_t metadata_hub_re_lock = PTHREAD_RWLOCK_INITIALIZER; struct track_metadata_bundle *track_metadata; // used for a temporary track metadata store From 99fd6b85d05903024c0109fe3a0f39e58d55b3db Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 18 Jan 2019 16:31:36 +0000 Subject: [PATCH 374/569] make OpenSSL routines un-cancellable, in case there is a possible memory leak --- common.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/common.c b/common.c index 729bcacd..a1354621 100644 --- a/common.c +++ b/common.c @@ -394,6 +394,8 @@ uint8_t *base64_dec(char *input, int *outlen) { #ifdef CONFIG_OPENSSL char *base64_enc(uint8_t *input, int length) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); BIO *bmem, *b64; BUF_MEM *bptr; b64 = BIO_new(BIO_f_base64()); @@ -414,10 +416,13 @@ char *base64_enc(uint8_t *input, int length) { BIO_free_all(bmem); + pthread_setcancelstate(oldState, NULL); return buf; } uint8_t *base64_dec(char *input, int *outlen) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); BIO *bmem, *b64; int inlen = strlen(input); @@ -442,6 +447,7 @@ uint8_t *base64_dec(char *input, int *outlen) { BIO_free_all(b64); *outlen = nread; + pthread_setcancelstate(oldState, NULL); return buf; } #endif @@ -473,6 +479,8 @@ static char super_secret_key[] = #ifdef CONFIG_OPENSSL uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); RSA *rsa = NULL; if (!rsa) { BIO *bmem = BIO_new_mem_buf(super_secret_key, -1); @@ -491,6 +499,7 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { default: die("bad rsa mode"); } + pthread_setcancelstate(oldState, NULL); return out; } #endif From 61c870bac162844fa04da419189e785f32ce9d49 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 18 Jan 2019 16:32:31 +0000 Subject: [PATCH 375/569] make OpenSSL routines un-cancellable, in case there is a possible memory leak --- rtsp.c | 6 ++++++ shairport.c | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/rtsp.c b/rtsp.c index 4a0be9ba..5a1cbd05 100644 --- a/rtsp.c +++ b/rtsp.c @@ -2007,9 +2007,12 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { uint8_t digest_urp[16], digest_mu[16], digest_total[16]; + #ifdef CONFIG_OPENSSL MD5_CTX ctx; + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); MD5_Init(&ctx); MD5_Update(&ctx, username, strlen(username)); MD5_Update(&ctx, ":", 1); @@ -2022,6 +2025,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { MD5_Update(&ctx, ":", 1); MD5_Update(&ctx, uri, strlen(uri)); MD5_Final(digest_mu, &ctx); + pthread_setcancelstate(oldState, NULL); #endif #ifdef CONFIG_MBEDTLS @@ -2062,6 +2066,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { snprintf((char *)buf + 2 * i, 3, "%02x", digest_urp[i]); #ifdef CONFIG_OPENSSL + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); MD5_Init(&ctx); MD5_Update(&ctx, buf, 32); MD5_Update(&ctx, ":", 1); @@ -2071,6 +2076,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]); MD5_Update(&ctx, buf, 32); MD5_Final(digest_total, &ctx); + pthread_setcancelstate(oldState, NULL); #endif #ifdef CONFIG_MBEDTLS diff --git a/shairport.c b/shairport.c index 65c0bd68..a02d541e 100644 --- a/shairport.c +++ b/shairport.c @@ -1577,10 +1577,14 @@ int main(int argc, char **argv) { uint8_t ap_md5[16]; #ifdef CONFIG_OPENSSL + // can't imagine this is necessary, but here it is anyway + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable MD5_CTX ctx; MD5_Init(&ctx); MD5_Update(&ctx, config.service_name, strlen(config.service_name)); MD5_Final(ap_md5, &ctx); + pthread_setcancelstate(oldState, NULL); #endif #ifdef CONFIG_MBEDTLS From a40a48693f7805d9d227c943423e1d400fa4ae70 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 18 Jan 2019 16:33:32 +0000 Subject: [PATCH 376/569] make OpenSSL routines un-cancellable, in case there might be possible memory leaks on cancellation --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index dfeb1147..14b8f39c 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.3d46], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d47], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From a8639f6c40177da41ff923f64f08a9b767e1ed3f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 20 Jan 2019 22:15:02 +0000 Subject: [PATCH 377/569] Add an activity monitor -- can set an activity idle timeout. --- Makefile.am | 2 +- activity_monitor.c | 188 +++++++++++++++++++++++++++++++++++++++++++++ activity_monitor.h | 4 + common.h | 1 + player.c | 4 + shairport.c | 6 ++ 6 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 activity_monitor.c create mode 100644 activity_monitor.h diff --git a/Makefile.am b/Makefile.am index 7106f35a..c384807c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -8,7 +8,7 @@ bin_PROGRAMS = shairport-sync # See below for the flags for the test client program -shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c loudness.c +shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c loudness.c activity_monitor.c if BUILD_FOR_FREEBSD AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\" diff --git a/activity_monitor.c b/activity_monitor.c new file mode 100644 index 00000000..3393e95d --- /dev/null +++ b/activity_monitor.c @@ -0,0 +1,188 @@ +/* + * Activity Monitor + * + * Contains code to run an activity flag and associated timer + * A pthread implements a simple state machine with three states, + * "idle", "active" and "timing out". + * + * + * This file is part of Shairport Sync. + * Copyright (c) Mike Brady 2019 + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include "config.h" + +#include "common.h" +#include "activity_monitor.h" + +enum am_state {am_inactive, am_active, am_timing_out} state; +enum ps_state {ps_inactive, ps_active} player_state; + +pthread_t activity_monitor_thread; +pthread_mutex_t activity_monitor_mutex; +pthread_cond_t activity_monitor_cv; + +void activity_monitor_signify_activity(int active) { + pthread_mutex_lock(&activity_monitor_mutex); + player_state = active == 0 ? ps_inactive : ps_active; + pthread_cond_signal(&activity_monitor_cv); + pthread_mutex_unlock(&activity_monitor_mutex); +} + +void going_active() { + pthread_mutex_unlock(&activity_monitor_mutex); + debug(1, "activity_monitor: state transitioning to \"active\""); + pthread_mutex_lock(&activity_monitor_mutex); +} + +void going_inactive() { + pthread_mutex_unlock(&activity_monitor_mutex); + debug(1, "activity_monitor: state transitioning to \"inactive\""); + pthread_mutex_lock(&activity_monitor_mutex); +} + +void activity_thread_cleanup_handler(__attribute__((unused)) void *arg) { + debug(3, "activity_monitor: thread exit."); + pthread_cond_destroy(&activity_monitor_cv); + pthread_mutex_destroy(&activity_monitor_mutex); +} + +void *activity_monitor_thread_code(void *arg) { + int rc = pthread_mutex_init(&activity_monitor_mutex, NULL); + if (rc) + die("activity_monitor: error %d initialising activity_monitor_mutex.", rc); + +// set the flowcontrol condition variable to wait on a monotonic clock +#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD + pthread_condattr_t attr; + pthread_condattr_init(&attr); + pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it. + rc = pthread_cond_init(&activity_monitor_cv, &attr); + pthread_condattr_destroy(&attr); + +#endif +#ifdef COMPILE_FOR_OSX + rc = pthread_cond_init(&activity_monitor_cv, NULL); +#endif + if (rc) + die("activity_monitor: error %d initialising activity_monitor_cv."); + pthread_cleanup_push(activity_thread_cleanup_handler, arg); + + uint64_t sec; + uint64_t nsec; + int rc; + struct timespec time_for_wait; + + state = am_inactive; + player_state = ps_inactive; + + pthread_mutex_lock(&activity_monitor_mutex); + do { + switch (state) { + case am_inactive: + debug(1,"am_state: am_inactive"); + while (player_state != ps_active) + pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); + state = am_active; + going_active(); + break; + case am_active: + debug(1,"am_state: am_active"); + while (player_state != ps_inactive) + pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); + if (config.active_timeout == 0.0) { + state = am_inactive; + going_inactive(); + } else { + state = am_timing_out; + + uint64_t time_to_wait_for_wakeup_fp = (uint64_t)(config.active_timeout *1000000); // resolution of microseconds + time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp << 32; + time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp / 1000000; + +#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD + uint64_t time_of_wakeup_fp = get_absolute_time_in_fp() + time_to_wait_for_wakeup_fp; + sec = time_of_wakeup_fp >> 32; + nsec = ((time_of_wakeup_fp & 0xffffffff) * 1000000000) >> 32; + time_for_wait.tv_sec = sec; + time_for_wait.tv_nsec = nsec; +#endif +#ifdef COMPILE_FOR_OSX + sec = time_to_wait_for_wakeup_fp >> 32; + nsec = ((time_to_wait_for_wakeup_fp & 0xffffffff) * 1000000000) >> 32; + time_for_wait.tv_sec = sec; + time_for_wait.tv_nsec = nsec; +#endif + } + break; + case am_timing_out: + debug(1,"am_state: am_timing_out"); + rc = 0; + while ((player_state != ps_active) && (rc != ETIMEDOUT)) { +#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD + rc = pthread_cond_timedwait(&activity_monitor_cv, &activity_monitor_mutex, &time_for_wait); // this is a pthread cancellation point +#endif +#ifdef COMPILE_FOR_OSX + rc = pthread_cond_timedwait_relative_np(&activity_monitor_cv, &activity_monitor_mutex, &time_for_wait); +#endif + } + if (player_state == ps_active) + state = am_active; // player has gone active -- do nothing, because it's still active + else if (rc == ETIMEDOUT) { + state = am_inactive; + going_inactive(); + } else { + // activity monitor was woken up in the state am_timing_out, but not by a timeout and player is not in ps_active state + debug(1, "activity monitor was woken up in the state am_timing_out, but didn't change state"); + } + break; + default: + debug(1,"activity monitor in an illegal state!"); + state = am_inactive; + break; + } + } while (1); + pthread_mutex_unlock(&activity_monitor_mutex); + pthread_cleanup_pop(0); // should never happen + pthread_exit(NULL); +} + +void activity_monitor_start() { + debug(1,"activity_monitor_start"); + config.active_timeout = 5.5; // hack + pthread_create(&activity_monitor_thread, NULL, activity_monitor_thread_code, NULL); +} + +void activity_monitor_stop() { + debug(1,"activity_monitor_stop start..."); + pthread_cancel(activity_monitor_thread); + pthread_join(activity_monitor_thread,NULL); + debug(1,"activity_monitor_stop complete"); +} + diff --git a/activity_monitor.h b/activity_monitor.h new file mode 100644 index 00000000..9de33454 --- /dev/null +++ b/activity_monitor.h @@ -0,0 +1,4 @@ +#pragma once +void activity_monitor_start(); +void activity_monitor_stop(); +void activity_monitor_signify_activity(int active); // 0 means inactive, non-zero means active diff --git a/common.h b/common.h index c6d5ae3a..505c426b 100644 --- a/common.h +++ b/common.h @@ -177,6 +177,7 @@ typedef struct { double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any // fixed latency there might be in the audio path double audio_backend_silent_lead_in_time; // the length of the silence that should precede a play. + double active_timeout; // the amount of time from when play ends to when the system does into the "inactive". uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's // native range. enum sps_format_t output_format; diff --git a/player.c b/player.c index ba40bf5b..6548b381 100644 --- a/player.c +++ b/player.c @@ -89,6 +89,8 @@ #include "loudness.h" +#include "activity_monitor.h" + // default buffer size // needs to be a power of 2 because of the way BUFIDX(seqno) works //#define BUFFER_FRAMES 512 @@ -1486,9 +1488,11 @@ void player_thread_cleanup_handler(void *arg) { clear_reference_timestamp(conn); conn->rtp_running = 0; pthread_setcancelstate(oldState, NULL); + activity_monitor_signify_activity(0); // inactive } void *player_thread_func(void *arg) { + activity_monitor_signify_activity(1); // active rtsp_conn_info *conn = (rtsp_conn_info *)arg; // pthread_cleanup_push(player_thread_initial_cleanup_handler, arg); conn->packet_count = 0; diff --git a/shairport.c b/shairport.c index a02d541e..382536ec 100644 --- a/shairport.c +++ b/shairport.c @@ -63,6 +63,7 @@ #include "mdns.h" #include "rtp.h" #include "rtsp.h" +#include "activity_monitor.h" #if defined(CONFIG_DACP_CLIENT) #include "dacp.h" @@ -1160,6 +1161,9 @@ void main_cleanup_handler(__attribute__((unused)) void *arg) { #ifdef CONFIG_METADATA metadata_stop(); // close down the metadata pipe #endif + + activity_monitor_stop(0); + if (config.output->deinit) { debug(1, "Deinitialise the audio backend."); config.output->deinit(); @@ -1633,6 +1637,8 @@ int main(int argc, char **argv) { } #endif + activity_monitor_start(); + // daemon_log(LOG_INFO, "Successful Startup"); rtsp_listen_loop(); From 2e181f937f056374da0e5b9dd7e4dc07f9c262be Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 21 Jan 2019 18:20:39 +0000 Subject: [PATCH 378/569] add initial script-execution functionality to active mode. Make is possible to wait for start and stop scripts. --- activity_monitor.c | 60 +++++++++++++++++++++++++++++++--------------- common.h | 3 ++- player.c | 6 ++--- shairport.c | 20 +++++++++++++++- 4 files changed, 65 insertions(+), 24 deletions(-) diff --git a/activity_monitor.c b/activity_monitor.c index 3393e95d..aea75cd9 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -48,25 +48,46 @@ pthread_t activity_monitor_thread; pthread_mutex_t activity_monitor_mutex; pthread_cond_t activity_monitor_cv; +void going_active(int block) { + debug(1, "activity_monitor: state transitioning to \"active\" with%s blocking", block ? "" : "out"); + if (config.cmd_active_start) + command_execute(config.cmd_active_start, "", block); +} + +void going_inactive(int block) { + debug(1, "activity_monitor: state transitioning to \"inactive\" with%s blocking", block ? "" : "out"); + if (config.cmd_active_stop) + command_execute(config.cmd_active_stop, "", block); +} + void activity_monitor_signify_activity(int active) { pthread_mutex_lock(&activity_monitor_mutex); player_state = active == 0 ? ps_inactive : ps_active; - pthread_cond_signal(&activity_monitor_cv); + // Now, although we could simply let the state machine in the activity monitor thread + // look after eveything, we will change state here in two situations: + // 1. If the state machine is am_inactive and the player is ps_active + // we will change the state to am_active and execute the going_active() function. + // 2. If the state machine is am_active and the player is ps_inactive and + // the activity_idle_timeout is 0, then we will change the state to am_inactive and + // execute the going_inactive() function. + // + // The reason for all this is that we might want to perform the attached scripts + // and wait for them to complete before continuing. If they were perfomed in the + // activity monitor thread, then we coldn't wait for them to complete. + + // Thus, the only time the thread will execute a going_... function is when a non-zero + // timeout actually matures. + + if ((state == am_inactive) && (player_state == ps_active)) { + going_active(config.cmd_blocking); // note -- will be executed with the mutex locked, but that's okay + } else if ((state == am_active) && (player_state == ps_inactive) && (config.active_mode_timeout == 0.0)) { + going_inactive(config.cmd_blocking); // note -- will be executed with the mutex locked, but that's okay + } + + pthread_cond_signal(&activity_monitor_cv); pthread_mutex_unlock(&activity_monitor_mutex); } -void going_active() { - pthread_mutex_unlock(&activity_monitor_mutex); - debug(1, "activity_monitor: state transitioning to \"active\""); - pthread_mutex_lock(&activity_monitor_mutex); -} - -void going_inactive() { - pthread_mutex_unlock(&activity_monitor_mutex); - debug(1, "activity_monitor: state transitioning to \"inactive\""); - pthread_mutex_lock(&activity_monitor_mutex); -} - void activity_thread_cleanup_handler(__attribute__((unused)) void *arg) { debug(3, "activity_monitor: thread exit."); pthread_cond_destroy(&activity_monitor_cv); @@ -110,19 +131,19 @@ void *activity_monitor_thread_code(void *arg) { while (player_state != ps_active) pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); state = am_active; - going_active(); + // going_active(); // this is done in activity_monitor_signify_activity break; case am_active: debug(1,"am_state: am_active"); while (player_state != ps_inactive) pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); - if (config.active_timeout == 0.0) { + if (config.active_mode_timeout == 0.0) { state = am_inactive; - going_inactive(); + // going_inactive(); // this is done in activity_monitor_signify_activity } else { state = am_timing_out; - uint64_t time_to_wait_for_wakeup_fp = (uint64_t)(config.active_timeout *1000000); // resolution of microseconds + uint64_t time_to_wait_for_wakeup_fp = (uint64_t)(config.active_mode_timeout *1000000); // resolution of microseconds time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp << 32; time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp / 1000000; @@ -156,7 +177,9 @@ void *activity_monitor_thread_code(void *arg) { state = am_active; // player has gone active -- do nothing, because it's still active else if (rc == ETIMEDOUT) { state = am_inactive; - going_inactive(); + pthread_mutex_unlock(&activity_monitor_mutex); + going_inactive(0); // don't wait for completion -- it makes no sense + pthread_mutex_lock(&activity_monitor_mutex); } else { // activity monitor was woken up in the state am_timing_out, but not by a timeout and player is not in ps_active state debug(1, "activity monitor was woken up in the state am_timing_out, but didn't change state"); @@ -175,7 +198,6 @@ void *activity_monitor_thread_code(void *arg) { void activity_monitor_start() { debug(1,"activity_monitor_start"); - config.active_timeout = 5.5; // hack pthread_create(&activity_monitor_thread, NULL, activity_monitor_thread_code, NULL); } diff --git a/common.h b/common.h index 505c426b..29ec460d 100644 --- a/common.h +++ b/common.h @@ -152,6 +152,7 @@ typedef struct { int statistics_requested, use_negotiated_latencies; enum playback_mode_type playback_mode; char *cmd_start, *cmd_stop, *cmd_set_volume, *cmd_unfixable; + char *cmd_active_start, *cmd_active_stop; int cmd_blocking, cmd_start_returns_output; double tolerance; // allow this much drift before attempting to correct it enum stuffing_type packet_stuffing; @@ -177,7 +178,7 @@ typedef struct { double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any // fixed latency there might be in the audio path double audio_backend_silent_lead_in_time; // the length of the silence that should precede a play. - double active_timeout; // the amount of time from when play ends to when the system does into the "inactive". + double active_mode_timeout; // the amount of time from when play ends to when the system leaves into the "active" mode. uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's // native range. enum sps_format_t output_format; diff --git a/player.c b/player.c index 6548b381..0de214c4 100644 --- a/player.c +++ b/player.c @@ -1488,12 +1488,10 @@ void player_thread_cleanup_handler(void *arg) { clear_reference_timestamp(conn); conn->rtp_running = 0; pthread_setcancelstate(oldState, NULL); - activity_monitor_signify_activity(0); // inactive } void *player_thread_func(void *arg) { - activity_monitor_signify_activity(1); // active - rtsp_conn_info *conn = (rtsp_conn_info *)arg; + rtsp_conn_info *conn = (rtsp_conn_info *)arg; // pthread_cleanup_push(player_thread_initial_cleanup_handler, arg); conn->packet_count = 0; conn->packet_count_since_flush = 0; @@ -2802,6 +2800,7 @@ int player_play(rtsp_conn_info *conn) { if (config.buffer_start_fill > BUFFER_FRAMES) die("specified buffer starting fill %d > buffer size %d", config.buffer_start_fill, BUFFER_FRAMES); + activity_monitor_signify_activity(1); // active, and should be before play's command hook, command_start() command_start(); pthread_t *pt = malloc(sizeof(pthread_t)); if (pt == NULL) @@ -2850,6 +2849,7 @@ int player_stop(rtsp_conn_info *conn) { #endif // debuglev = dl; command_stop(); + activity_monitor_signify_activity(0); // inactive, and should be after command_stop() return 0; } else { debug(3, "Connection %d: player thread already deleted.", conn->connection_number); diff --git a/shairport.c b/shairport.c index 382536ec..8d43629e 100644 --- a/shairport.c +++ b/shairport.c @@ -331,6 +331,7 @@ int parse_options(int argc, char **argv) { // nothing else comes in first. config.fixedLatencyOffset = 11025; // this sounds like it works properly. config.diagnostic_drop_packet_fraction = 0.0; + config.active_mode_timeout = 10.0; #ifdef CONFIG_METADATA_HUB config.cover_art_cache_dir = "/tmp/shairport-sync/.cache/coverart"; @@ -722,7 +723,24 @@ int parse_options(int argc, char **argv) { config.cmd_stop = (char *)str; } - if (config_lookup_string(config.cfg, + if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_entering_active_mode", &str)) { + config.cmd_active_start = (char *)str; + } + + if (config_lookup_string(config.cfg, "sessioncontrol.run_this_after_exiting_active_mode", &str)) { + config.cmd_active_stop = (char *)str; + } + + if (config_lookup_float(config.cfg, "sessioncontrol.active_mode_timeout", &dvalue)) { + if (dvalue < 0.0) + warn("Invalid value \"%f\" for sessioncontrol.active_mode_timeout. It must be positive. The default of %f will be used instead.", + dvalue, config.active_mode_timeout); + else + config.active_mode_timeout = dvalue; + } + + + if (config_lookup_string(config.cfg, "sessioncontrol.run_this_if_an_unfixable_error_is_detected", &str)) { config.cmd_unfixable = (char *)str; } From 604ca8dee458a07075453c0fde2b8a505846e3f5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 21 Jan 2019 21:18:40 +0000 Subject: [PATCH 379/569] Hook up active mode stuff to metadata, add two four-char codes, improve external command calling. --- activity_monitor.c | 21 +++++++++++++++------ common.c | 38 ++++++-------------------------------- metadata_hub.c | 12 ++++++++++++ metadata_hub.h | 7 +++++++ rtsp.c | 4 +++- 5 files changed, 43 insertions(+), 39 deletions(-) diff --git a/activity_monitor.c b/activity_monitor.c index aea75cd9..53e3d7f1 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -39,6 +39,7 @@ #include "config.h" #include "common.h" +#include "rtsp.h" #include "activity_monitor.h" enum am_state {am_inactive, am_active, am_timing_out} state; @@ -49,15 +50,23 @@ pthread_mutex_t activity_monitor_mutex; pthread_cond_t activity_monitor_cv; void going_active(int block) { - debug(1, "activity_monitor: state transitioning to \"active\" with%s blocking", block ? "" : "out"); + // debug(1, "activity_monitor: state transitioning to \"active\" with%s blocking", block ? "" : "out"); if (config.cmd_active_start) command_execute(config.cmd_active_start, "", block); +#ifdef CONFIG_METADATA + debug(2, "abeg"); // active mode begin + send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points +#endif } void going_inactive(int block) { - debug(1, "activity_monitor: state transitioning to \"inactive\" with%s blocking", block ? "" : "out"); + // debug(1, "activity_monitor: state transitioning to \"inactive\" with%s blocking", block ? "" : "out"); if (config.cmd_active_stop) command_execute(config.cmd_active_stop, "", block); +#ifdef CONFIG_METADATA + debug(2, "aend"); // active mode end + send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points +#endif } void activity_monitor_signify_activity(int active) { @@ -127,14 +136,14 @@ void *activity_monitor_thread_code(void *arg) { do { switch (state) { case am_inactive: - debug(1,"am_state: am_inactive"); + // debug(1,"am_state: am_inactive"); while (player_state != ps_active) pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); state = am_active; // going_active(); // this is done in activity_monitor_signify_activity break; case am_active: - debug(1,"am_state: am_active"); + // debug(1,"am_state: am_active"); while (player_state != ps_inactive) pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); if (config.active_mode_timeout == 0.0) { @@ -163,7 +172,7 @@ void *activity_monitor_thread_code(void *arg) { } break; case am_timing_out: - debug(1,"am_state: am_timing_out"); + // debug(1,"am_state: am_timing_out"); rc = 0; while ((player_state != ps_active) && (rc != ETIMEDOUT)) { #ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD @@ -197,7 +206,7 @@ void *activity_monitor_thread_code(void *arg) { } void activity_monitor_start() { - debug(1,"activity_monitor_start"); + // debug(1,"activity_monitor_start"); pthread_create(&activity_monitor_thread, NULL, activity_monitor_thread_code, NULL); } diff --git a/common.c b/common.c index a1354621..2c2ede62 100644 --- a/common.c +++ b/common.c @@ -725,7 +725,8 @@ void command_start(void) { and if blocking is true, wait for process to finish */ pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */ - if (rc != pid) { + if ((rc != pid) && (errno != ECHILD)) { + // In this context, ECHILD means that the child process has already completed, I think! warn("Execution of on-start command returned an error."); debug(1, "on-start command %s finished with error %d", config.cmd_start, errno); } @@ -779,7 +780,8 @@ void command_execute(const char *command, const char *extra_argument, const int if (block) { /* pid!=0 means parent process and if blocking is true, wait for process to finish */ pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */ - if (rc != pid) { + if ((rc != pid) && (errno != ECHILD)) { + // In this context, ECHILD means that the child process has already completed, I think! warn("Execution of command \"%s\" returned an error.", full_command); debug(1, "Command \"%s\" finished with error %d", full_command, errno); } @@ -791,36 +793,8 @@ void command_execute(const char *command, const char *extra_argument, const int void command_stop(void) { // this has a cancellation point if waiting is enabled - if (config.cmd_stop) { - /*Spawn a child to run the program.*/ - pid_t pid = fork(); - if (pid == 0) { /* child process */ - int argC; - char **argV; - // debug(1,"on-stop command found."); - if (poptParseArgvString(config.cmd_stop, &argC, (const char ***)&argV) != - 0) // note that argV should be free()'d after use, but we expect this fork to exit - // eventually. - debug(1, "Can't decipher on-stop command arguments"); - else { - // debug(1,"Executing on-stop command %s",config.cmd_stop); - execv(argV[0], argV); - warn("Execution of on-stop command failed to start"); - debug(1, "Error executing on-stop command %s", config.cmd_stop); - exit(127); /* only if execv fails */ - } - } else { - if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for - process to finish */ - pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */ - if (rc != pid) { - warn("Execution of on-stop command returned an error."); - debug(1, "Stop command %s finished with error %d", config.cmd_stop, errno); - } - } - // debug(1,"Continue after on-stop command"); - } - } + if (config.cmd_stop) + command_execute(config.cmd_stop,"",config.cmd_blocking); } // this is for reading an unsigned 32 bit number, such as an RTP timestamp diff --git a/metadata_hub.c b/metadata_hub.c index 84cd8076..398ad35b 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -535,6 +535,18 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin break; // these could tell us about play / pause etc. but will only occur if metadata is enabled, so // we'll just ignore them + case 'abeg': { + metadata_hub_modify_prolog(); + int changed = (metadata_store.active_state != AM_ACTIVE); + metadata_store.active_state = AM_ACTIVE; + metadata_hub_modify_epilog(changed); + } break; + case 'aend': { + metadata_hub_modify_prolog(); + int changed = (metadata_store.active_state != AM_INACTIVE); + metadata_store.active_state = AM_INACTIVE; + metadata_hub_modify_epilog(changed); + } break; case 'pbeg': { metadata_hub_modify_prolog(); int changed = (metadata_store.player_state != PS_PLAYING); diff --git a/metadata_hub.h b/metadata_hub.h index 65e93a23..7acc6722 100644 --- a/metadata_hub.h +++ b/metadata_hub.h @@ -12,6 +12,11 @@ enum play_status_type { PS_PLAYING, } play_status_type; +enum active_mode_type { + AM_INACTIVE = 0, + AM_ACTIVE, +} active_mode_type; + enum shuffle_status_type { SS_NOT_AVAILABLE = 0, SS_OFF, @@ -77,6 +82,8 @@ typedef struct metadata_bundle { enum play_status_type player_state; // this is the state of the actual player itself, which can be a bit noisy. + enum active_mode_type + active_state; int speaker_volume; // this is the actual speaker volume, allowing for the main volume and the // speaker volume control diff --git a/rtsp.c b/rtsp.c index 5a1cbd05..2fcd3103 100644 --- a/rtsp.c +++ b/rtsp.c @@ -974,7 +974,7 @@ 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 with UDP ports Control: %d, Timing: %d and Audio: %d.", + debug(2, "Connection %d: SETUP with UDP ports Control: %d, Timing: %d and Audio: %d.", conn->connection_number, conn->local_control_port, conn->local_timing_port, conn->local_audio_port); @@ -1087,6 +1087,8 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req, // 'PICT' -- the payload is a picture, either a JPEG or a PNG. Check the // first few bytes to see // which. +// 'abeg' -- active mode entered. No arguments +// 'aend' -- active mode exited. No arguments // 'pbeg' -- play stream begin. No arguments // 'pend' -- play stream end. No arguments // 'pfls' -- play stream flush. No arguments From 2e4428538d9f77233de0b0b9f151339b4ef8f923 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 21 Jan 2019 21:43:55 +0000 Subject: [PATCH 380/569] Add an "active" mode that can time out after a period -- 10 seconds by default -- after play ends. --- activity_monitor.c | 189 ++++++++++++++++++++++++--------------------- audio.h | 3 +- audio_alsa.c | 30 +++---- audio_jack.c | 6 +- audio_soundio.c | 5 +- common.c | 38 ++++----- common.h | 11 +-- dacp.c | 22 ++++-- dbus-service.c | 6 +- mdns_dns_sd.c | 2 +- mdns_external.c | 2 +- metadata_hub.c | 2 +- metadata_hub.h | 3 +- player.c | 147 ++++++++++++++++++----------------- rtp.c | 17 ++-- rtsp.c | 50 +++++++----- shairport.c | 25 +++--- tinysvcmdns.c | 3 +- 18 files changed, 300 insertions(+), 261 deletions(-) diff --git a/activity_monitor.c b/activity_monitor.c index 53e3d7f1..6452a08e 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -1,11 +1,11 @@ /* * Activity Monitor - * + * * Contains code to run an activity flag and associated timer - * A pthread implements a simple state machine with three states, + * A pthread implements a simple state machine with three states, * "idle", "active" and "timing out". - * - * + * + * * This file is part of Shairport Sync. * Copyright (c) Mike Brady 2019 * All rights reserved. @@ -31,41 +31,43 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include #include #include +#include #include #include "config.h" +#include "activity_monitor.h" #include "common.h" #include "rtsp.h" -#include "activity_monitor.h" -enum am_state {am_inactive, am_active, am_timing_out} state; -enum ps_state {ps_inactive, ps_active} player_state; +enum am_state { am_inactive, am_active, am_timing_out } state; +enum ps_state { ps_inactive, ps_active } player_state; pthread_t activity_monitor_thread; pthread_mutex_t activity_monitor_mutex; pthread_cond_t activity_monitor_cv; void going_active(int block) { - // debug(1, "activity_monitor: state transitioning to \"active\" with%s blocking", block ? "" : "out"); + // debug(1, "activity_monitor: state transitioning to \"active\" with%s blocking", block ? "" : + // "out"); if (config.cmd_active_start) command_execute(config.cmd_active_start, "", block); #ifdef CONFIG_METADATA - debug(2, "abeg"); // active mode begin - send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points + debug(2, "abeg"); // active mode begin + send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points #endif } void going_inactive(int block) { - // debug(1, "activity_monitor: state transitioning to \"inactive\" with%s blocking", block ? "" : "out"); + // debug(1, "activity_monitor: state transitioning to \"inactive\" with%s blocking", block ? "" : + // "out"); if (config.cmd_active_stop) command_execute(config.cmd_active_stop, "", block); #ifdef CONFIG_METADATA - debug(2, "aend"); // active mode end - send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points + debug(2, "aend"); // active mode end + send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points #endif } @@ -83,17 +85,20 @@ void activity_monitor_signify_activity(int active) { // The reason for all this is that we might want to perform the attached scripts // and wait for them to complete before continuing. If they were perfomed in the // activity monitor thread, then we coldn't wait for them to complete. - + // Thus, the only time the thread will execute a going_... function is when a non-zero // timeout actually matures. - + if ((state == am_inactive) && (player_state == ps_active)) { - going_active(config.cmd_blocking); // note -- will be executed with the mutex locked, but that's okay - } else if ((state == am_active) && (player_state == ps_inactive) && (config.active_mode_timeout == 0.0)) { - going_inactive(config.cmd_blocking); // note -- will be executed with the mutex locked, but that's okay + going_active( + config.cmd_blocking); // note -- will be executed with the mutex locked, but that's okay + } else if ((state == am_active) && (player_state == ps_inactive) && + (config.active_mode_timeout == 0.0)) { + going_inactive( + config.cmd_blocking); // note -- will be executed with the mutex locked, but that's okay } - - pthread_cond_signal(&activity_monitor_cv); + + pthread_cond_signal(&activity_monitor_cv); pthread_mutex_unlock(&activity_monitor_mutex); } @@ -115,7 +120,7 @@ void *activity_monitor_thread_code(void *arg) { pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it. rc = pthread_cond_init(&activity_monitor_cv, &attr); pthread_condattr_destroy(&attr); - + #endif #ifdef COMPILE_FOR_OSX rc = pthread_cond_init(&activity_monitor_cv, NULL); @@ -123,84 +128,89 @@ void *activity_monitor_thread_code(void *arg) { if (rc) die("activity_monitor: error %d initialising activity_monitor_cv."); pthread_cleanup_push(activity_thread_cleanup_handler, arg); - + uint64_t sec; uint64_t nsec; int rc; struct timespec time_for_wait; - + state = am_inactive; player_state = ps_inactive; - + pthread_mutex_lock(&activity_monitor_mutex); do { switch (state) { - case am_inactive: - // debug(1,"am_state: am_inactive"); - while (player_state != ps_active) - pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); - state = am_active; - // going_active(); // this is done in activity_monitor_signify_activity + case am_inactive: + // debug(1,"am_state: am_inactive"); + while (player_state != ps_active) + pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); + state = am_active; + // going_active(); // this is done in activity_monitor_signify_activity break; - case am_active: - // debug(1,"am_state: am_active"); - while (player_state != ps_inactive) - pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); - if (config.active_mode_timeout == 0.0) { - state = am_inactive; - // going_inactive(); // this is done in activity_monitor_signify_activity - } else { - state = am_timing_out; - - uint64_t time_to_wait_for_wakeup_fp = (uint64_t)(config.active_mode_timeout *1000000); // resolution of microseconds - time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp << 32; - time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp / 1000000; - -#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD - uint64_t time_of_wakeup_fp = get_absolute_time_in_fp() + time_to_wait_for_wakeup_fp; - sec = time_of_wakeup_fp >> 32; - nsec = ((time_of_wakeup_fp & 0xffffffff) * 1000000000) >> 32; - time_for_wait.tv_sec = sec; - time_for_wait.tv_nsec = nsec; -#endif -#ifdef COMPILE_FOR_OSX - sec = time_to_wait_for_wakeup_fp >> 32; - nsec = ((time_to_wait_for_wakeup_fp & 0xffffffff) * 1000000000) >> 32; - time_for_wait.tv_sec = sec; - time_for_wait.tv_nsec = nsec; -#endif - } - break; - case am_timing_out: - // debug(1,"am_state: am_timing_out"); - rc = 0; - while ((player_state != ps_active) && (rc != ETIMEDOUT)) { -#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD - rc = pthread_cond_timedwait(&activity_monitor_cv, &activity_monitor_mutex, &time_for_wait); // this is a pthread cancellation point -#endif -#ifdef COMPILE_FOR_OSX - rc = pthread_cond_timedwait_relative_np(&activity_monitor_cv, &activity_monitor_mutex, &time_for_wait); -#endif - } - if (player_state == ps_active) - state = am_active; // player has gone active -- do nothing, because it's still active - else if (rc == ETIMEDOUT) { - state = am_inactive; - pthread_mutex_unlock(&activity_monitor_mutex); - going_inactive(0); // don't wait for completion -- it makes no sense - pthread_mutex_lock(&activity_monitor_mutex); - } else { - // activity monitor was woken up in the state am_timing_out, but not by a timeout and player is not in ps_active state - debug(1, "activity monitor was woken up in the state am_timing_out, but didn't change state"); - } - break; - default: - debug(1,"activity monitor in an illegal state!"); + case am_active: + // debug(1,"am_state: am_active"); + while (player_state != ps_inactive) + pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); + if (config.active_mode_timeout == 0.0) { state = am_inactive; + // going_inactive(); // this is done in activity_monitor_signify_activity + } else { + state = am_timing_out; + + uint64_t time_to_wait_for_wakeup_fp = + (uint64_t)(config.active_mode_timeout * 1000000); // resolution of microseconds + time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp << 32; + time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp / 1000000; + +#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD + uint64_t time_of_wakeup_fp = get_absolute_time_in_fp() + time_to_wait_for_wakeup_fp; + sec = time_of_wakeup_fp >> 32; + nsec = ((time_of_wakeup_fp & 0xffffffff) * 1000000000) >> 32; + time_for_wait.tv_sec = sec; + time_for_wait.tv_nsec = nsec; +#endif +#ifdef COMPILE_FOR_OSX + sec = time_to_wait_for_wakeup_fp >> 32; + nsec = ((time_to_wait_for_wakeup_fp & 0xffffffff) * 1000000000) >> 32; + time_for_wait.tv_sec = sec; + time_for_wait.tv_nsec = nsec; +#endif + } + break; + case am_timing_out: + // debug(1,"am_state: am_timing_out"); + rc = 0; + while ((player_state != ps_active) && (rc != ETIMEDOUT)) { +#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD + rc = pthread_cond_timedwait(&activity_monitor_cv, &activity_monitor_mutex, + &time_for_wait); // this is a pthread cancellation point +#endif +#ifdef COMPILE_FOR_OSX + rc = pthread_cond_timedwait_relative_np(&activity_monitor_cv, &activity_monitor_mutex, + &time_for_wait); +#endif + } + if (player_state == ps_active) + state = am_active; // player has gone active -- do nothing, because it's still active + else if (rc == ETIMEDOUT) { + state = am_inactive; + pthread_mutex_unlock(&activity_monitor_mutex); + going_inactive(0); // don't wait for completion -- it makes no sense + pthread_mutex_lock(&activity_monitor_mutex); + } else { + // activity monitor was woken up in the state am_timing_out, but not by a timeout and player + // is not in ps_active state + debug(1, + "activity monitor was woken up in the state am_timing_out, but didn't change state"); + } + break; + default: + debug(1, "activity monitor in an illegal state!"); + state = am_inactive; break; } } while (1); - pthread_mutex_unlock(&activity_monitor_mutex); + pthread_mutex_unlock(&activity_monitor_mutex); pthread_cleanup_pop(0); // should never happen pthread_exit(NULL); } @@ -211,9 +221,8 @@ void activity_monitor_start() { } void activity_monitor_stop() { - debug(1,"activity_monitor_stop start..."); + debug(1, "activity_monitor_stop start..."); pthread_cancel(activity_monitor_thread); - pthread_join(activity_monitor_thread,NULL); - debug(1,"activity_monitor_stop complete"); + pthread_join(activity_monitor_thread, NULL); + debug(1, "activity_monitor_stop complete"); } - diff --git a/audio.h b/audio.h index b8ecf958..1767e416 100644 --- a/audio.h +++ b/audio.h @@ -22,7 +22,8 @@ typedef struct { void (*start)(int sample_rate, int sample_format); // block of samples - int (*preflight)(void *buf, int samples); // say you are about to send these samples (before interpolation) + int (*preflight)(void *buf, + int samples); // say you are about to send these samples (before interpolation) int (*play)(void *buf, int samples); void (*stop)(void); diff --git a/audio_alsa.c b/audio_alsa.c index 49500038..83f2086d 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -469,8 +469,9 @@ int actual_open_alsa_device(void) { buffer_size); } */ - debug(1, "The alsa buffer is smaller (%lu bytes) than the desired backend buffer " - "length (%ld) you have chosen.", + debug(1, + "The alsa buffer is smaller (%lu bytes) than the desired backend buffer " + "length (%ld) you have chosen.", actual_buffer_length, config.audio_backend_buffer_desired_length); } @@ -633,13 +634,15 @@ static int init(int argc, char **argv) { config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.120; // below this, basic interpolation will be used to save time. config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem - config.audio_backend_silence_threshold = 0.040; //start sending silent frames if the delay goes below this time - config.audio_backend_silence_scan_interval = 0.004; //check silence threshold this often + config.audio_backend_silence_threshold = + 0.040; // start sending silent frames if the delay goes below this time + config.audio_backend_silence_scan_interval = 0.004; // check silence threshold this often stall_monitor_error_threshold = (uint64_t)1000000 * config.alsa_maximum_stall_time; // stall time max to microseconds; stall_monitor_error_threshold = (stall_monitor_error_threshold << 32) / 1000000; // now in fp form - debug(1, "stall_monitor_error_threshold is 0x%" PRIx64 ", with alsa_maximum_stall_time of %f sec.", + debug(1, + "stall_monitor_error_threshold is 0x%" PRIx64 ", with alsa_maximum_stall_time of %f sec.", stall_monitor_error_threshold, config.alsa_maximum_stall_time); stall_monitor_start_time = 0; @@ -1047,9 +1050,10 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) || ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) { - debug(2, "DAC seems to have stalled with time_now_ns: %" PRIX64 - ", update_timestamp_ns: %" PRIX64 ", stall_monitor_start_time %" PRIX64 - ", stall_monitor_error_threshold %" PRIX64 ".", + debug(2, + "DAC seems to have stalled with time_now_ns: %" PRIX64 + ", update_timestamp_ns: %" PRIX64 ", stall_monitor_start_time %" PRIX64 + ", stall_monitor_error_threshold %" PRIX64 ".", time_now_ns, update_timestamp_ns, stall_monitor_start_time, stall_monitor_error_threshold); ret = sps_extra_code_output_stalled; @@ -1264,8 +1268,8 @@ static void flush(void) { int preflight(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) { uint64_t time_now = get_absolute_time_in_fp(); // this is to regulate access by the silence filler thread - uint64_t standoff_time = 60; // milliseconds - standoff_time = (standoff_time << 32)/1000; + uint64_t standoff_time = 60; // milliseconds + standoff_time = (standoff_time << 32) / 1000; most_recent_write_time = time_now + standoff_time; return 0; } @@ -1467,7 +1471,7 @@ void *alsa_buffer_monitor_thread_code(void *arg) { int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000); long buffer_size_threshold = (long)(config.audio_backend_silence_threshold * desired_sample_rate); - + uint64_t sleep_time_in_fp = sleep_time_ms; sleep_time_in_fp = sleep_time_in_fp << 32; sleep_time_in_fp = sleep_time_in_fp / 1000; @@ -1489,8 +1493,8 @@ void *alsa_buffer_monitor_thread_code(void *arg) { if ((most_recent_write_time == 0) || (present_time > most_recent_write_time)) { -// ((present_time > most_recent_write_time) && -// ((present_time - most_recent_write_time) > (sleep_time_in_fp)))) { + // ((present_time > most_recent_write_time) && + // ((present_time - most_recent_write_time) > (sleep_time_in_fp)))) { reply = delay(&buffer_size); if (reply != 0) { buffer_size = 0; diff --git a/audio_jack.c b/audio_jack.c index 90f08ac4..9a584385 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -27,7 +27,6 @@ #include #include #include -#include #include #include @@ -336,8 +335,9 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a * we should try. */ if (config_lookup_int(config.cfg, "jack.auto_client_open_interval", &value)) { if ((value < 0) || (value > 300)) - debug(1, "Invalid jack auto_client_open_interval \"%sd\". It should be between 0 and 300, " - "default is %d.", + debug(1, + "Invalid jack auto_client_open_interval \"%sd\". It should be between 0 and 300, " + "default is %d.", value, config.jack_auto_client_open_interval); else config.jack_auto_client_open_interval = value; diff --git a/audio_soundio.c b/audio_soundio.c index 0449895b..8942554d 100644 --- a/audio_soundio.c +++ b/audio_soundio.c @@ -27,8 +27,9 @@ static void write_callback(struct SoundIoOutStream *outstream, int frame_count_m int fill_bytes = soundio_ring_buffer_fill_count(ring_buffer); int fill_count = fill_bytes / outstream->bytes_per_frame; - debug(3, "[--->>] frame_count_min: %d , frame_count_max: %d , fill_bytes: %d , fill_count: %d , " - "outstream->bytes_per_frame: %d", + debug(3, + "[--->>] frame_count_min: %d , frame_count_max: %d , fill_bytes: %d , fill_count: %d , " + "outstream->bytes_per_frame: %d", frame_count_min, frame_count_max, fill_bytes, fill_count, outstream->bytes_per_frame); if (frame_count_min > fill_count) { diff --git a/common.c b/common.c index 2c2ede62..8430c29d 100644 --- a/common.c +++ b/common.c @@ -114,7 +114,8 @@ uint16_t nextFreeUDPPort() { if (UDPPortIndex == 0) UDPPortIndex = config.udp_port_base; else if (UDPPortIndex == (config.udp_port_base + config.udp_port_range - 1)) - UDPPortIndex = config.udp_port_base + 3; // avoid wrapping back to the first three, as they can be assigned by resetFreeUDPPort without checking + UDPPortIndex = config.udp_port_base + 3; // avoid wrapping back to the first three, as they can + // be assigned by resetFreeUDPPort without checking else UDPPortIndex++; return UDPPortIndex; @@ -394,7 +395,7 @@ uint8_t *base64_dec(char *input, int *outlen) { #ifdef CONFIG_OPENSSL char *base64_enc(uint8_t *input, int length) { - int oldState; + int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); BIO *bmem, *b64; BUF_MEM *bptr; @@ -421,7 +422,7 @@ char *base64_enc(uint8_t *input, int length) { } uint8_t *base64_dec(char *input, int *outlen) { - int oldState; + int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); BIO *bmem, *b64; int inlen = strlen(input); @@ -479,7 +480,7 @@ static char super_secret_key[] = #ifdef CONFIG_OPENSSL uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { - int oldState; + int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); RSA *rsa = NULL; if (!rsa) { @@ -794,7 +795,7 @@ void command_execute(const char *command, const char *extra_argument, const int void command_stop(void) { // this has a cancellation point if waiting is enabled if (config.cmd_stop) - command_execute(config.cmd_stop,"",config.cmd_blocking); + command_execute(config.cmd_stop, "", config.cmd_blocking); } // this is for reading an unsigned 32 bit number, such as an RTP timestamp @@ -829,17 +830,17 @@ double flat_vol2attn(double vol, long max_db, long min_db) { double vol2attn(double vol, long max_db, long min_db) { -// We use a little coordinate geometry to build a transfer function from the volume passed in to -// the device's dynamic range. (See the diagram in the documents folder.) The x axis is the -// "volume in" which will be from -30 to 0. The y axis will be the "volume out" which will be from -// the bottom of the range to the top. We build the transfer function from one or more lines. We -// characterise each line with two numbers: the first is where on x the line starts when y=0 (x -// can be from 0 to -30); the second is where on y the line stops when when x is -30. thus, if the -// line was characterised as {0,-30}, it would be an identity transfer. Assuming, for example, a -// dynamic range of lv=-60 to hv=0 Typically we'll use three lines -- a three order transfer -// function First: {0,30} giving a gentle slope -- the 30 comes from half the dynamic range -// Second: {-5,-30-(lv+30)/2} giving a faster slope from y=0 at x=-12 to y=-42.5 at x=-30 -// Third: {-17,lv} giving a fast slope from y=0 at x=-19 to y=-60 at x=-30 + // We use a little coordinate geometry to build a transfer function from the volume passed in to + // the device's dynamic range. (See the diagram in the documents folder.) The x axis is the + // "volume in" which will be from -30 to 0. The y axis will be the "volume out" which will be from + // the bottom of the range to the top. We build the transfer function from one or more lines. We + // characterise each line with two numbers: the first is where on x the line starts when y=0 (x + // can be from 0 to -30); the second is where on y the line stops when when x is -30. thus, if the + // line was characterised as {0,-30}, it would be an identity transfer. Assuming, for example, a + // dynamic range of lv=-60 to hv=0 Typically we'll use three lines -- a three order transfer + // function First: {0,30} giving a gentle slope -- the 30 comes from half the dynamic range + // Second: {-5,-30-(lv+30)/2} giving a faster slope from y=0 at x=-12 to y=-42.5 at x=-30 + // Third: {-17,lv} giving a fast slope from y=0 at x=-19 to y=-60 at x=-30 #define order 3 @@ -1151,8 +1152,9 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, et = (et * 1000000) >> 32; // microseconds char errstr[1000]; if (r == ETIMEDOUT) - debug(debuglevel, "timed out waiting for a mutex, having waiting %f seconds, with a maximum " - "waiting time of %d microseconds. \"%s\".", + debug(debuglevel, + "timed out waiting for a mutex, having waiting %f seconds, with a maximum " + "waiting time of %d microseconds. \"%s\".", (1.0 * et) / 1000000, dally_time, debugmessage); else debug(debuglevel, "error %d: \"%s\" waiting for a mutex: \"%s\".", r, diff --git a/common.h b/common.h index 29ec460d..c1235b71 100644 --- a/common.h +++ b/common.h @@ -172,15 +172,16 @@ typedef struct { double audio_backend_buffer_interpolation_threshold_in_seconds; // below this, soxr interpolation // will not occur -- it'll be // basic interpolation instead. - double audio_backend_silence_threshold; // below this, silence will be added to the output buffer + double audio_backend_silence_threshold; // below this, silence will be added to the output buffer double audio_backend_silence_scan_interval; // check the threshold this often - + double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any // fixed latency there might be in the audio path double audio_backend_silent_lead_in_time; // the length of the silence that should precede a play. - double active_mode_timeout; // the amount of time from when play ends to when the system leaves into the "active" mode. - uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's - // native range. + double active_mode_timeout; // the amount of time from when play ends to when the system leaves + // into the "active" mode. + uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's + // native range. enum sps_format_t output_format; enum volume_control_profile_type volume_control_profile; int output_rate; diff --git a/dacp.c b/dacp.c index a4a04df1..bd84f3c7 100644 --- a/dacp.c +++ b/dacp.c @@ -111,7 +111,10 @@ static void response_code(void *opaque, int code) { } static const struct http_funcs responseFuncs = { - response_realloc, response_body, response_header, response_code, + response_realloc, + response_body, + response_header, + response_code, }; // static pthread_mutex_t dacp_conversation_lock = PTHREAD_MUTEX_INITIALIZER; @@ -324,9 +327,10 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // debug(1,"Sent command\"%s\" with a response body of size %d.",command,response.size); // debug(1,"dacp_conversation_lock released."); } else { - debug(3, "dacp_send_command: could not acquire a lock on the dacp transmit/receive section " - "when attempting to " - "send the command \"%s\". Possible timeout?", + debug(3, + "dacp_send_command: could not acquire a lock on the dacp transmit/receive section " + "when attempting to " + "send the command \"%s\". Possible timeout?", command); response.code = 494; // This client is already busy } @@ -423,8 +427,9 @@ void set_dacp_server_information(rtsp_conn_info *conn) { void dacp_monitor_port_update_callback(char *dacp_id, uint16_t port) { debug_mutex_lock(&dacp_server_information_lock, 500000, 2); - debug(3, "dacp_monitor_port_update_callback with Remote ID \"%s\", target ID \"%s\" and port " - "number %d.", + debug(3, + "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; @@ -472,8 +477,9 @@ 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(2, "setting dacp_server_active and advanced_dacp_server_active to 0 with an update " - "flag value of %d", + debug(2, + "setting dacp_server_active and advanced_dacp_server_active to 0 with an update " + "flag value of %d", ch); metadata_hub_modify_epilog(ch); while (dacp_server.scan_enable == 0) { diff --git a/dbus-service.c b/dbus-service.c index 9ae1d0aa..59fdd2c0 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -379,7 +379,7 @@ gboolean notify_verbosity_callback(ShairportSyncDiagnostics *skeleton, } gboolean notify_disable_standby_callback(ShairportSync *skeleton, - __attribute__((unused)) gpointer user_data) { + __attribute__((unused)) gpointer user_data) { // debug(1, "\"notify_disable_standby_callback\" called."); if (shairport_sync_get_disable_standby(skeleton)) { debug(1, ">> activating disable standby"); @@ -624,9 +624,9 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name G_CALLBACK(notify_alacdecoder_callback), NULL); g_signal_connect(shairportSyncSkeleton, "notify::volume-control-profile", G_CALLBACK(notify_volume_control_profile_callback), NULL); - g_signal_connect(shairportSyncSkeleton, "notify::disable-standby", + g_signal_connect(shairportSyncSkeleton, "notify::disable-standby", G_CALLBACK(notify_disable_standby_callback), NULL); -g_signal_connect(shairportSyncSkeleton, "notify::loudness-filter-active", + g_signal_connect(shairportSyncSkeleton, "notify::loudness-filter-active", G_CALLBACK(notify_loudness_filter_active_callback), NULL); g_signal_connect(shairportSyncSkeleton, "notify::loudness-threshold", G_CALLBACK(notify_loudness_threshold_callback), NULL); diff --git a/mdns_dns_sd.c b/mdns_dns_sd.c index 95bae443..cf178f1d 100644 --- a/mdns_dns_sd.c +++ b/mdns_dns_sd.c @@ -24,8 +24,8 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include "mdns.h" #include "common.h" +#include "mdns.h" #include #include #include diff --git a/mdns_external.c b/mdns_external.c index d3879ff5..8c0ab262 100644 --- a/mdns_external.c +++ b/mdns_external.c @@ -24,8 +24,8 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include "mdns.h" #include "common.h" +#include "mdns.h" #include #include #include diff --git a/metadata_hub.c b/metadata_hub.c index 398ad35b..17b221a8 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -205,7 +205,7 @@ char *metadata_write_image_file(const char *buf, int len) { char *path = NULL; // this will be what is returned uint8_t img_md5[16]; -// uint8_t ap_md5[16]; + // uint8_t ap_md5[16]; #ifdef CONFIG_SSL MD5_CTX ctx; diff --git a/metadata_hub.h b/metadata_hub.h index 7acc6722..9406851f 100644 --- a/metadata_hub.h +++ b/metadata_hub.h @@ -82,8 +82,7 @@ typedef struct metadata_bundle { enum play_status_type player_state; // this is the state of the actual player itself, which can be a bit noisy. - enum active_mode_type - active_state; + enum active_mode_type active_state; int speaker_volume; // this is the actual speaker volume, allowing for the main volume and the // speaker volume control diff --git a/player.c b/player.c index 0de214c4..ca4b00df 100644 --- a/player.c +++ b/player.c @@ -285,16 +285,18 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_co } if (outsize > toutsize) { - debug(2, "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- " - "truncated, but buffer overflow possible! Encrypted = %d.", + debug(2, + "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- " + "truncated, but buffer overflow possible! Encrypted = %d.", outsize, toutsize, conn->stream.encrypted); reply = -1; // output packet is the wrong size } *destlen = outsize / conn->input_bytes_per_frame; if ((outsize % conn->input_bytes_per_frame) != 0) - debug(1, "Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) " - "and the audio frame size (%d).", + debug(1, + "Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) " + "and the audio frame size (%d).", *destlen, outsize, conn->input_bytes_per_frame); return reply; } @@ -454,9 +456,10 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in if ((conn->flush_rtp_timestamp != 0) && (actual_timestamp != conn->flush_rtp_timestamp) && (modulo_32_offset(actual_timestamp, conn->flush_rtp_timestamp) < conn->input_rate * 10)) { // if it's less than 10 seconds - debug(3, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %" PRIu32 - ", flushing to " - "timestamp: %" PRIu32 ".", + debug(3, + "Dropping flushed packet in player_put_packet, seqno %u, timestamp %" PRIu32 + ", flushing to " + "timestamp: %" PRIu32 ".", seqno, actual_timestamp, conn->flush_rtp_timestamp); conn->initial_reference_time = 0; conn->initial_reference_timestamp = 0; @@ -849,9 +852,10 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { (curframe->given_timestamp != conn->flush_rtp_timestamp) && (modulo_32_offset(curframe->given_timestamp, conn->flush_rtp_timestamp) < conn->input_rate * 10)) { // if it's less than ten seconds - debug(3, "Dropping flushed packet in buffer_get_frame, seqno %u, timestamp %" PRIu32 - ", flushing to " - "timestamp: %" PRIu32 ".", + debug(3, + "Dropping flushed packet in buffer_get_frame, seqno %u, timestamp %" PRIu32 + ", flushing to " + "timestamp: %" PRIu32 ".", curframe->sequence_number, curframe->given_timestamp, conn->flush_rtp_timestamp); curframe->ready = 0; curframe->resend_level = 0; @@ -934,8 +938,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { frame_to_local_time(conn->first_packet_timestamp + conn->latency + (uint32_t)(config.audio_backend_latency_offset * conn->input_rate), // this will go modulo 2^32 - &should_be_time, - conn); + &should_be_time, conn); conn->first_packet_time_to_play = should_be_time; @@ -956,8 +959,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { frame_to_local_time(conn->first_packet_timestamp + conn->latency + (uint32_t)(config.audio_backend_latency_offset * conn->input_rate), // this should go modulo 2^32 - &should_be_time, - conn); + &should_be_time, conn); conn->first_packet_time_to_play = should_be_time; @@ -1028,8 +1030,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // this might happen if a big clock adjustment was made at just the wrong // time. - debug(1, "Run a bit past the exact start time by %" PRId64 - " frames with a DAC delay of %ld frames.", + debug(1, + "Run a bit past the exact start time by %" PRId64 + " frames with a DAC delay of %ld frames.", -exact_frame_gap, dac_delay); if (config.output->flush) config.output->flush(); @@ -1187,8 +1190,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { (uint32_t)(config.audio_backend_latency_offset * conn->input_rate) - (uint32_t)(config.audio_backend_buffer_desired_length * conn->input_rate), // this will go modulo 2^32 - &time_to_play, - conn); + &time_to_play, conn); if (local_time_now >= time_to_play) { do_wait = 0; @@ -1491,7 +1493,7 @@ void player_thread_cleanup_handler(void *arg) { } void *player_thread_func(void *arg) { - rtsp_conn_info *conn = (rtsp_conn_info *)arg; + rtsp_conn_info *conn = (rtsp_conn_info *)arg; // pthread_cleanup_push(player_thread_initial_cleanup_handler, arg); conn->packet_count = 0; conn->packet_count_since_flush = 0; @@ -1667,17 +1669,17 @@ void *player_thread_func(void *arg) { // if ((input_rate!=config.output_rate) || (input_bit_depth!=output_bit_depth)) { // debug(1,"Define tbuf of length // %d.",output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); - conn->tbuf = - malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + - conn->max_frame_size_change)); + conn->tbuf = malloc( + sizeof(int32_t) * 2 * + (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change)); if (conn->tbuf == NULL) die("Failed to allocate memory for the transition buffer."); // initialise this, because soxr stuffing might be chosen later - conn->sbuf = - malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + - conn->max_frame_size_change)); + conn->sbuf = malloc( + sizeof(int32_t) * 2 * + (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change)); if (conn->sbuf == NULL) die("Failed to allocate memory for the sbuf buffer."); @@ -1965,8 +1967,9 @@ void *player_thread_func(void *arg) { SUCCESSOR(conn->last_seqno_read); // int32_t from seq_t, i.e. uint16_t, so okay. if (inframe->sequence_number != conn->last_seqno_read) { // seq_t, ei.e. uint16_t and int32_t, so okay - debug(2, "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u " - "and ab_write: %u.", + debug(2, + "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u " + "and ab_write: %u.", conn->last_seqno_read, inframe->sequence_number, conn->ab_read, conn->ab_write); conn->last_seqno_read = inframe->sequence_number; // reset warning... } @@ -2191,7 +2194,7 @@ void *player_thread_func(void *arg) { #ifdef CONFIG_CONVOLUTION || config.convolution #endif - ) { + ) { int32_t *tbuf32 = (int32_t *)conn->tbuf; float fbuf_l[inbuflength]; float fbuf_r[inbuflength]; @@ -2429,42 +2432,41 @@ void *player_thread_func(void *arg) { if ((config.output->delay)) { if (config.no_sync == 0) { - inform("%*.2f," /* Sync error in milliseconds */ - "%*.1f," /* net correction in ppm */ - "%*.1f," /* corrections in ppm */ - "%*d," /* total packets */ - "%*" PRIu64 "," /* missing packets */ - "%*" PRIu64 "," /* late packets */ - "%*" PRIu64 "," /* too late packets */ - "%*" PRIu64 "," /* resend requests */ - "%*" PRId64 "," /* min DAC queue size */ - "%*" PRId32 "," /* min buffer occupancy */ - "%*" PRId32 "," /* max buffer occupancy */ - "%*.2f," /* source nominal frame rate */ - "%*.2f," /* source actual (average) frame rate */ - "%*.2f," /* output frame rate */ - "%*.2f," /* source clock drift */ - "%*d," /* source clock drift sample count */ - "%*.2f", /* rough calculated correction in ppm */ - 10, - 1000 * moving_average_sync_error / config.output_rate, 10, - moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), - 10, moving_average_insertions_plus_deletions * 1000000 / - (352 * conn->output_sample_ratio), - 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, - conn->too_late_packets, 7, conn->resend_requests, 7, - minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, - maximum_buffer_occupancy, 11, conn->remote_frame_rate, 11, - conn->input_frame_rate, 11, conn->frame_rate, 10, - (conn->local_to_remote_time_gradient - 1.0) * 1000000, 6, - conn->local_to_remote_time_gradient_sample_count, 10, - (conn->frame_rate > 0.0) - ? ((conn->frame_rate - - conn->remote_frame_rate * conn->output_sample_ratio * - conn->local_to_remote_time_gradient) * - 1000000) / - conn->frame_rate - : 0.0); + inform( + "%*.2f," /* Sync error in milliseconds */ + "%*.1f," /* net correction in ppm */ + "%*.1f," /* corrections in ppm */ + "%*d," /* total packets */ + "%*" PRIu64 "," /* missing packets */ + "%*" PRIu64 "," /* late packets */ + "%*" PRIu64 "," /* too late packets */ + "%*" PRIu64 "," /* resend requests */ + "%*" PRId64 "," /* min DAC queue size */ + "%*" PRId32 "," /* min buffer occupancy */ + "%*" PRId32 "," /* max buffer occupancy */ + "%*.2f," /* source nominal frame rate */ + "%*.2f," /* source actual (average) frame rate */ + "%*.2f," /* output frame rate */ + "%*.2f," /* source clock drift */ + "%*d," /* source clock drift sample count */ + "%*.2f", /* rough calculated correction in ppm */ + 10, 1000 * moving_average_sync_error / config.output_rate, 10, + moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), 10, + moving_average_insertions_plus_deletions * 1000000 / + (352 * conn->output_sample_ratio), + 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, + conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, + 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy, 11, + conn->remote_frame_rate, 11, conn->input_frame_rate, 11, conn->frame_rate, 10, + (conn->local_to_remote_time_gradient - 1.0) * 1000000, 6, + conn->local_to_remote_time_gradient_sample_count, 10, + (conn->frame_rate > 0.0) + ? ((conn->frame_rate - conn->remote_frame_rate * + conn->output_sample_ratio * + conn->local_to_remote_time_gradient) * + 1000000) / + conn->frame_rate + : 0.0); } else { inform("%*.2f," /* Sync error in milliseconds */ "%*d," /* total packets */ @@ -2479,10 +2481,9 @@ void *player_thread_func(void *arg) { "%*.2f," /* source actual (average) frame rate */ "%*.2f," /* source clock drift */ "%*d", /* source clock drift sample count */ - 10, - 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, - conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, - conn->resend_requests, 7, minimum_dac_queue_size, 5, + 10, 1000 * moving_average_sync_error / config.output_rate, 12, play_number, + 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, + 7, conn->resend_requests, 7, minimum_dac_queue_size, 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, @@ -2501,10 +2502,9 @@ void *player_thread_func(void *arg) { "%*.2f," /* source actual (average) frame rate */ "%*.2f," /* source clock drift */ "%*d", /* source clock drift sample count */ - 10, - 1000 * moving_average_sync_error / config.output_rate, 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, + 10, 1000 * moving_average_sync_error / config.output_rate, 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, @@ -2800,7 +2800,8 @@ int player_play(rtsp_conn_info *conn) { if (config.buffer_start_fill > BUFFER_FRAMES) die("specified buffer starting fill %d > buffer size %d", config.buffer_start_fill, BUFFER_FRAMES); - activity_monitor_signify_activity(1); // active, and should be before play's command hook, command_start() + activity_monitor_signify_activity( + 1); // active, and should be before play's command hook, command_start() command_start(); pthread_t *pt = malloc(sizeof(pthread_t)); if (pt == NULL) diff --git a/rtp.c b/rtp.c index 157bc77a..a1ba6794 100644 --- a/rtp.c +++ b/rtp.c @@ -137,8 +137,9 @@ void *rtp_audio_receiver(void *arg) { stat_mean += stat_delta / stat_n; stat_M2 += stat_delta * (time_interval_us - stat_mean); if (stat_n % 2500 == 0) { - debug(2, "Packet reception interval stats: mean, standard deviation and max for the last " - "2,500 packets in microseconds: %10.1f, %10.1f, %10.1f.", + debug(2, + "Packet reception interval stats: mean, standard deviation and max for the last " + "2,500 packets in microseconds: %10.1f, %10.1f, %10.1f.", stat_mean, sqrtf(stat_M2 / (stat_n - 1)), longest_packet_time_interval_us); stat_n = 0; stat_mean = 0.0; @@ -368,9 +369,10 @@ void *rtp_control_receiver(void *arg) { if (la != conn->latency) { conn->latency = la; - debug(3, "New latency detected: %" PRIu32 ", sync latency: %" PRIu32 - ", minimum latency: %" PRIu32 ", maximum " - "latency: %" PRIu32 ", fixed offset: %" PRIu32 ".", + debug(3, + "New latency detected: %" PRIu32 ", sync latency: %" PRIu32 + ", minimum latency: %" PRIu32 ", maximum " + "latency: %" PRIu32 ", fixed offset: %" PRIu32 ".", la, sync_rtp_timestamp - rtp_timestamp_less_latency, conn->minimum_latency, conn->maximum_latency, config.fixedLatencyOffset); } @@ -1226,8 +1228,9 @@ void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) { (struct sockaddr *)&conn->rtp_client_control_socket, msgsize) == -1) { char em[1024]; strerror_r(errno, em, sizeof(em)); - debug(1, "Error %d using sendto to an audio socket: \"%s\". Backing off for 1/16th of a " - "second.", + debug(1, + "Error %d using sendto to an audio socket: \"%s\". Backing off for 1/16th of a " + "second.", errno, em); conn->rtp_time_of_last_resend_request_error_fp = time_of_sending_fp; } else { diff --git a/rtsp.c b/rtsp.c index 2fcd3103..d854ae1b 100644 --- a/rtsp.c +++ b/rtsp.c @@ -288,15 +288,16 @@ void *player_watchdog_thread_code(void *arg) { if (time_since_last_bark >= ct) { conn->watchdog_barks++; if (conn->watchdog_barks == 1) { - 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); } else if (conn->watchdog_barks == 3) { if ((config.cmd_unfixable) && (conn->unfixable_error_reported == 0)) { conn->unfixable_error_reported = 1; - command_execute(config.cmd_unfixable, "unable_to_cancel_play_session",1); + command_execute(config.cmd_unfixable, "unable_to_cancel_play_session", 1); } else { warn("an unrecoverable error, \"unable_to_cancel_play_session\", has been detected.", conn->connection_number); @@ -404,17 +405,17 @@ static char *nextline(char *in, int inbuf) { } void msg_retain(rtsp_message *msg) { - int rc = pthread_mutex_lock(&reference_counter_lock); - if (rc) - debug(1, "Error %d locking reference counter lock"); - if (msg > (rtsp_message *) 0x00010000) { + int rc = pthread_mutex_lock(&reference_counter_lock); + if (rc) + debug(1, "Error %d locking reference counter lock"); + if (msg > (rtsp_message *)0x00010000) { 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) debug(1, "Error %d unlocking reference counter lock"); } else { - debug(1, "invalid rtsp_message pointer 0x%x passed to retain", (uintptr_t) msg); + debug(1, "invalid rtsp_message pointer 0x%x passed to retain", (uintptr_t)msg); } } @@ -499,13 +500,18 @@ void msg_free(rtsp_message **msgh) { uintptr_t index = (msg->index_number) & 0xFFFF; if (index == 0) 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 + *msgh = + (rtsp_message *)(index); // put a version of the index number of the freed message in here free(msg); } else { - // debug(1,"msg_free item %d -- decrement reference to %d.",msg->index_number,msg->referenceCount); + // debug(1,"msg_free item %d -- decrement reference to + // %d.",msg->index_number,msg->referenceCount); } } else if (*msgh != NULL) { - debug(1, "msg_free: error attempting to free an allocated but already-freed rtsp_message, number %d.",(uintptr_t)*msgh); + debug(1, + "msg_free: error attempting to free an allocated but already-freed rtsp_message, number " + "%d.", + (uintptr_t)*msgh); } debug_mutex_unlock(&reference_counter_lock, 0); } @@ -831,9 +837,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, @@ -1564,7 +1571,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 @@ -2009,7 +2016,6 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { uint8_t digest_urp[16], digest_mu[16], digest_total[16]; - #ifdef CONFIG_OPENSSL MD5_CTX ctx; @@ -2244,8 +2250,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); @@ -2304,8 +2311,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" @@ -2602,7 +2610,7 @@ void rtsp_listen_loop(void) { char errorstring[1024]; strerror_r(ret, (char *)errorstring, sizeof(errorstring)); die("Connection %d: cannot create an RTSP conversation thread. Error %d: \"%s\".", - conn->connection_number, ret, (char *)errorstring); + conn->connection_number, ret, (char *)errorstring); } debug(3, "Successfully created RTSP receiver thread %d.", conn->connection_number); conn->running = 1; // this must happen before the thread is tracked diff --git a/shairport.c b/shairport.c index 8d43629e..292acf25 100644 --- a/shairport.c +++ b/shairport.c @@ -59,11 +59,11 @@ #include #endif +#include "activity_monitor.h" #include "common.h" #include "mdns.h" #include "rtp.h" #include "rtsp.h" -#include "activity_monitor.h" #if defined(CONFIG_DACP_CLIENT) #include "dacp.h" @@ -723,24 +723,26 @@ int parse_options(int argc, char **argv) { config.cmd_stop = (char *)str; } - if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_entering_active_mode", &str)) { + if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_entering_active_mode", + &str)) { config.cmd_active_start = (char *)str; } - if (config_lookup_string(config.cfg, "sessioncontrol.run_this_after_exiting_active_mode", &str)) { + if (config_lookup_string(config.cfg, "sessioncontrol.run_this_after_exiting_active_mode", + &str)) { config.cmd_active_stop = (char *)str; } if (config_lookup_float(config.cfg, "sessioncontrol.active_mode_timeout", &dvalue)) { if (dvalue < 0.0) - warn("Invalid value \"%f\" for sessioncontrol.active_mode_timeout. It must be positive. The default of %f will be used instead.", - dvalue, config.active_mode_timeout); + warn("Invalid value \"%f\" for sessioncontrol.active_mode_timeout. It must be positive. " + "The default of %f will be used instead.", + dvalue, config.active_mode_timeout); else config.active_mode_timeout = dvalue; } - - if (config_lookup_string(config.cfg, + if (config_lookup_string(config.cfg, "sessioncontrol.run_this_if_an_unfixable_error_is_detected", &str)) { config.cmd_unfixable = (char *)str; } @@ -1181,7 +1183,7 @@ void main_cleanup_handler(__attribute__((unused)) void *arg) { #endif activity_monitor_stop(0); - + if (config.output->deinit) { debug(1, "Deinitialise the audio backend."); config.output->deinit(); @@ -1519,8 +1521,9 @@ int main(int argc, char **argv) { /* Print out options */ debug(1, "log verbosity is %d.", debuglev); debug(1, "disable resend requests is %s.", config.disable_resend_requests ? "on" : "off"); - debug(1, "diagnostic_drop_packet_fraction is %f. A value of 0.0 means no packets will be dropped " - "deliberately.", + debug(1, + "diagnostic_drop_packet_fraction is %f. A value of 0.0 means no packets will be dropped " + "deliberately.", config.diagnostic_drop_packet_fraction); debug(1, "statistics_requester status is %d.", config.statistics_requested); debug(1, "daemon status is %d.", config.daemonise); @@ -1599,7 +1602,7 @@ int main(int argc, char **argv) { uint8_t ap_md5[16]; #ifdef CONFIG_OPENSSL - // can't imagine this is necessary, but here it is anyway + // can't imagine this is necessary, but here it is anyway int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable MD5_CTX ctx; diff --git a/tinysvcmdns.c b/tinysvcmdns.c index ebf6fea7..bdce032c 100644 --- a/tinysvcmdns.c +++ b/tinysvcmdns.c @@ -1713,7 +1713,8 @@ void mdnsd_stop(struct mdnsd *s) { assert(s != NULL); struct timeval tv = { - .tv_sec = 0, .tv_usec = 500 * 1000, + .tv_sec = 0, + .tv_usec = 500 * 1000, }; s->stop_flag = 1; From eda674850fb0ddb29f159f0249ae3bd9c10cab41 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 21 Jan 2019 21:44:22 +0000 Subject: [PATCH 381/569] Add an "active" mode that can time out after a period -- 10 seconds by default -- after play ends. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 14b8f39c..530bf763 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.3d47], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d48], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 0feb59eaa57a27a6f8997f6228ed6658e38c97d1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 23 Jan 2019 16:34:05 +0000 Subject: [PATCH 382/569] move malloc_cleanup to common.c --- dacp.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dacp.c b/dacp.c index bd84f3c7..7efaea3a 100644 --- a/dacp.c +++ b/dacp.c @@ -147,11 +147,6 @@ void http_cleanup(void *arg) { http_free(rt); } -void malloc_cleanup(void *arg) { - // debug(1, "malloc cleanup called."); - free(arg); -} - int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // will malloc space for the body or set it to NULL -- the caller should free it. From 0a9063fafeeccaa16db979a81c2bef5278f9dee8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 23 Jan 2019 16:34:34 +0000 Subject: [PATCH 383/569] move malloc_cleanup to common.c --- common.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common.c b/common.c index 8430c29d..10c9cc9f 100644 --- a/common.c +++ b/common.c @@ -1243,6 +1243,11 @@ int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexname, const cha return r; } +void malloc_cleanup(void *arg) { + // debug(1, "malloc cleanup called."); + free(arg); +} + void pthread_cleanup_debug_mutex_unlock(void *arg) { pthread_mutex_unlock((pthread_mutex_t *)arg); } char *get_version_string() { From dd1b85383d0600ee503e0357706c8f592fa975d3 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 23 Jan 2019 16:34:46 +0000 Subject: [PATCH 384/569] move malloc_cleanup to common.c --- common.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common.h b/common.h index c1235b71..4a111e2c 100644 --- a/common.h +++ b/common.h @@ -351,4 +351,6 @@ void sps_nanosleep(const time_t sec, int64_t generate_zero_frames(char *outp, size_t number_of_frames, enum sps_format_t format, int with_dither, int64_t random_number_in); +void malloc_cleanup(void *arg); + #endif // _COMMON_H From bd81e733cd894fbf95b2987df2e8c2be7332e791 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 23 Jan 2019 16:44:02 +0000 Subject: [PATCH 385/569] Introduce a simple state machine into the alsa backend to keep track of the connected state of it. Needed to deal properly with disabling and enabling standby dynamically. --- audio_alsa.c | 390 ++++++++++++++++++++++++++------------------------- 1 file changed, 202 insertions(+), 188 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 83f2086d..871ac86a 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -37,6 +37,12 @@ #include #include +enum alsa_backend_mode { + abm_disconnected, + abm_connected, + abm_playing +} alsa_backend_state; // under the control of alsa_mutex + static void help(void); static int init(int argc, char **argv); static void deinit(void); @@ -392,7 +398,7 @@ int actual_open_alsa_device(void) { return ret; } - // check parameters after attempting to set them… + // check parameters after attempting to set them if (set_period_size_request != 0) { snd_pcm_uframes_t actual_period_size; @@ -625,6 +631,9 @@ static int init(int argc, char **argv) { // double dvalue; // set up default values first + + alsa_backend_state = abm_disconnected; // startup state + debug(1, "alsa: init() -- alsa_backend_state => abm_disconnected."); set_period_size_request = 0; set_buffer_size_request = 0; config.alsa_use_hardware_mute = 0; // don't use it by default @@ -966,16 +975,6 @@ static int init(int argc, char **argv) { desired_sample_rate = config.output_rate; sample_format = config.output_format; - if (response == 0) { - // try opening the device. - int ret = actual_open_alsa_device(); - - if (ret == 0) - actual_close_alsa_device(); - else - die("audio_alsa error %d opening the alsa device. Incorrect settings or device already busy?", - ret); - } most_recent_write_time = 0; // could be used by the alsa_buffer_monitor_thread_code pthread_create(&alsa_buffer_monitor_thread, NULL, &alsa_buffer_monitor_thread_code, NULL); @@ -1133,136 +1132,177 @@ int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) { return response; } -int play(void *buf, int samples) { - +int do_play(void *buf, int samples) { + // assuming the alsa_mutex has been acquired // debug(3,"audio_alsa play called."); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable - int ret = 0; - if (alsa_handle == NULL) { - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); - ret = actual_open_alsa_device(); + snd_pcm_state_t state; + snd_pcm_sframes_t my_delay; + int ret = delay_and_status(&state, &my_delay); + + if (ret == 0) { // will be non-zero if an error or a stall + + if ((samples != 0) && (buf != NULL)) { + + // jut check the state of the DAC + + if ((state != SND_PCM_STATE_PREPARED) && (state != SND_PCM_STATE_RUNNING) && + (state != SND_PCM_STATE_XRUN)) { + debug(1, "alsa: DAC in odd SND_PCM_STATE_* %d prior to writing.", state); + } + + // debug(3, "write %d frames.", samples); + ret = alsa_pcm_write(alsa_handle, buf, samples); + if (ret == samples) { + stall_monitor_frame_count += samples; + + if (frame_index == 0) { + frames_sent_for_playing = samples; + } else { + frames_sent_for_playing += samples; + } + + const uint64_t start_measurement_from_this_frame = + (2 * config.output_rate) / 352; // two seconds of framesÉ + + frame_index++; + + if ((frame_index == start_measurement_from_this_frame) || + ((frame_index > start_measurement_from_this_frame) && (frame_index % 32 == 0))) { + + measurement_time = get_absolute_time_in_fp(); + frames_played_at_measurement_time = frames_sent_for_playing - my_delay - samples; + + if (frame_index == start_measurement_from_this_frame) { + // debug(1, "Start frame counting"); + frames_played_at_measurement_start_time = frames_played_at_measurement_time; + measurement_start_time = measurement_time; + measurement_data_is_valid = 1; + } + } + } else { + + debug(1, "alsa: error %d writing %d samples to alsa device.", ret, samples); + frame_index = 0; + measurement_data_is_valid = 0; + if (ret == -EPIPE) { /* underrun */ + ret = snd_pcm_recover(alsa_handle, ret, debuglev > 0 ? 1 : 0); + if (ret < 0) { + warn("alsa: can't recover from SND_PCM_STATE_XRUN: %s.", snd_strerror(ret)); + } + } else if (ret == -ESTRPIPE) { /* suspended */ + while ((ret = snd_pcm_resume(alsa_handle)) == -EAGAIN) { + sleep(1); /* wait until the suspend flag is released */ + if (ret < 0) { + warn("alsa: can't recover from SND_PCM_STATE_SUSPENDED state, snd_pcm_prepare() " + "failed: %s.", + snd_strerror(ret)); + } + } + } + } + } + } else { + debug(1, "alsa: device status returns fault status %d and SND_PCM_STATE_* %d for play.", ret, + state); + frame_index = 0; + measurement_data_is_valid = 0; + } + + pthread_setcancelstate(oldState, NULL); + return ret; +} + +int do_open() { + int ret = 0; + if (alsa_backend_state != abm_disconnected) + debug(1, "alsa: do_open() -- opening the output device when it is already connected"); + if (alsa_handle == NULL) { + // debug(1,"alsa: do_open() -- opening the output device"); + ret = open_alsa_device(); if (ret == 0) { if (audio_alsa.volume) do_volume(set_volume); if (audio_alsa.mute) do_mute(0); } - - debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(0); // release the mutex + alsa_backend_state = abm_connected; // only do this if it really opened it. + } else { + debug(1, "alsa: do_open() -- output device already open."); } + return ret; +} + +int do_close() { + if (alsa_backend_state == abm_disconnected) + debug(1, "alsa: do_close() -- closing the output device when it is already disconnected"); + int derr = 0; + if (alsa_handle) { + // debug(1,"alsa: do_close() -- closing the output device"); + if ((derr = snd_pcm_drop(alsa_handle))) + debug(1, "Error %d (\"%s\") dropping output device.", derr, snd_strerror(derr)); + if ((derr = snd_pcm_hw_free(alsa_handle))) + debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr)); + + // flush also closes the device + if ((derr = snd_pcm_close(alsa_handle))) + debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr)); + alsa_handle = NULL; + } else { + debug(1, "alsa: do_close() -- output device already closed."); + } + alsa_backend_state = abm_disconnected; + return derr; +} + +int play(void *buf, int samples) { + + // play() will change the state of the alsa_backend_mode to abm_playing + // also, if the present alsa_backend_state is abm_disconnected, then first the DAC must be + // connected + + // debug(3,"audio_alsa play called."); + int ret = 0; + + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); + + if (alsa_backend_state == abm_disconnected) { + debug(1, "alsa: play() -- opening output device"); + ret = do_open(); + } + if (ret == 0) { - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0); - - snd_pcm_state_t state; - snd_pcm_sframes_t my_delay; - ret = delay_and_status(&state, &my_delay); - - if (ret == 0) { // will be non-zero if an error or a stall - - if ((samples != 0) && (buf != NULL)) { - - // jut check the state of the DAC - - if ((state != SND_PCM_STATE_PREPARED) && (state != SND_PCM_STATE_RUNNING) && - (state != SND_PCM_STATE_XRUN)) { - debug(1, "alsa: DAC in odd SND_PCM_STATE_* %d prior to writing.", state); - } - - // debug(3, "write %d frames.", samples); - ret = alsa_pcm_write(alsa_handle, buf, samples); - if (ret == samples) { - stall_monitor_frame_count += samples; - - if (frame_index == 0) { - frames_sent_for_playing = samples; - } else { - frames_sent_for_playing += samples; - } - - const uint64_t start_measurement_from_this_frame = - (2 * config.output_rate) / 352; // two seconds of frames… - - frame_index++; - - if ((frame_index == start_measurement_from_this_frame) || - ((frame_index > start_measurement_from_this_frame) && (frame_index % 32 == 0))) { - - measurement_time = get_absolute_time_in_fp(); - frames_played_at_measurement_time = frames_sent_for_playing - my_delay - samples; - - if (frame_index == start_measurement_from_this_frame) { - // debug(1, "Start frame counting"); - frames_played_at_measurement_start_time = frames_played_at_measurement_time; - measurement_start_time = measurement_time; - measurement_data_is_valid = 1; - } - } - } else { - - debug(1, "alsa: error %d writing %d samples to alsa device.", ret, samples); - frame_index = 0; - measurement_data_is_valid = 0; - if (ret == -EPIPE) { /* underrun */ - ret = snd_pcm_recover(alsa_handle, ret, debuglev > 0 ? 1 : 0); - if (ret < 0) { - warn("alsa: can't recover from SND_PCM_STATE_XRUN: %s.", snd_strerror(ret)); - } - } else if (ret == -ESTRPIPE) { /* suspended */ - while ((ret = snd_pcm_resume(alsa_handle)) == -EAGAIN) { - sleep(1); /* wait until the suspend flag is released */ - if (ret < 0) { - warn("alsa: can't recover from SND_PCM_STATE_SUSPENDED state, snd_pcm_prepare() " - "failed: %s.", - snd_strerror(ret)); - } - } - } - } - } - } else { - debug(1, "alsa: device status returns fault status %d and SND_PCM_STATE_* %d for play.", ret, - state); - frame_index = 0; - measurement_data_is_valid = 0; + if (alsa_backend_state != abm_playing) { + debug(1, "alsa: play() -- alsa_backend_state => abm_playing"); + alsa_backend_state = abm_playing; } - - debug_mutex_unlock(&alsa_mutex, 0); - pthread_cleanup_pop(0); // release the mutex + ret = do_play(buf, samples); } - pthread_setcancelstate(oldState, NULL); + + debug_mutex_unlock(&alsa_mutex, 0); + pthread_cleanup_pop(0); // release the mutex return ret; } static void flush(void) { // debug(2,"audio_alsa flush called."); - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); - int derr; do_mute(1); - - if (alsa_handle) { - stall_monitor_start_time = 0; - if (config.keep_dac_busy == 0) { - if ((derr = snd_pcm_drop(alsa_handle))) - debug(1, "Error %d (\"%s\") dropping output device.", derr, snd_strerror(derr)); - if ((derr = snd_pcm_hw_free(alsa_handle))) - debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr)); - - // flush also closes the device - if ((derr = snd_pcm_close(alsa_handle))) - debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr)); - alsa_handle = NULL; + if (alsa_backend_state != abm_disconnected) { // must be playing or connected... + if (config.keep_dac_busy != 0) { + debug(1, "alsa: flush() -- alsa_backend_state => abm_connected."); + alsa_backend_state = abm_connected; + } else { + debug(1, "alsa: flush() -- closing the output device"); + do_close(); // will change the state to disconnected + debug(1, "alsa: flush() -- alsa_backend_state => abm_disconnected."); } - frame_index = 0; - measurement_data_is_valid = 0; - } + } else + debug(1, "alac: flush() -- called on a disconnected alsa backend"); debug_mutex_unlock(&alsa_mutex, 3); pthread_cleanup_pop(0); // release the mutex - pthread_setcancelstate(oldState, NULL); } int preflight(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) { @@ -1274,32 +1314,9 @@ int preflight(__attribute__((unused)) void *buf, __attribute__((unused)) int sam return 0; } -/* -static int play(void *buf, int samples) { - uint64_t time_now = - get_absolute_time_in_fp(); // this is to regulate access by the silence filler thread - uint64_t sample_duration = ((uint64_t)samples) << 32; - sample_duration = sample_duration / desired_sample_rate; - most_recent_write_time = time_now + sample_duration; - return untimed_play(buf, samples); -} - -int delay(long *the_delay) { - uint64_t time_now = - get_absolute_time_in_fp(); // this is to regulate access by the silence filler thread - most_recent_write_time = time_now; - return untimed_delay(the_delay); -} -*/ - static void stop(void) { // debug(2,"audio_alsa stop called."); - // when we want to stop, we want the alsa device - // to be closed immediately -- we may even be killing the thread, so we - // don't wish to wait - // so we should flush first - flush(); // flush will also close the device - // close_alsa_device(); + flush(); // flush will also close the device if appropriate } static void parameters(audio_parameters *info) { @@ -1428,46 +1445,13 @@ void do_mute(int mute_state_requested) { pthread_setcancelstate(oldState, NULL); } +/* void alsa_buffer_monitor_thread_cleanup_function(__attribute__((unused)) void *arg) { debug(1, "alsa: alsa_buffer_monitor_thread_cleanup_function called."); } +*/ -void *alsa_buffer_monitor_thread_code(void *arg) { - // debug(1,"alsa: alsa_buffer_monitor_thread_code called."); - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable - int ret = 0; - if (alsa_handle == NULL) { - - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); - ret = actual_open_alsa_device(); - if (ret == 0) { - if (audio_alsa.volume) - do_volume(set_volume); - if (audio_alsa.mute) - do_mute(0); - } - - debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(0); // release the mutex - } - pthread_cleanup_push(alsa_buffer_monitor_thread_cleanup_function, arg); - pthread_setcancelstate(oldState, NULL); - - // The thinking is, if the device has a hardware mixer, then - // (if no source transformation is happening), Shairport Sync - // will deliver fill-in silences and the audio material without adding dither. - // So don't insert dither into the silences sent to keep the DAC busy. - - // Also, if the ignore_volume_setting is set, the audio is sent through unaltered, - // so, in that circumstance, don't add dither either. - - int use_dither = 0; - if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && - (config.airplay_volume != 0.0)) - use_dither = 1; - - debug(1, "alsa: dither will %sbe added to inter-session silence.", use_dither ? "" : "not "); +void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000); long buffer_size_threshold = (long)(config.audio_backend_silence_threshold * desired_sample_rate); @@ -1485,17 +1469,49 @@ void *alsa_buffer_monitor_thread_code(void *arg) { debug(1, "alsa: failed to allocate memory for a silent frame buffer, thus " "disable_standby_mode is \"off\"."); } else { - long buffer_size; - int reply; + pthread_cleanup_push(malloc_cleanup, silence); + while (1) { - if (config.keep_dac_busy != 0) { + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); + + // check possible state transitions here + if ((alsa_backend_state == abm_disconnected) && (config.keep_dac_busy != 0)) { + // open the dac and move to abm_connected mode + debug(1, "alsa: alsa_buffer_monitor_thread_code -- opening the output device"); + do_open(); + debug(1, "alsa: alsa_buffer_monitor_thread_code() -- alsa_backend_state => abm_connected"); + } else if ((alsa_backend_state == abm_connected) && (config.keep_dac_busy == 0)) { + stall_monitor_start_time = 0; + frame_index = 0; + measurement_data_is_valid = 0; + debug(1, "alsa: alsa_buffer_monitor_thread_code() -- closing the output device"); + do_close(); + debug(1, + "alsa: alsa_buffer_monitor_thread_code() -- alsa_backend_state => abm_disconnected"); + } + // now, if the backend is not in the abm_disconnected state + // and config.keep_dac_busy is true (at the present, this has to be the case to be in the + // abm_connected state in the first place...) then do the silence-filling thing, if needed + if ((alsa_backend_state != abm_disconnected) && (config.keep_dac_busy != 0)) { + int use_dither = 0; + if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && + (config.airplay_volume != 0.0)) + use_dither = 1; + + long buffer_size = 0; + int reply; + uint64_t present_time = get_absolute_time_in_fp(); if ((most_recent_write_time == 0) || (present_time > most_recent_write_time)) { // ((present_time > most_recent_write_time) && // ((present_time - most_recent_write_time) > (sleep_time_in_fp)))) { - reply = delay(&buffer_size); + + snd_pcm_state_t state; + + reply = delay_and_status(&state, &buffer_size); + if (reply != 0) { buffer_size = 0; char errorstring[1024]; @@ -1504,25 +1520,23 @@ void *alsa_buffer_monitor_thread_code(void *arg) { (char *)errorstring); } if (buffer_size < buffer_size_threshold) { - if ((hardware_mixer == 0) && (config.ignore_volume_control == 0)) - use_dither = 1; - else - use_dither = 0; dither_random_number_store = generate_zero_frames( silence, frames_of_silence, config.output_format, use_dither, // i.e. with dither dither_random_number_store); // debug(1,"Play %d frames of silence with most_recent_write_time of %" PRIx64 ".", // frames_of_silence,most_recent_write_time); - play(silence, frames_of_silence); + do_play(silence, frames_of_silence); } } else { // debug(2,"Skipping sending silence"); } } + debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(0); // release the mutex usleep(sleep_time_ms * 1000); // has a cancellation point in it // pthread_testcancel(); } + pthread_cleanup_pop(1); // should never happen } - pthread_cleanup_pop(1); pthread_exit(NULL); -} +} \ No newline at end of file From 0697a40b08ae76f91d612106b985cfbe87dac6d9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 23 Jan 2019 22:21:43 +0000 Subject: [PATCH 386/569] fix crypto library flags --- metadata_hub.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/metadata_hub.c b/metadata_hub.c index 17b221a8..f6c92ae8 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -45,6 +45,18 @@ #include "dacp.h" #include "metadata_hub.h" +#ifdef CONFIG_MBEDTLS +#include +#endif + +#ifdef CONFIG_POLARSSL +#include +#endif + +#ifdef CONFIG_OPENSSL +#include +#endif + pthread_rwlock_t metadata_hub_re_lock = PTHREAD_RWLOCK_INITIALIZER; struct track_metadata_bundle *track_metadata; // used for a temporary track metadata store @@ -207,7 +219,7 @@ char *metadata_write_image_file(const char *buf, int len) { uint8_t img_md5[16]; // uint8_t ap_md5[16]; -#ifdef CONFIG_SSL +#ifdef CONFIG_OPENSSL MD5_CTX ctx; MD5_Init(&ctx); MD5_Update(&ctx, buf, len); From 9fb37e4293b72c036a01993f76c7b7792905213f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 23 Jan 2019 22:22:27 +0000 Subject: [PATCH 387/569] Reorganise code to make it respond dynamically to changes in settings. --- audio_alsa.c | 142 +++++++++++++++++++++++---------------------------- 1 file changed, 65 insertions(+), 77 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 871ac86a..5582202c 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -94,7 +94,7 @@ static unsigned int desired_sample_rate; static enum sps_format_t sample_format; int frame_size; // in bytes for interleaved stereo -static snd_pcm_t *alsa_handle = NULL; +snd_pcm_t *alsa_handle = NULL; static snd_pcm_hw_params_t *alsa_params = NULL; static snd_pcm_sw_params_t *alsa_swparams = NULL; static snd_ctl_t *ctl = NULL; @@ -219,6 +219,7 @@ void do_snd_mixer_selem_set_playback_dB_all(snd_mixer_elem_t *mix_elem, double v } void actual_close_alsa_device() { + debug(1, "actual close"); if (alsa_handle) { int derr; if ((derr = snd_pcm_hw_free(alsa_handle))) @@ -1247,6 +1248,7 @@ int do_close() { debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr)); // flush also closes the device + debug(1, "closing alsa handle"); if ((derr = snd_pcm_close(alsa_handle))) debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr)); alsa_handle = NULL; @@ -1300,7 +1302,7 @@ static void flush(void) { debug(1, "alsa: flush() -- alsa_backend_state => abm_disconnected."); } } else - debug(1, "alac: flush() -- called on a disconnected alsa backend"); + debug(1, "alsa: flush() -- called on a disconnected alsa backend"); debug_mutex_unlock(&alsa_mutex, 3); pthread_cleanup_pop(0); // release the mutex } @@ -1452,91 +1454,77 @@ void alsa_buffer_monitor_thread_cleanup_function(__attribute__((unused)) void *a */ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { - - int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000); - long buffer_size_threshold = (long)(config.audio_backend_silence_threshold * desired_sample_rate); - - uint64_t sleep_time_in_fp = sleep_time_ms; - sleep_time_in_fp = sleep_time_in_fp << 32; - sleep_time_in_fp = sleep_time_in_fp / 1000; - // debug(1,"alsa: sleep_time: %d ms or 0x%" PRIx64 " in fp form.",sleep_time_ms,sleep_time_in_fp); - // int frames_of_silence = (desired_sample_rate * sleep_time_ms * 2) / 1000; - int frames_of_silence = 1024; - size_t size_of_silence_buffer = frames_of_silence * frame_size; - // debug(1,"Silence buffer length: %u bytes.",size_of_silence_buffer); - void *silence = malloc(size_of_silence_buffer); - if (silence == NULL) { - debug(1, "alsa: failed to allocate memory for a silent frame buffer, thus " - "disable_standby_mode is \"off\"."); - } else { - pthread_cleanup_push(malloc_cleanup, silence); - - while (1) { - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); - - // check possible state transitions here - if ((alsa_backend_state == abm_disconnected) && (config.keep_dac_busy != 0)) { - // open the dac and move to abm_connected mode - debug(1, "alsa: alsa_buffer_monitor_thread_code -- opening the output device"); - do_open(); + while (1) { + int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); + // check possible state transitions here + if ((alsa_backend_state == abm_disconnected) && (config.keep_dac_busy != 0)) { + // open the dac and move to abm_connected mode + debug(1, "alsa: alsa_buffer_monitor_thread_code -- opening the output device"); + if (do_open() == 0) debug(1, "alsa: alsa_buffer_monitor_thread_code() -- alsa_backend_state => abm_connected"); - } else if ((alsa_backend_state == abm_connected) && (config.keep_dac_busy == 0)) { - stall_monitor_start_time = 0; - frame_index = 0; - measurement_data_is_valid = 0; - debug(1, "alsa: alsa_buffer_monitor_thread_code() -- closing the output device"); - do_close(); - debug(1, - "alsa: alsa_buffer_monitor_thread_code() -- alsa_backend_state => abm_disconnected"); - } - // now, if the backend is not in the abm_disconnected state - // and config.keep_dac_busy is true (at the present, this has to be the case to be in the - // abm_connected state in the first place...) then do the silence-filling thing, if needed - if ((alsa_backend_state != abm_disconnected) && (config.keep_dac_busy != 0)) { - int use_dither = 0; - if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && - (config.airplay_volume != 0.0)) - use_dither = 1; - - long buffer_size = 0; - int reply; - - uint64_t present_time = get_absolute_time_in_fp(); - - if ((most_recent_write_time == 0) || (present_time > most_recent_write_time)) { - - // ((present_time > most_recent_write_time) && - // ((present_time - most_recent_write_time) > (sleep_time_in_fp)))) { - - snd_pcm_state_t state; - - reply = delay_and_status(&state, &buffer_size); - - if (reply != 0) { - buffer_size = 0; - char errorstring[1024]; - strerror_r(-reply, (char *)errorstring, sizeof(errorstring)); - debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply, - (char *)errorstring); - } - if (buffer_size < buffer_size_threshold) { + } else if ((alsa_backend_state == abm_connected) && (config.keep_dac_busy == 0)) { + stall_monitor_start_time = 0; + frame_index = 0; + measurement_data_is_valid = 0; + debug(1, "alsa: alsa_buffer_monitor_thread_code() -- closing the output device"); + do_close(); + debug(1, "alsa: alsa_buffer_monitor_thread_code() -- alsa_backend_state => abm_disconnected"); + } + // now, if the backend is not in the abm_disconnected state + // and config.keep_dac_busy is true (at the present, this has to be the case to be in the + // abm_connected state in the first place...) then do the silence-filling thing, if needed + if ((alsa_backend_state != abm_disconnected) && (config.keep_dac_busy != 0)) { + int reply; + long buffer_size = 0; + snd_pcm_state_t state; + uint64_t present_time = get_absolute_time_in_fp(); + if ((most_recent_write_time == 0) || (present_time > most_recent_write_time)) { + reply = delay_and_status(&state, &buffer_size); + if (reply != 0) { + buffer_size = 0; + char errorstring[1024]; + strerror_r(-reply, (char *)errorstring, sizeof(errorstring)); + debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply, + (char *)errorstring); + } + long buffer_size_threshold = + (long)(config.audio_backend_silence_threshold * desired_sample_rate); + if (buffer_size < buffer_size_threshold) { + uint64_t sleep_time_in_fp = sleep_time_ms; + sleep_time_in_fp = sleep_time_in_fp << 32; + sleep_time_in_fp = sleep_time_in_fp / 1000; + // debug(1,"alsa: sleep_time: %d ms or 0x%" PRIx64 " in fp + // form.",sleep_time_ms,sleep_time_in_fp); int frames_of_silence = (desired_sample_rate * + // sleep_time_ms * 2) / 1000; + int frames_of_silence = 1024; + size_t size_of_silence_buffer = frames_of_silence * frame_size; + // debug(1, "alsa: alsa_buffer_monitor_thread_code -- silence buffer length: %u bytes.", + // size_of_silence_buffer); + void *silence = malloc(size_of_silence_buffer); + if (silence == NULL) { + debug(1, "alsa: alsa_buffer_monitor_thread_code -- failed to allocate memory for a " + "silent frame buffer."); + } else { + pthread_cleanup_push(malloc_cleanup, silence); + int use_dither = 0; + if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && + (config.airplay_volume != 0.0)) + use_dither = 1; dither_random_number_store = generate_zero_frames( silence, frames_of_silence, config.output_format, use_dither, // i.e. with dither dither_random_number_store); // debug(1,"Play %d frames of silence with most_recent_write_time of %" PRIx64 ".", - // frames_of_silence,most_recent_write_time); + // frames_of_silence,most_recent_write_time); do_play(silence, frames_of_silence); + pthread_cleanup_pop(1); } - } else { - // debug(2,"Skipping sending silence"); } } - debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(0); // release the mutex - usleep(sleep_time_ms * 1000); // has a cancellation point in it - // pthread_testcancel(); } - pthread_cleanup_pop(1); // should never happen + debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(0); // release the mutex + usleep(sleep_time_ms * 1000); // has a cancellation point in it } pthread_exit(NULL); } \ No newline at end of file From b02e5d9f208019c2525dd944419b34b3bce71232 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 24 Jan 2019 09:14:41 +0000 Subject: [PATCH 388/569] Use returned codes from do_open() to control the state machine -- don't go to abm_connected unless, eh, connected. --- audio_alsa.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 5582202c..d77bfffa 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1228,8 +1228,8 @@ int do_open() { do_volume(set_volume); if (audio_alsa.mute) do_mute(0); + alsa_backend_state = abm_connected; // only do this if it really opened it. } - alsa_backend_state = abm_connected; // only do this if it really opened it. } else { debug(1, "alsa: do_open() -- output device already open."); } @@ -1271,9 +1271,10 @@ int play(void *buf, int samples) { pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); if (alsa_backend_state == abm_disconnected) { - debug(1, "alsa: play() -- opening output device"); ret = do_open(); - } + if (ret == 0) + debug(1, "alsa: play() -- opened output device"); + } if (ret == 0) { if (alsa_backend_state != abm_playing) { @@ -1460,9 +1461,8 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { // check possible state transitions here if ((alsa_backend_state == abm_disconnected) && (config.keep_dac_busy != 0)) { // open the dac and move to abm_connected mode - debug(1, "alsa: alsa_buffer_monitor_thread_code -- opening the output device"); if (do_open() == 0) - debug(1, "alsa: alsa_buffer_monitor_thread_code() -- alsa_backend_state => abm_connected"); + debug(1, "alsa: alsa_buffer_monitor_thread_code() -- output device opened; alsa_backend_state => abm_connected"); } else if ((alsa_backend_state == abm_connected) && (config.keep_dac_busy == 0)) { stall_monitor_start_time = 0; frame_index = 0; From 7a4949b32f147b90f2cc0cb2c803bb22dd8ea474 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 24 Jan 2019 09:15:09 +0000 Subject: [PATCH 389/569] quieten a returned error-code message. --- player.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player.c b/player.c index ca4b00df..5efb679c 100644 --- a/player.c +++ b/player.c @@ -2024,7 +2024,7 @@ void *player_thread_func(void *arg) { conn->connection_number); } } else - debug(2, "Delay error %d when checking running latency.", resp); + debug(3, "Delay error %d when checking running latency.", resp); } } From c06586cb852a0f5b7751705cffb24a83b3fa7701 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 24 Jan 2019 09:20:11 +0000 Subject: [PATCH 390/569] Update README.md --- README.md | 542 +----------------------------------------------------- 1 file changed, 3 insertions(+), 539 deletions(-) diff --git a/README.md b/README.md index 0800ae38..05129d9d 100644 --- a/README.md +++ b/README.md @@ -6,544 +6,8 @@ Audio played by a Shairport Sync-powered device stays synchronised with the sour Shairport Sync runs on Linux and FreeBSD. It does not support AirPlay video or photo streaming. -This is the unstable "development" branch. Changes and updates are incorporated into this branch quickly. To access the stable version, where changes are made after due time, please switch to the "master" branch. +**Warning** -More Information ----------- -Shairport Sync offers *full audio synchronisation*, a feature of AirPlay that previous implementations do not provide. Full audio synchronisation means that audio is played on the output device at exactly the time specified by the audio source. To accomplish this, Shairport Sync needs access to audio systems – such as `alsa` on Linux and `sndio` on FreeBSD – that provide very accurate timing information about output devices. Shairport Sync must have direct access to the output device used, which must be a real sound card capable of working with 44,100, 88,200 or 176,400 samples per second, interleaved PCM stereo of 8, 16, 24 or 32 bits. The default is 44,100 samples per second / 16 bits (you'll get a message in the logfile if there's a problem). - -Alternatively, Shairport Sync works well with PulseAudio, a widely used sound server found on many desktop Linuxes. While the timing information is not as accurate as that of `alsa` or `sndio`, it is often impractical to remove or disable PulseAudio. In that case, the `pa` backend can be used. An older backend for PulseAudio called `pulse` does not support synchronisation and is deprecated. - -For other use cases, Shairport Sync can provide synchronised audio output to a unix pipe or to standard output, or to audio systems that do not provide timing information. This could perhaps be described as *partial audio synchronisation*, where synchronised audio is provided by Shairport Sync, but what happens to it in the subsequent processing chain, before it reaches the listener's ear, is outside the control of Shairport Sync. - -For more about the motivation behind Shairport Sync, please see the wiki at https://github.com/mikebrady/shairport-sync/wiki. - -Synchronisation, Latency, "Stuffing" ---------- -The AirPlay protocol uses an agreed *latency* – the time difference, or delay, between the time represented by a sound sample's `timestamp` and the time it is actually played by the audio output device, typically a Digital to Audio Converter (DAC). The latency to be used is specified by the audio source when it negotiates with Shairport Sync. Most sources set a latency of two seconds. Recent versions of iTunes and forkedDaapd use a latency of just over 2.25 seconds. A latency of this length allows AirPlay players to correct for network delays, processing time variations and so on. - -As mentioned previously, Shairport Sync implements full audio synchronisation when used with `alsa`, `sndio` or PulseAudio systems. This is done by monitoring the timestamps present in data coming from the audio source and the timing information from the audio system, e.g. `alsa`. To maintain the latency required for exact synchronisation, if the output device is running slow relative to the source, Shairport Sync will delete frames of audio to allow the device to keep up. If the output device is running fast, Shairport Sync will insert frames to keep time. The number of frames inserted or deleted is so small as to be almost inaudible on normal audio material. Frames are inserted or deleted as necessary at pseudorandom intervals. Alternatively, with `libsoxr` support, Shairport Sync can resample the audio feed to ensure the output device can keep up. This is less obtrusive than insertion and deletion but requires a good deal of processing power — most embedded devices probably can't support it. The process of insertion/deletion or resampling is rather inelegantly called “stuffing”. - -Stuffing is not done for partial audio synchronisation – the audio samples are simply presented at exactly the right time to the next stage in the processing chain. - -Timestamps are referenced relative to the source computer's clock – the `source clock`, but timing must be done relative to the clock of the computer running Shairport Sync – the `local clock`. So, another thing Shairport Sync has to do is to synchronize the source clock and the local clock, and it does this usually to within a fraction of a millisecond, using a variant of NTP synchronisation protocols. - -What else? --------------- -* Better Volume Control — Shairport Sync offers finer control at very top and very bottom of the volume range. See http://tangentsoft.net/audio/atten.html for a good discussion of audio "attenuators", upon which volume control in Shairport Sync is modelled. See also the diagram of the volume transfer function in the documents folder. In addition, Shairport Sync can offer an extended volume control range on devices with a restricted range. -* Hardware Mute — Shairport Sync can mute properly if the hardware supports it. -* Support for the Apple ALAC decoder. -* Output bit depths of 8, 16, 24 and 32 bits, rather than the standard 16 bits. -* Fast Response — With hardware volume control, response is instantaneous; otherwise the response time is 0.15 seconds with `alsa`, 0.35 seconds with `sndio`. -* Non-Interruptible — Shairport Sync sends back a "busy" signal if it's already playing audio from another source, so other sources can't disrupt an existing Shairport Sync session. (If a source disappears without warning, the session automatically terminates after two minutes and the device becomes available again.) -* Metadata — Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in iOS. -* A native [MQTT](https://en.wikipedia.org/wiki/MQTT) interface for metadata and/or controls. -* Raw Audio — Shairport Sync can deliver raw PCM audio to standard output or to a pipe. This output is delivered synchronously with the source after the appropriate latency and is not interpolated or "stuffed" on its way through Shairport Sync. -* Autotools and Libtool Support — the Shairport Sync build process uses GNU `autotools` and `libtool` to examine and configure the build environment — important for portability and for cross compilation. Previous versions of Shairport looked at the current system to determine which packages were available, instead of looking at the target system for what packages were available. - -Heritage -------- -Shairport Sync is a substantial rewrite of the fantastic work done in Shairport 1.0 by James Laird and others — please see https://github.com/abrasive/shairport/blob/development/README.md#contributors-to-version-1x for a list of the contributors to Shairport 1.x and Shairport 0.x. From a "heritage" point of view, Shairport Sync is a fork of Shairport 1.0. - -Status ------- -Shairport Sync works on a wide variety of Linux devices and FreeBSD. It works on standard Ubuntu laptops, on the Raspberry Pi with Raspbian Stretch, Jessie and Wheezy, Arch Linux and OpenWrt, and it runs on a Linksys NSLU2 and a TP-Link 710N using OpenWrt. It works with built-in audio and with a variety of USB-connected audio amplifiers and DACs, including a cheapo USB "3D Sound" dongle, a first generation iMic and a Topping TP30 amplifier with a USB DAC input. - -Shairport Sync will work with PulseAudio, which is installed in many desktop Linuxes. PulseAudio normally runs in the *user mode* but can be configured to run in *system mode*, though this is not recommended. Shairport Sync can work with it in either mode. - -Shairport Sync runs well on the Raspberry Pi on USB and I2S cards. It can drive the built-in sound card – see the note below on configuring the Raspberry Pi to make best use of it. It runs well on the Raspberry Pi Zero W with a suitable USB or I2S card. - -Shairport Sync runs natively on FreeBSD using the `sndio` sound system. - -Shairport Sync runs on Ubuntu, OpenWrt, Debian, Arch Linux, Fedora and FreeBSD inside VMWare Fusion on a Mac, but synchronisation is inaccurate — possibly because the sound card is being emulated. - -In addition, Shairport Sync can output to standard output, pipes, `soundio` and `ao` devices using appropriate backends. - -For information about changes and updates, please refer to the RELEASENOTES.md file in the distribution. - -Building And Installing the Development Version ---------------------- -See [here](https://github.com/mikebrady/shairport-sync/blob/development/INSTALL.md) for simple installation instructions for modern Linux systems like Rapsberry Pis. - -The following procedures will build and install the `shairport-sync` application into your system. - -**Remove Old Versions of Shairport Sync and its Startup Scripts** - -You should check to see if `shairport-sync` is already installed – you can use the command `$ which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere. - -You should also remove the initialisation script files `/etc/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed in necessary. - -**FreeBSD** - -To build Shairport Sync from sources on FreeBSD please refer to [FREEBSD.md](https://github.com/mikebrady/shairport-sync/blob/master/FREEBSD.md). - -**Determine The Configuration Needed** - -Shairport Sync has a number of different "backends" that connnect it to the system's audio handling infrastructure. Most recent Linux distributions that have a GUI – including Ubuntu, Debian and others – use PulseAudio to handle sound. In such cases, it is inadvisable to attempt to disable or remove PulseAudio. Thus, if your system uses PulseAudio, you should build Shairport Sync with the PulseAudio backend. You can check to see if PulseAudio is running by opening a Terminal window and entering the command `$ pactl info`. Here is an example of what you'll get if PulseAudio is installed, though the exact details may vary: -``` -$ pactl info -Server String: unix:/run/user/1000/pulse/native -Library Protocol Version: 30 -Server Protocol Version: 30 -Is Local: yes -Client Index: 9 -Tile Size: 65472 -User Name: mike -Host Name: ubuntu -Server Name: pulseaudio -Server Version: 8.0 -Default Sample Specification: s16le 2ch 44100Hz -Default Channel Map: front-left,front-right -Default Sink: alsa_output.pci-0000_02_02.0.analog-stereo -Default Source: alsa_input.pci-0000_02_02.0.analog-stereo -Cookie: 96f9:3e8d -$ -``` -If PulseAudio in not installed, you'll get something like this: -``` -$ pactl info --bash: pactl: command not found -$ -``` -If your system does not use PulseAudio, then it is likely that it uses the Advanced Linux Sound Architecture (ALSA), so you should build Shairport Sync with the ALSA backend. By the way, many systems with PulseAudio also have ALSA (in fact, PulseAudio is effectively a client of ALSA); in those cases you should choose the PulseAudio backend. - -If PulseAudio is not installed, there is no necessity to install it for Shairport Sync. In fact, Shairport Sync works better without it. - -**Building** - -To build Shairport Sync from sources on Debian, Ubuntu, Raspbian, etc. follow these instructions. - -The following libraries are required: -* OpenSSL or mbed TLS (PolarSSL is supported but deprecated) -* Avahi -* ALSA and/or PulseAudio -* libdaemon -* autoconf -* automake -* libtool -* libpopt -* libconfig - -Optional: -* libsoxr -* libalac (This is a library containing the Apple ALAC decoder.) -* libmosquitto (This is to enable the mqtt-metadata-interface.) - -Many Linux distributions have Avahi and OpenSSL already in place, so normally it probably makes sense to choose those options rather than tinysvcmdns or mbed TLS. The `libsoxr` library is available in recent Linux distributions, but it requires lots of processor power — chances are an embedded processor won't be able to keep up. - -Debian, Ubuntu and Raspbian users can get the basics with: - -- `# apt-get install build-essential git xmltoman` – these may already be installed. -- `# apt-get install autoconf automake libtool libdaemon-dev libpopt-dev libconfig-dev` -- `# apt-get install libasound2-dev` for the ALSA libraries -- `# apt-get install libpulse-dev` for the PulseAudio libraries -- `# apt-get install avahi-daemon libavahi-client-dev` if you want to use Avahi (recommended). -- `# apt-get install libssl-dev` if you want to use OpenSSL and libcrypto, or use mbed TLS otherwise. -- `# apt-get install libmbedtls-dev` if you want to use mbed TLS, or use OpenSSL/libcrypto otherwise. You can still use PolarSSL with `apt-get install libpolarssl-dev` if you want to use PolarSSL, but it is deprecated as it's not longer being supported. -- `# apt-get install libsoxr-dev` if you want support for libsoxr-based resampling. This library is in many recent distributions; if not, instructions for how to build it from source for Rasbpian/Debian Wheezy are available at [LIBSOXR.md](https://github.com/mikebrady/shairport-sync/blob/master/LIBSOXR.md). - -If you wish to include the Apple ALAC decoder, you need install it first – please refer to the [ALAC](https://github.com/mikebrady/alac) repository for more information. - -**Download Shairport Sync:** -``` -$ git clone https://github.com/mikebrady/shairport-sync.git -``` - -Next, `cd` into the shairport-sync directory and execute the following commands: -``` -$ git checkout development -$ autoreconf -i -f -``` -(Note that the `autoreconf...` step may take some time on less powerful machines.) - -**Choose the appropriate `--with-*` options:** - -(Don't worry -- there's a recommended set of configuration options further down.) - -- `--with-alsa` include the ALSA backend module to audio to be output through the Advanced Linux Sound Architecture (ALSA) system directly. This is recommended for highest quality. -- `--with-pa` include the PulseAudio audio back end. This is recommended if your Linux installation already has PulseAudio installed. Although ALSA would be better, it requires direct and exclusive access to to a real (hardware) soundcard, and this is often impractical if PulseAudio is installed. -- `--with-stdout` include an optional backend module to enable raw audio to be output through standard output (stdout). -- `--with-pipe` include an optional backend module to enable raw audio to be output through a unix pipe. -- `--with-soundio` include an optional backend module to enable raw audio to be output through the soundio system. -- `--with-avahi` or `--with-tinysvcmdns` for mdns support. Avahi is a widely-used system-wide zero-configuration networking (zeroconf) service — it may already be in your system. If you don't have Avahi, or similar, then consider including tinysvcmdns, which is a tiny zeroconf service embedded inside the shairport-sync application itself. To enable multicast for `tinysvcmdns`, you may have to add a default route with the following command: `route add -net 224.0.0.0 netmask 224.0.0.0 eth0` (substitute the correct network port for `eth0`). You should not have more than one zeroconf service on the same system — bad things may happen, according to RFC 6762, §15. -- `--with-ssl=openssl`, `--with-ssl=mbedtls` or `--with-ssl=polarssl` (deprecated) for encryption and related utilities using either OpenSSL, mbed TLS or PolarSSL. -- `--with-soxr` for libsoxr-based resampling. -- `--with-piddir` for specifying where the PID file should be stored. This directory is normally chosen automatically. The directory must be writable. If you use this option, you may have to edit the init script to search for the PID file in your new location. -- `--with-metadata` to add support for Shairport Sync to pipe metadata to a compatible application of your choice. See https://github.com/mikebrady/shairport-sync-metadata-reader for a sample metadata reader. -- `--with-mqtt-client` to add support for publishing metadata and remote control via [MQTT](https://en.wikipedia.org/wiki/MQTT). -- `--with-configfiles` to install a configuration file and a separate sample file at the `make install` stage. Default is to install. An existing `/etc/shairport-sync.conf` will not be overwritten. -- `--with-pkg-config` to use pkg-config to find libraries. Default is to use pkg-config — this option is for special purpose use. -- `--with-apple-alac` to include the Apple ALAC Decoder. -- `--with-convolution` to include a convolution filter that can be used to apply effects such as frequency and phase correction, and a loudness filter that compensates for human ear non-linearity. Requires `libsndfile`. -- `--with-systemd` to include a script to create a Shairport Sync service that can optionally launch automatically at startup on `systemd`-based Linuxes. Default is not to to install. -- `--with-systemv` to include a script to create a Shairport Sync service that can optionally launch automatically at startup on System V based Linuxes. Default is not to to install. - -**Determine if it's a `systemd` or a "System V" installation:** - -If you wish to have Shairport Sync start automatically when your system boots, you need to figure out what so-called "init system" your system is using. (If you are using Shairport Sync with PulseAudio, as installed in many desktop systems, this section doesn't apply.) - -There are a number of init systems in use: `systemd`, `upstart` and "System V" among others, and it's actually difficult to be certain which one your system is using. Fortunately, for Shairport Sync, all you have to do is figure out if it's a `systemd` init system or not. If it is not a `systemd` init system, you can assume that it is either a System V init system or else it is compatible with a System V init system. Recent systems tend to use `systemd`, whereas older systems use `upstart` or the earlier System V init system. - -The easiest way to look at the first few lines of the `init` manual. Enter the command: - -``` -$ man init -``` -In a `systemd` system, the top lines of the `man` page make it clear that it's a `systemd` system, as follows (from Ubuntu 16.04): -``` -SYSTEMD(1) systemd SYSTEMD(1) - -NAME - systemd, init - systemd system and service manager -... -``` -Other init systems will look considerably different. For instance, in an `upstart` system, the top lines of the `man` page indicate it's using `upstart` system, as follows (from Ubuntu 14.04): - -``` -init(8) System Manager's Manual init(8) - -NAME - init - Upstart process management daemon - -SYNOPSIS - init [OPTION]... -... -``` -In a System V system, the top lines of the `man` page are as follows (from Debian 7.11): -``` -INIT(8) Linux System Administrator's Manual INIT(8) - -NAME - init, telinit - process control initialization - -SYNOPSIS - /sbin/init [ -a ] [ -s ] [ -b ] [ -z xxx ] [ 0123456Ss ] - /sbin/telinit [ -t SECONDS ] [ 0123456sSQqabcUu ] - /sbin/telinit [ -e VAR[=VAL] ] - -... -``` -If your system is definitely a `systemd` system, choose `--with-systemd` below. Otherwise, choose `--with-systemv`. - -**Choose the location of the configuration file** - -A final consideration is the location of the configuration file `shairport-sync.conf`. This will be placed in the directory specified by the `sysconfdir` configuration variable, which defaults to `/usr/local/etc`. This is normal in BSD Unixes, but is unusual in Linux. Hence, for Linux installations, you need to set the `sysconfdir` variable to `/etc` using the configuration setting `--sysconfdir=/etc`. - -**Sample `./configure` command with parameters for a typical Linux `systemd` installation:** - -Here is a recommended set of configuration options suitable for Linux installations that use `systemd`, such as Ubuntu 15.10 and later, and Raspbian Stretch and Jessie. It specifies both the ALSA and PulseAudio backends and includes a sample configuration file and an script for automatic startup on system boot: - -`$ ./configure --sysconfdir=/etc --with-alsa --with-pa --with-avahi --with-ssl=openssl --with-metadata --with-soxr --with-systemd` - -* Omit the `--with-soxr` if the libsoxr library is not available. -* For installation into a System V system, replace the `--with-systemd` with `--with-systemv`. -* If you intend to use Shairport Sync with PulseAudio in the standard user mode, it can not be a system service, so you should omit both `--with-systemd` and `--with-systemv`. - -**Build and Install the Application:** - -Enter: - -``` -$ make -``` - -to build the application. - -Assuming you have used the `./configure` settings suggested above, you can now install the application: - -``` -$ sudo make install -``` -The user and group `shairport-sync` will be created if necessary, and a runtime folder will be created at `/var/run/shairport-sync` if you have chosen `--with-systemv`. In addition, a `man` page, a default configuration file and automatic power-on startup script will be installed. - - -**Complete installation into to a `systemd` system** - -If you have chosen the `--with-systemd` configuration option, then, to enable Shairport Sync to start automatically at system startup, enter: - -``` -$ sudo systemctl enable shairport-sync -``` - -**Complete installation into a System V system** - -If you have chosen the `--with-systemv` configuration option, enter: -``` -$ sudo update-rc.d shairport-sync defaults 90 10 -``` - -**Man Page** - -You can view the man page here: http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/development/man/shairport-sync.html - -Configuring Shairport Sync --------- -There are two logically distinct parts to getting Shairport Sync to run properly on your machine: (1) starting and stopping it and (2) ensuring it has the right settings. - -**(1) Starting and Stopping:** -Starting and stopping Shairport Sync automatically is taken care of differently in different versions of Linux – see the previous section for an example of installing into a `systemd` or a System V based system. - -If you are using PulseAudio in the standard "user" mode, you can not start Shairport Sync automatically at boot time – it must be started when the user logs in to a GUI session. Various GUI tools exist to enable you to build a Startup Application. However, there does not appear to be a similar tool for terminating the program when you log out. Also, it the GUI switches to another user, the user's PulseAudio session is disconnected from the output device. - -**(2) Settings:** -To get the best from Shairport Sync, you’ll need to (a) give Shairport Sync a service name by which it will be seen in iTunes etc. and (b) specify the backend you wish to use - `alsa` for the ALSA backend, or `pa` for the PulseAudio back end. If only one backend is included at compilation, or if the backend is ALSA, there is no need to explicitly specify the backend. - -For the ALSA backend you may need to (c) specify the output device to use and (d) specify the name of the mixer volume control to use to control the output level. To get values for (b) and (c) you might need to explore the ALSA output devices with a program like `alsamixer` or similar. - -Shairport Sync reads settings from a configuration file at `/etc/shairport-sync.conf` (note that in FreeBSD it will be at `/usr/local/etc/shairport-sync.conf`). When you run `$sudo make install`, a sample configuration file is installed or updated at `/etc/shairport-sync.conf.sample` (`/usr/local/etc/shairport-sync.conf.sample` in FreeBSD). This contains all the setting groups and all the settings available, but they all are commented out (comments begin with `//`) so that default values are used. The file contains explanations of the settings, useful hints and suggestions. In addition, if the file doesn't already exist, a default configuration is installed, which should work in almost any system with a sound card. - -Settings in the configuration file are grouped. For instance, there is a `general` group within which you can use the `name` tag to set the service name. Suppose you wanted to set the name of the service to `Front Room`, give the service the password `secret` and use `libsoxr` interpolation, then you should do the following: - -``` -general = -{ - name = "Front Room"; - password = "secret"; - interpolation = "soxr"; - // ... other general settings -}; -``` -(Remember, anything preceded by `//` is a comment and will have no effect on the setting of Shairport Sync.) - -**Important:** You should *never* use an important password as the AirPlay password for a Shairport Sync player -- the password is stored in Shairport Sync's configuration file in plain text and is thus completely vulnerable. - -No backend is specified here, so it will default to the `alsa` backend if more than one back end has been compiled. To route the output to PulseAudio, add: - -``` - output_backend = "pa"; -``` -to the `general` stanza. - -The `alsa` group is used to specify properties of the output device. The most obvious setting is the name of the output device which you can set using the `output_device` tag. - -The following `alsa` group settings are very important for maximum performance. If your audio device has a mixer that can be use to control the volume, then Shairport Sync can use it to give instant response to volume and mute commands and it can offload some work from the processor. -* The `mixer_control_name` tag allows you to specify the name of the mixer volume control. -* The `mixer_device` tag allows you specify where the mixer is. By default, the mixer is on the `output_device`, so you only need to use the `mixer_device` tag if the mixer is elsewhere. This can happen if you specify a *device* rather than a *card* with the `output_device` tag, because normally a mixer is associated with a *card* rather than a device. Suppose you wish to use the output device `5` of card `hw:0` and the mixer volume-control named `PCM`: - -``` -alsa = -{ - output_device = "hw:0,5"; - mixer_device = "hw:0"; - mixer_control_name = "PCM"; - // ... other alsa settings -}; -``` - -The `pa` group is used to specify settings relevant to the PulseAudio backend. You can set the "Application Name" that will appear in the "Sound" control panel. You can also set the sink name that audio will be pushed to. Here's an example `pa` group configuration: - -``` -pa = -{ - application_name = "Shairport Sync - Bedroom"; - sink = "server_bedroom"; // Sink name or ID. If not specified, the default sink will be used. -}; -``` - -Shairport Sync can run programs just before it starts to play an audio stream and just after it finishes. You specify them using the `sessioncontrol` group settings `run_this_before_play_begins` and `run_this_after_play_ends`. This is to facilitate situations where something has to be done before and after playing, e.g. switching on an amplifier beforehand and switching it off afterwards. Set the `wait_for_completion` value to `"yes"` for Shairport Sync to wait until the respective commands have been completed before continuing. - -Note that the full path to the programs must be specified, and script files will not be executed unless they are marked as executable and have the appropriate shebang `#!/bin/...` as the first line. (This behaviour may be different from other Shairports.) - -Shairport Sync can run a program whenever the volume is set or changed. You specify them using the `general` group settings `run_this_when_volume_changes`. This is to facilitate situations where something has to be done when the volume changes, e.g. adjust an external mixer value. Set the `wait_for_completion` value to `"yes"` for Shairport Sync to wait until the command has been completed before continuing. Again, please note that the full path to the program must be specified, and script files will not be executed unless they are marked as executable and have the appropriate shebang `#!/bin/...` as the first line. - -Note: Shairport Sync can take configuration settings from command line options. This is mainly for backward compatibility, but sometimes still useful. For normal use, it is strongly recommended that you use the configuration file method. - -**Raspberry Pi** - -The Raspberry Pi has a built-in audio DAC that is connected to the device's headphone jack. An updated audio driver has greatly improved the quality of the output – see [#525](https://github.com/mikebrady/shairport-sync/issues/525) for details. To activate the updated driver, add the line: -``` -audio_pwm_mode=2 -``` -to `/boot/config.txt` and reboot. -Apart from a loud click when used for the first time after power-up, it is quite adequate for casual listening. A problem is that it declares itself to have a very large mixer volume control range – all the way from -102.38dB up to +4dB, a range of 106.38 dB. In reality, only the top 50 dB of it is in any way usable. To help get the most from the DAC, consider using the `volume_range_db` setting in the `general` group to instruct Shairport Sync to use the top of the DAC mixer's declared range. For example, if you set the `volume_range_db` figure to 50, the top 50 dB of the range will the used. With this setting on the Raspberry Pi, maximum volume will be +4dB and minimum volume will be -46dB, below which muting will occur. - -From a user's point of view, the effect of using this setting is to move the minimum usable volume all the way down to the bottom of the user's volume control, rather than have the minimum usable volume concentrated very close to the maximum volume. - -Another setting to consider is the `general` `drift_tolerance_in_seconds` setting: you should set it to a larger tolerance, say 10 milliseconds – `drift_tolerance_in_seconds=0.010;` – to reduce the amount of overcorrection that seems to occur when using the Raspberry Pi's built-in DAC. - -*Command Line Arguments* - -As previously mentioned, you can use command line arguments to provide settings to Shairport Sync as before, though newer settings will only be available via the configuration file. For full information, please read the Shairport Sync `man` page, also available at http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/development/man/shairport-sync.html. - -Apart from the following options, all command line options can be replaced by settings in the configuration file. Here is a brief description of command line options that are not replicated by settings in the settings file. - -* The `-c` option allows you to specify the location of the configuration file. -* The `-V` option gives you version information about Shairport Sync and then quits. -* The `-d` option causes Shairport Sync to properly daemonise itself, that is, to run in the background. You may need sudo privileges for this. -* The `-k` option causes Shairport Sync to kill an existing Shairport Sync daemon. You may need to have sudo privileges for this. - -The System V init script at `/etc/init.d/shairport-sync` has a bare minimum : -`-d`. Basically all it does is put the program in daemon mode. The program will read its settings from the configuration file. - -Examples --------- - -Here are some examples of complete configuration files. - -``` -general = { - name = "Joe's Stereo"; -}; - -alsa = { - output_device = "hw:0"; -}; -``` - -This gives the service a particular name — "Joe's Stereo" and specifies that audio device hw:0 be used. - -For best results with the `alsa` backend — including getting true mute and instant response to volume control and pause commands — you should access the hardware volume controls. Use `amixer` or `alsamixer` or similar to discover the name of the mixer control to be used as the `mixer_control_name`. - -Here is an example for for a Raspberry Pi using its internal soundcard — device hw:0 — that drives the headphone jack: -``` -general = { - name = "Mike's Boombox"; -}; - -alsa = { - output_device = "hw:0"; - mixer_control_name = "PCM"; -}; -``` - -Here is an example of using soxr-based resampling and driving a Topping TP30 Digital Amplifier, which has an integrated USB DAC and which is connected as audio device `hw:1`: -``` -general = { - name = "Kitchen"; - interpolation = "soxr"; -}; - -alsa = { - output_device = "hw:1"; - mixer_control_name = "PCM"; -}; -``` - -For a cheapo "3D Sound" USB card (Stereo output and input only) on a Raspberry Pi: -``` -general = { - name = "Front Room"; -}; - -alsa = { - output_device = "hw:1"; - mixer_control_name = "Speaker"; -}; -``` - -For a first generation Griffin iMic on a Raspberry Pi: -``` -general = { - name = "Attic"; -}; - -alsa = { - output_device = "hw:1"; - mixer_control_name = "PCM"; -}; -``` - -For an NSLU2, which has no internal sound card, there appears to be a bug in ALSA — you can not specify a device other than "default". Thus: - -On an NSLU2, to drive a first generation Griffin iMic: -``` -general = { - name = "Den"; -}; - -alsa = { - mixer_control_name = "PCM"; -}; -``` - -On an NSLU2, to drive the "3D Sound" USB card: -``` -general = { - name = "TV Room"; -}; - -alsa = { - mixer_control_name = "Speaker"; -}; -``` - -Finally, here is an example of using the PulseAudio backend: -``` -general = { - name = "Zoe's Computer"; - output_backend = "pa"; -}; - -``` - - -Metadata --------- - -Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in iOS. - -**Metadata broadcasting over UDP** - -As an alternative to sending metadata to a pipe, the `socket_address` and `socket_port` tags may be set in the metadata group to cause Shairport Sync to broadcast UDP packets containing the track metadata. - -The advantage of UDP is that packets can be sent to a single listener or, if a multicast address is used, to multiple listeners. It also allows metadata to be routed to a different host. However UDP has a maximum packet size of about 65000 bytes; while large enough for most data, Cover Art will often exceed this value. Any metadata exceeding this limit will not be sent over the socket interface. The maximum packet size may be set with the `socket_msglength` tag to any value between 500 and 65000 to control this - lower values may be used to ensure that each UDP packet is sent in a single network frame. The default is 500. Other than this restriction, metadata sent over the socket interface is identical to metadata sent over the pipe interface. - -The UDP metadata format is very simple - the first four bytes are the metadata *type*, and the next four bytes are the metadata *code* (both are sent in network byte order - see https://github.com/mikebrady/shairport-sync-metadata-reader for a definition of those terms). The remaining bytes of the packet, if any, make up the raw value of the metadata. - -Latency -------- -Latency is the exact time from a sound signal's original timestamp until that signal actually "appears" on the output of the audio output device, usually a Digital to Audio Converter (DAC), irrespective of any internal delays, processing times, etc. in the computer. - -Shairport Sync uses latencies supplied by the source, typically either 2 seconds or just over 2.25 seconds. You shouldn't need to change them. - -Problems can arise when you are trying to synchronise with speaker systems — typically surround-sound home theatre systems — that have their own inherent delays. You can compensate for an inherent delay using the appropriate backend (typically `alsa`) `audio_backend_latency_offset_in_seconds`. Set this offset (in frames) to compensate for a fixed delay in the audio back end; for example, if the output device delays by 100 ms, set this to -0.1. - -Resynchronisation -------------- -Shairport Sync actively maintains synchronisation with the source. -If synchronisation is lost — say due to a busy source or a congested network — Shairport Sync will mute its output and resynchronise. The loss-of-sync threshold is a very conservative 0.050 seconds — i.e. the actual time and the expected time must differ by more than 50 ms to trigger a resynchronisation. Smaller disparities are corrected by insertions or deletions, as described above. -* You can vary the resync threshold, or turn resync off completely, with the `general` `resync_threshold_in_seconds` setting. - -Tolerance ---------- -Playback synchronisation is allowed to wander — to "drift" — a small amount before attempting to correct it. The default is 0.002 seconds, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the `statistics` setting to monitor correction levels. Corrections should not greatly exceed net corrections. -* You can vary the tolerance with the `general` `drift_tolerance_in_seconds` setting. - -Some Statistics ---------------- -If you turn on the `general` `statistics` setting, a heading like this will be output to the console or log file: -``` -sync error in milliseconds, net correction in ppm, corrections in ppm, total packets, missing packets, late packets, too late packets, resend requests, min DAC queue size, min buffer occupancy, max buffer occupancy -``` -This will be followed by the statistics themselves at regular intervals, for example: -``` - -1.3, 25.9, 25.9, 3758, 0, 0, 0, 0, 4444, 263, 270 - -2.0, 96.0, 96.0, 7516, 0, 0, 0, 0, 5357, 260, 267 - -2.0, 96.0, 96.0, 11274, 0, 0, 0, 0, 4956, 262, 268 - -2.0, 94.5, 94.5, 15032, 0, 0, 0, 0, 5430, 263, 267 - -2.0, 99.8, 99.8, 18790, 0, 0, 0, 0, 5047, 261, 268 - -2.0, 91.5, 91.5, 22548, 0, 0, 0, 0, 5258, 260, 267 - -2.0, 90.7, 90.7, 26306, 0, 0, 0, 0, 5279, 262, 267 - -2.0, 96.0, 96.0, 30064, 0, 0, 0, 0, 5551, 260, 266 -``` - -"Sync error in milliseconds" is the average deviation from exact synchronisation. The first line of the example above indicates that the output is on average 1.3 milliseconds behind exact synchronisation. Sync is allowed to drift by the `general` `drift_tolerance_in_seconds` setting — (± 0.002 seconds) by default — before a correction will be made. - -"Net correction in ppm" is actually the net sum of corrections — the number of frame insertions less the number of frame deletions — given as a moving average in parts per million. After an initial settling period, it represents the drift — the divergence between the rate at which frames are generated at the source and the rate at which the output device consumes them. The first line of the example above indicates that the output device is consuming frames 25.9 ppm faster than the source is generating them. The subsequent lines indicate that the stable actual difference in the clocks is around 95 ppm. - -"Corrections in ppm" is the number of frame insertions plus the number of frame deletions (i.e. the total number of corrections), given as a moving average in parts per million. The closer this is to the net corrections, the fewer "unnecessary" corrections that are being made. Third party programs tend to have much larger levels of corrections. - -For reference, a drift of one second per day is approximately 11.57 ppm. Left uncorrected, even a drift this small between two audio outputs will be audible after a short time. The above sample is from an Ethernet-connected iMac driving a WiFi-connected Raspberry Pi 3 using an IQaudIO Pi-DigiAMP+. - -It's not unusual to have resend requests, late packets and even missing packets if some part of the connection to the Shairport Sync device is over WiFi. Late packets can sometimes be asked for and received multiple times. Sometimes late packets are sent and arrive too late, but have already been sent and received in time, so weren't needed anyway... - -"Min DAC queue size" is the minimum size the queue of samples in the output device's hardware buffer was measured at. It is meant to stand at 0.15 seconds = 6,615 frames at 44,100 frames per second, and will go low if the processor is very busy. If it goes below about 2,000 then it's an indication that the processor can't really keep up. - -WiFi Issues ---------- -If you are using WiFi, you should ensure that WiFi power management is off. See [TROUBLESHOOTING](https://github.com/mikebrady/shairport-sync/blob/development/TROUBLESHOOTING.md) for more details. - -Troubleshooting ---------------- -Please refer to [TROUBLESHOOTING](https://github.com/mikebrady/shairport-sync/blob/development/TROUBLESHOOTING.md) for a few hints, contributed by users. +This is the very unstable "transit" branch. It is used to move unfinished or untested code between development sites. +To access the stable version, where changes are made after due time, please switch to the "master" branch. From 8a8b59ad8debb058aefb57a47508d723f05d6929 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 24 Jan 2019 09:23:51 +0000 Subject: [PATCH 391/569] Update README.md --- README.md | 542 +----------------------------------------------------- 1 file changed, 2 insertions(+), 540 deletions(-) diff --git a/README.md b/README.md index 0800ae38..9324e75c 100644 --- a/README.md +++ b/README.md @@ -6,544 +6,6 @@ Audio played by a Shairport Sync-powered device stays synchronised with the sour Shairport Sync runs on Linux and FreeBSD. It does not support AirPlay video or photo streaming. -This is the unstable "development" branch. Changes and updates are incorporated into this branch quickly. To access the stable version, where changes are made after due time, please switch to the "master" branch. - -More Information ----------- -Shairport Sync offers *full audio synchronisation*, a feature of AirPlay that previous implementations do not provide. Full audio synchronisation means that audio is played on the output device at exactly the time specified by the audio source. To accomplish this, Shairport Sync needs access to audio systems – such as `alsa` on Linux and `sndio` on FreeBSD – that provide very accurate timing information about output devices. Shairport Sync must have direct access to the output device used, which must be a real sound card capable of working with 44,100, 88,200 or 176,400 samples per second, interleaved PCM stereo of 8, 16, 24 or 32 bits. The default is 44,100 samples per second / 16 bits (you'll get a message in the logfile if there's a problem). - -Alternatively, Shairport Sync works well with PulseAudio, a widely used sound server found on many desktop Linuxes. While the timing information is not as accurate as that of `alsa` or `sndio`, it is often impractical to remove or disable PulseAudio. In that case, the `pa` backend can be used. An older backend for PulseAudio called `pulse` does not support synchronisation and is deprecated. - -For other use cases, Shairport Sync can provide synchronised audio output to a unix pipe or to standard output, or to audio systems that do not provide timing information. This could perhaps be described as *partial audio synchronisation*, where synchronised audio is provided by Shairport Sync, but what happens to it in the subsequent processing chain, before it reaches the listener's ear, is outside the control of Shairport Sync. - -For more about the motivation behind Shairport Sync, please see the wiki at https://github.com/mikebrady/shairport-sync/wiki. - -Synchronisation, Latency, "Stuffing" ---------- -The AirPlay protocol uses an agreed *latency* – the time difference, or delay, between the time represented by a sound sample's `timestamp` and the time it is actually played by the audio output device, typically a Digital to Audio Converter (DAC). The latency to be used is specified by the audio source when it negotiates with Shairport Sync. Most sources set a latency of two seconds. Recent versions of iTunes and forkedDaapd use a latency of just over 2.25 seconds. A latency of this length allows AirPlay players to correct for network delays, processing time variations and so on. - -As mentioned previously, Shairport Sync implements full audio synchronisation when used with `alsa`, `sndio` or PulseAudio systems. This is done by monitoring the timestamps present in data coming from the audio source and the timing information from the audio system, e.g. `alsa`. To maintain the latency required for exact synchronisation, if the output device is running slow relative to the source, Shairport Sync will delete frames of audio to allow the device to keep up. If the output device is running fast, Shairport Sync will insert frames to keep time. The number of frames inserted or deleted is so small as to be almost inaudible on normal audio material. Frames are inserted or deleted as necessary at pseudorandom intervals. Alternatively, with `libsoxr` support, Shairport Sync can resample the audio feed to ensure the output device can keep up. This is less obtrusive than insertion and deletion but requires a good deal of processing power — most embedded devices probably can't support it. The process of insertion/deletion or resampling is rather inelegantly called “stuffing”. - -Stuffing is not done for partial audio synchronisation – the audio samples are simply presented at exactly the right time to the next stage in the processing chain. - -Timestamps are referenced relative to the source computer's clock – the `source clock`, but timing must be done relative to the clock of the computer running Shairport Sync – the `local clock`. So, another thing Shairport Sync has to do is to synchronize the source clock and the local clock, and it does this usually to within a fraction of a millisecond, using a variant of NTP synchronisation protocols. - -What else? --------------- -* Better Volume Control — Shairport Sync offers finer control at very top and very bottom of the volume range. See http://tangentsoft.net/audio/atten.html for a good discussion of audio "attenuators", upon which volume control in Shairport Sync is modelled. See also the diagram of the volume transfer function in the documents folder. In addition, Shairport Sync can offer an extended volume control range on devices with a restricted range. -* Hardware Mute — Shairport Sync can mute properly if the hardware supports it. -* Support for the Apple ALAC decoder. -* Output bit depths of 8, 16, 24 and 32 bits, rather than the standard 16 bits. -* Fast Response — With hardware volume control, response is instantaneous; otherwise the response time is 0.15 seconds with `alsa`, 0.35 seconds with `sndio`. -* Non-Interruptible — Shairport Sync sends back a "busy" signal if it's already playing audio from another source, so other sources can't disrupt an existing Shairport Sync session. (If a source disappears without warning, the session automatically terminates after two minutes and the device becomes available again.) -* Metadata — Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in iOS. -* A native [MQTT](https://en.wikipedia.org/wiki/MQTT) interface for metadata and/or controls. -* Raw Audio — Shairport Sync can deliver raw PCM audio to standard output or to a pipe. This output is delivered synchronously with the source after the appropriate latency and is not interpolated or "stuffed" on its way through Shairport Sync. -* Autotools and Libtool Support — the Shairport Sync build process uses GNU `autotools` and `libtool` to examine and configure the build environment — important for portability and for cross compilation. Previous versions of Shairport looked at the current system to determine which packages were available, instead of looking at the target system for what packages were available. - -Heritage -------- -Shairport Sync is a substantial rewrite of the fantastic work done in Shairport 1.0 by James Laird and others — please see https://github.com/abrasive/shairport/blob/development/README.md#contributors-to-version-1x for a list of the contributors to Shairport 1.x and Shairport 0.x. From a "heritage" point of view, Shairport Sync is a fork of Shairport 1.0. - -Status ------- -Shairport Sync works on a wide variety of Linux devices and FreeBSD. It works on standard Ubuntu laptops, on the Raspberry Pi with Raspbian Stretch, Jessie and Wheezy, Arch Linux and OpenWrt, and it runs on a Linksys NSLU2 and a TP-Link 710N using OpenWrt. It works with built-in audio and with a variety of USB-connected audio amplifiers and DACs, including a cheapo USB "3D Sound" dongle, a first generation iMic and a Topping TP30 amplifier with a USB DAC input. - -Shairport Sync will work with PulseAudio, which is installed in many desktop Linuxes. PulseAudio normally runs in the *user mode* but can be configured to run in *system mode*, though this is not recommended. Shairport Sync can work with it in either mode. - -Shairport Sync runs well on the Raspberry Pi on USB and I2S cards. It can drive the built-in sound card – see the note below on configuring the Raspberry Pi to make best use of it. It runs well on the Raspberry Pi Zero W with a suitable USB or I2S card. - -Shairport Sync runs natively on FreeBSD using the `sndio` sound system. - -Shairport Sync runs on Ubuntu, OpenWrt, Debian, Arch Linux, Fedora and FreeBSD inside VMWare Fusion on a Mac, but synchronisation is inaccurate — possibly because the sound card is being emulated. - -In addition, Shairport Sync can output to standard output, pipes, `soundio` and `ao` devices using appropriate backends. - -For information about changes and updates, please refer to the RELEASENOTES.md file in the distribution. - -Building And Installing the Development Version ---------------------- -See [here](https://github.com/mikebrady/shairport-sync/blob/development/INSTALL.md) for simple installation instructions for modern Linux systems like Rapsberry Pis. - -The following procedures will build and install the `shairport-sync` application into your system. - -**Remove Old Versions of Shairport Sync and its Startup Scripts** - -You should check to see if `shairport-sync` is already installed – you can use the command `$ which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere. - -You should also remove the initialisation script files `/etc/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed in necessary. - -**FreeBSD** - -To build Shairport Sync from sources on FreeBSD please refer to [FREEBSD.md](https://github.com/mikebrady/shairport-sync/blob/master/FREEBSD.md). - -**Determine The Configuration Needed** - -Shairport Sync has a number of different "backends" that connnect it to the system's audio handling infrastructure. Most recent Linux distributions that have a GUI – including Ubuntu, Debian and others – use PulseAudio to handle sound. In such cases, it is inadvisable to attempt to disable or remove PulseAudio. Thus, if your system uses PulseAudio, you should build Shairport Sync with the PulseAudio backend. You can check to see if PulseAudio is running by opening a Terminal window and entering the command `$ pactl info`. Here is an example of what you'll get if PulseAudio is installed, though the exact details may vary: -``` -$ pactl info -Server String: unix:/run/user/1000/pulse/native -Library Protocol Version: 30 -Server Protocol Version: 30 -Is Local: yes -Client Index: 9 -Tile Size: 65472 -User Name: mike -Host Name: ubuntu -Server Name: pulseaudio -Server Version: 8.0 -Default Sample Specification: s16le 2ch 44100Hz -Default Channel Map: front-left,front-right -Default Sink: alsa_output.pci-0000_02_02.0.analog-stereo -Default Source: alsa_input.pci-0000_02_02.0.analog-stereo -Cookie: 96f9:3e8d -$ -``` -If PulseAudio in not installed, you'll get something like this: -``` -$ pactl info --bash: pactl: command not found -$ -``` -If your system does not use PulseAudio, then it is likely that it uses the Advanced Linux Sound Architecture (ALSA), so you should build Shairport Sync with the ALSA backend. By the way, many systems with PulseAudio also have ALSA (in fact, PulseAudio is effectively a client of ALSA); in those cases you should choose the PulseAudio backend. - -If PulseAudio is not installed, there is no necessity to install it for Shairport Sync. In fact, Shairport Sync works better without it. - -**Building** - -To build Shairport Sync from sources on Debian, Ubuntu, Raspbian, etc. follow these instructions. - -The following libraries are required: -* OpenSSL or mbed TLS (PolarSSL is supported but deprecated) -* Avahi -* ALSA and/or PulseAudio -* libdaemon -* autoconf -* automake -* libtool -* libpopt -* libconfig - -Optional: -* libsoxr -* libalac (This is a library containing the Apple ALAC decoder.) -* libmosquitto (This is to enable the mqtt-metadata-interface.) - -Many Linux distributions have Avahi and OpenSSL already in place, so normally it probably makes sense to choose those options rather than tinysvcmdns or mbed TLS. The `libsoxr` library is available in recent Linux distributions, but it requires lots of processor power — chances are an embedded processor won't be able to keep up. - -Debian, Ubuntu and Raspbian users can get the basics with: - -- `# apt-get install build-essential git xmltoman` – these may already be installed. -- `# apt-get install autoconf automake libtool libdaemon-dev libpopt-dev libconfig-dev` -- `# apt-get install libasound2-dev` for the ALSA libraries -- `# apt-get install libpulse-dev` for the PulseAudio libraries -- `# apt-get install avahi-daemon libavahi-client-dev` if you want to use Avahi (recommended). -- `# apt-get install libssl-dev` if you want to use OpenSSL and libcrypto, or use mbed TLS otherwise. -- `# apt-get install libmbedtls-dev` if you want to use mbed TLS, or use OpenSSL/libcrypto otherwise. You can still use PolarSSL with `apt-get install libpolarssl-dev` if you want to use PolarSSL, but it is deprecated as it's not longer being supported. -- `# apt-get install libsoxr-dev` if you want support for libsoxr-based resampling. This library is in many recent distributions; if not, instructions for how to build it from source for Rasbpian/Debian Wheezy are available at [LIBSOXR.md](https://github.com/mikebrady/shairport-sync/blob/master/LIBSOXR.md). - -If you wish to include the Apple ALAC decoder, you need install it first – please refer to the [ALAC](https://github.com/mikebrady/alac) repository for more information. - -**Download Shairport Sync:** -``` -$ git clone https://github.com/mikebrady/shairport-sync.git -``` - -Next, `cd` into the shairport-sync directory and execute the following commands: -``` -$ git checkout development -$ autoreconf -i -f -``` -(Note that the `autoreconf...` step may take some time on less powerful machines.) - -**Choose the appropriate `--with-*` options:** - -(Don't worry -- there's a recommended set of configuration options further down.) - -- `--with-alsa` include the ALSA backend module to audio to be output through the Advanced Linux Sound Architecture (ALSA) system directly. This is recommended for highest quality. -- `--with-pa` include the PulseAudio audio back end. This is recommended if your Linux installation already has PulseAudio installed. Although ALSA would be better, it requires direct and exclusive access to to a real (hardware) soundcard, and this is often impractical if PulseAudio is installed. -- `--with-stdout` include an optional backend module to enable raw audio to be output through standard output (stdout). -- `--with-pipe` include an optional backend module to enable raw audio to be output through a unix pipe. -- `--with-soundio` include an optional backend module to enable raw audio to be output through the soundio system. -- `--with-avahi` or `--with-tinysvcmdns` for mdns support. Avahi is a widely-used system-wide zero-configuration networking (zeroconf) service — it may already be in your system. If you don't have Avahi, or similar, then consider including tinysvcmdns, which is a tiny zeroconf service embedded inside the shairport-sync application itself. To enable multicast for `tinysvcmdns`, you may have to add a default route with the following command: `route add -net 224.0.0.0 netmask 224.0.0.0 eth0` (substitute the correct network port for `eth0`). You should not have more than one zeroconf service on the same system — bad things may happen, according to RFC 6762, §15. -- `--with-ssl=openssl`, `--with-ssl=mbedtls` or `--with-ssl=polarssl` (deprecated) for encryption and related utilities using either OpenSSL, mbed TLS or PolarSSL. -- `--with-soxr` for libsoxr-based resampling. -- `--with-piddir` for specifying where the PID file should be stored. This directory is normally chosen automatically. The directory must be writable. If you use this option, you may have to edit the init script to search for the PID file in your new location. -- `--with-metadata` to add support for Shairport Sync to pipe metadata to a compatible application of your choice. See https://github.com/mikebrady/shairport-sync-metadata-reader for a sample metadata reader. -- `--with-mqtt-client` to add support for publishing metadata and remote control via [MQTT](https://en.wikipedia.org/wiki/MQTT). -- `--with-configfiles` to install a configuration file and a separate sample file at the `make install` stage. Default is to install. An existing `/etc/shairport-sync.conf` will not be overwritten. -- `--with-pkg-config` to use pkg-config to find libraries. Default is to use pkg-config — this option is for special purpose use. -- `--with-apple-alac` to include the Apple ALAC Decoder. -- `--with-convolution` to include a convolution filter that can be used to apply effects such as frequency and phase correction, and a loudness filter that compensates for human ear non-linearity. Requires `libsndfile`. -- `--with-systemd` to include a script to create a Shairport Sync service that can optionally launch automatically at startup on `systemd`-based Linuxes. Default is not to to install. -- `--with-systemv` to include a script to create a Shairport Sync service that can optionally launch automatically at startup on System V based Linuxes. Default is not to to install. - -**Determine if it's a `systemd` or a "System V" installation:** - -If you wish to have Shairport Sync start automatically when your system boots, you need to figure out what so-called "init system" your system is using. (If you are using Shairport Sync with PulseAudio, as installed in many desktop systems, this section doesn't apply.) - -There are a number of init systems in use: `systemd`, `upstart` and "System V" among others, and it's actually difficult to be certain which one your system is using. Fortunately, for Shairport Sync, all you have to do is figure out if it's a `systemd` init system or not. If it is not a `systemd` init system, you can assume that it is either a System V init system or else it is compatible with a System V init system. Recent systems tend to use `systemd`, whereas older systems use `upstart` or the earlier System V init system. - -The easiest way to look at the first few lines of the `init` manual. Enter the command: - -``` -$ man init -``` -In a `systemd` system, the top lines of the `man` page make it clear that it's a `systemd` system, as follows (from Ubuntu 16.04): -``` -SYSTEMD(1) systemd SYSTEMD(1) - -NAME - systemd, init - systemd system and service manager -... -``` -Other init systems will look considerably different. For instance, in an `upstart` system, the top lines of the `man` page indicate it's using `upstart` system, as follows (from Ubuntu 14.04): - -``` -init(8) System Manager's Manual init(8) - -NAME - init - Upstart process management daemon - -SYNOPSIS - init [OPTION]... -... -``` -In a System V system, the top lines of the `man` page are as follows (from Debian 7.11): -``` -INIT(8) Linux System Administrator's Manual INIT(8) - -NAME - init, telinit - process control initialization - -SYNOPSIS - /sbin/init [ -a ] [ -s ] [ -b ] [ -z xxx ] [ 0123456Ss ] - /sbin/telinit [ -t SECONDS ] [ 0123456sSQqabcUu ] - /sbin/telinit [ -e VAR[=VAL] ] - -... -``` -If your system is definitely a `systemd` system, choose `--with-systemd` below. Otherwise, choose `--with-systemv`. - -**Choose the location of the configuration file** - -A final consideration is the location of the configuration file `shairport-sync.conf`. This will be placed in the directory specified by the `sysconfdir` configuration variable, which defaults to `/usr/local/etc`. This is normal in BSD Unixes, but is unusual in Linux. Hence, for Linux installations, you need to set the `sysconfdir` variable to `/etc` using the configuration setting `--sysconfdir=/etc`. - -**Sample `./configure` command with parameters for a typical Linux `systemd` installation:** - -Here is a recommended set of configuration options suitable for Linux installations that use `systemd`, such as Ubuntu 15.10 and later, and Raspbian Stretch and Jessie. It specifies both the ALSA and PulseAudio backends and includes a sample configuration file and an script for automatic startup on system boot: - -`$ ./configure --sysconfdir=/etc --with-alsa --with-pa --with-avahi --with-ssl=openssl --with-metadata --with-soxr --with-systemd` - -* Omit the `--with-soxr` if the libsoxr library is not available. -* For installation into a System V system, replace the `--with-systemd` with `--with-systemv`. -* If you intend to use Shairport Sync with PulseAudio in the standard user mode, it can not be a system service, so you should omit both `--with-systemd` and `--with-systemv`. - -**Build and Install the Application:** - -Enter: - -``` -$ make -``` - -to build the application. - -Assuming you have used the `./configure` settings suggested above, you can now install the application: - -``` -$ sudo make install -``` -The user and group `shairport-sync` will be created if necessary, and a runtime folder will be created at `/var/run/shairport-sync` if you have chosen `--with-systemv`. In addition, a `man` page, a default configuration file and automatic power-on startup script will be installed. - - -**Complete installation into to a `systemd` system** - -If you have chosen the `--with-systemd` configuration option, then, to enable Shairport Sync to start automatically at system startup, enter: - -``` -$ sudo systemctl enable shairport-sync -``` - -**Complete installation into a System V system** - -If you have chosen the `--with-systemv` configuration option, enter: -``` -$ sudo update-rc.d shairport-sync defaults 90 10 -``` - -**Man Page** - -You can view the man page here: http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/development/man/shairport-sync.html - -Configuring Shairport Sync --------- -There are two logically distinct parts to getting Shairport Sync to run properly on your machine: (1) starting and stopping it and (2) ensuring it has the right settings. - -**(1) Starting and Stopping:** -Starting and stopping Shairport Sync automatically is taken care of differently in different versions of Linux – see the previous section for an example of installing into a `systemd` or a System V based system. - -If you are using PulseAudio in the standard "user" mode, you can not start Shairport Sync automatically at boot time – it must be started when the user logs in to a GUI session. Various GUI tools exist to enable you to build a Startup Application. However, there does not appear to be a similar tool for terminating the program when you log out. Also, it the GUI switches to another user, the user's PulseAudio session is disconnected from the output device. - -**(2) Settings:** -To get the best from Shairport Sync, you’ll need to (a) give Shairport Sync a service name by which it will be seen in iTunes etc. and (b) specify the backend you wish to use - `alsa` for the ALSA backend, or `pa` for the PulseAudio back end. If only one backend is included at compilation, or if the backend is ALSA, there is no need to explicitly specify the backend. - -For the ALSA backend you may need to (c) specify the output device to use and (d) specify the name of the mixer volume control to use to control the output level. To get values for (b) and (c) you might need to explore the ALSA output devices with a program like `alsamixer` or similar. - -Shairport Sync reads settings from a configuration file at `/etc/shairport-sync.conf` (note that in FreeBSD it will be at `/usr/local/etc/shairport-sync.conf`). When you run `$sudo make install`, a sample configuration file is installed or updated at `/etc/shairport-sync.conf.sample` (`/usr/local/etc/shairport-sync.conf.sample` in FreeBSD). This contains all the setting groups and all the settings available, but they all are commented out (comments begin with `//`) so that default values are used. The file contains explanations of the settings, useful hints and suggestions. In addition, if the file doesn't already exist, a default configuration is installed, which should work in almost any system with a sound card. - -Settings in the configuration file are grouped. For instance, there is a `general` group within which you can use the `name` tag to set the service name. Suppose you wanted to set the name of the service to `Front Room`, give the service the password `secret` and use `libsoxr` interpolation, then you should do the following: - -``` -general = -{ - name = "Front Room"; - password = "secret"; - interpolation = "soxr"; - // ... other general settings -}; -``` -(Remember, anything preceded by `//` is a comment and will have no effect on the setting of Shairport Sync.) - -**Important:** You should *never* use an important password as the AirPlay password for a Shairport Sync player -- the password is stored in Shairport Sync's configuration file in plain text and is thus completely vulnerable. - -No backend is specified here, so it will default to the `alsa` backend if more than one back end has been compiled. To route the output to PulseAudio, add: - -``` - output_backend = "pa"; -``` -to the `general` stanza. - -The `alsa` group is used to specify properties of the output device. The most obvious setting is the name of the output device which you can set using the `output_device` tag. - -The following `alsa` group settings are very important for maximum performance. If your audio device has a mixer that can be use to control the volume, then Shairport Sync can use it to give instant response to volume and mute commands and it can offload some work from the processor. -* The `mixer_control_name` tag allows you to specify the name of the mixer volume control. -* The `mixer_device` tag allows you specify where the mixer is. By default, the mixer is on the `output_device`, so you only need to use the `mixer_device` tag if the mixer is elsewhere. This can happen if you specify a *device* rather than a *card* with the `output_device` tag, because normally a mixer is associated with a *card* rather than a device. Suppose you wish to use the output device `5` of card `hw:0` and the mixer volume-control named `PCM`: - -``` -alsa = -{ - output_device = "hw:0,5"; - mixer_device = "hw:0"; - mixer_control_name = "PCM"; - // ... other alsa settings -}; -``` - -The `pa` group is used to specify settings relevant to the PulseAudio backend. You can set the "Application Name" that will appear in the "Sound" control panel. You can also set the sink name that audio will be pushed to. Here's an example `pa` group configuration: - -``` -pa = -{ - application_name = "Shairport Sync - Bedroom"; - sink = "server_bedroom"; // Sink name or ID. If not specified, the default sink will be used. -}; -``` - -Shairport Sync can run programs just before it starts to play an audio stream and just after it finishes. You specify them using the `sessioncontrol` group settings `run_this_before_play_begins` and `run_this_after_play_ends`. This is to facilitate situations where something has to be done before and after playing, e.g. switching on an amplifier beforehand and switching it off afterwards. Set the `wait_for_completion` value to `"yes"` for Shairport Sync to wait until the respective commands have been completed before continuing. - -Note that the full path to the programs must be specified, and script files will not be executed unless they are marked as executable and have the appropriate shebang `#!/bin/...` as the first line. (This behaviour may be different from other Shairports.) - -Shairport Sync can run a program whenever the volume is set or changed. You specify them using the `general` group settings `run_this_when_volume_changes`. This is to facilitate situations where something has to be done when the volume changes, e.g. adjust an external mixer value. Set the `wait_for_completion` value to `"yes"` for Shairport Sync to wait until the command has been completed before continuing. Again, please note that the full path to the program must be specified, and script files will not be executed unless they are marked as executable and have the appropriate shebang `#!/bin/...` as the first line. - -Note: Shairport Sync can take configuration settings from command line options. This is mainly for backward compatibility, but sometimes still useful. For normal use, it is strongly recommended that you use the configuration file method. - -**Raspberry Pi** - -The Raspberry Pi has a built-in audio DAC that is connected to the device's headphone jack. An updated audio driver has greatly improved the quality of the output – see [#525](https://github.com/mikebrady/shairport-sync/issues/525) for details. To activate the updated driver, add the line: -``` -audio_pwm_mode=2 -``` -to `/boot/config.txt` and reboot. -Apart from a loud click when used for the first time after power-up, it is quite adequate for casual listening. A problem is that it declares itself to have a very large mixer volume control range – all the way from -102.38dB up to +4dB, a range of 106.38 dB. In reality, only the top 50 dB of it is in any way usable. To help get the most from the DAC, consider using the `volume_range_db` setting in the `general` group to instruct Shairport Sync to use the top of the DAC mixer's declared range. For example, if you set the `volume_range_db` figure to 50, the top 50 dB of the range will the used. With this setting on the Raspberry Pi, maximum volume will be +4dB and minimum volume will be -46dB, below which muting will occur. - -From a user's point of view, the effect of using this setting is to move the minimum usable volume all the way down to the bottom of the user's volume control, rather than have the minimum usable volume concentrated very close to the maximum volume. - -Another setting to consider is the `general` `drift_tolerance_in_seconds` setting: you should set it to a larger tolerance, say 10 milliseconds – `drift_tolerance_in_seconds=0.010;` – to reduce the amount of overcorrection that seems to occur when using the Raspberry Pi's built-in DAC. - -*Command Line Arguments* - -As previously mentioned, you can use command line arguments to provide settings to Shairport Sync as before, though newer settings will only be available via the configuration file. For full information, please read the Shairport Sync `man` page, also available at http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/development/man/shairport-sync.html. - -Apart from the following options, all command line options can be replaced by settings in the configuration file. Here is a brief description of command line options that are not replicated by settings in the settings file. - -* The `-c` option allows you to specify the location of the configuration file. -* The `-V` option gives you version information about Shairport Sync and then quits. -* The `-d` option causes Shairport Sync to properly daemonise itself, that is, to run in the background. You may need sudo privileges for this. -* The `-k` option causes Shairport Sync to kill an existing Shairport Sync daemon. You may need to have sudo privileges for this. - -The System V init script at `/etc/init.d/shairport-sync` has a bare minimum : -`-d`. Basically all it does is put the program in daemon mode. The program will read its settings from the configuration file. - -Examples --------- - -Here are some examples of complete configuration files. - -``` -general = { - name = "Joe's Stereo"; -}; - -alsa = { - output_device = "hw:0"; -}; -``` - -This gives the service a particular name — "Joe's Stereo" and specifies that audio device hw:0 be used. - -For best results with the `alsa` backend — including getting true mute and instant response to volume control and pause commands — you should access the hardware volume controls. Use `amixer` or `alsamixer` or similar to discover the name of the mixer control to be used as the `mixer_control_name`. - -Here is an example for for a Raspberry Pi using its internal soundcard — device hw:0 — that drives the headphone jack: -``` -general = { - name = "Mike's Boombox"; -}; - -alsa = { - output_device = "hw:0"; - mixer_control_name = "PCM"; -}; -``` - -Here is an example of using soxr-based resampling and driving a Topping TP30 Digital Amplifier, which has an integrated USB DAC and which is connected as audio device `hw:1`: -``` -general = { - name = "Kitchen"; - interpolation = "soxr"; -}; - -alsa = { - output_device = "hw:1"; - mixer_control_name = "PCM"; -}; -``` - -For a cheapo "3D Sound" USB card (Stereo output and input only) on a Raspberry Pi: -``` -general = { - name = "Front Room"; -}; - -alsa = { - output_device = "hw:1"; - mixer_control_name = "Speaker"; -}; -``` - -For a first generation Griffin iMic on a Raspberry Pi: -``` -general = { - name = "Attic"; -}; - -alsa = { - output_device = "hw:1"; - mixer_control_name = "PCM"; -}; -``` - -For an NSLU2, which has no internal sound card, there appears to be a bug in ALSA — you can not specify a device other than "default". Thus: - -On an NSLU2, to drive a first generation Griffin iMic: -``` -general = { - name = "Den"; -}; - -alsa = { - mixer_control_name = "PCM"; -}; -``` - -On an NSLU2, to drive the "3D Sound" USB card: -``` -general = { - name = "TV Room"; -}; - -alsa = { - mixer_control_name = "Speaker"; -}; -``` - -Finally, here is an example of using the PulseAudio backend: -``` -general = { - name = "Zoe's Computer"; - output_backend = "pa"; -}; - -``` - - -Metadata --------- - -Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in iOS. - -**Metadata broadcasting over UDP** - -As an alternative to sending metadata to a pipe, the `socket_address` and `socket_port` tags may be set in the metadata group to cause Shairport Sync to broadcast UDP packets containing the track metadata. - -The advantage of UDP is that packets can be sent to a single listener or, if a multicast address is used, to multiple listeners. It also allows metadata to be routed to a different host. However UDP has a maximum packet size of about 65000 bytes; while large enough for most data, Cover Art will often exceed this value. Any metadata exceeding this limit will not be sent over the socket interface. The maximum packet size may be set with the `socket_msglength` tag to any value between 500 and 65000 to control this - lower values may be used to ensure that each UDP packet is sent in a single network frame. The default is 500. Other than this restriction, metadata sent over the socket interface is identical to metadata sent over the pipe interface. - -The UDP metadata format is very simple - the first four bytes are the metadata *type*, and the next four bytes are the metadata *code* (both are sent in network byte order - see https://github.com/mikebrady/shairport-sync-metadata-reader for a definition of those terms). The remaining bytes of the packet, if any, make up the raw value of the metadata. - -Latency -------- -Latency is the exact time from a sound signal's original timestamp until that signal actually "appears" on the output of the audio output device, usually a Digital to Audio Converter (DAC), irrespective of any internal delays, processing times, etc. in the computer. - -Shairport Sync uses latencies supplied by the source, typically either 2 seconds or just over 2.25 seconds. You shouldn't need to change them. - -Problems can arise when you are trying to synchronise with speaker systems — typically surround-sound home theatre systems — that have their own inherent delays. You can compensate for an inherent delay using the appropriate backend (typically `alsa`) `audio_backend_latency_offset_in_seconds`. Set this offset (in frames) to compensate for a fixed delay in the audio back end; for example, if the output device delays by 100 ms, set this to -0.1. - -Resynchronisation -------------- -Shairport Sync actively maintains synchronisation with the source. -If synchronisation is lost — say due to a busy source or a congested network — Shairport Sync will mute its output and resynchronise. The loss-of-sync threshold is a very conservative 0.050 seconds — i.e. the actual time and the expected time must differ by more than 50 ms to trigger a resynchronisation. Smaller disparities are corrected by insertions or deletions, as described above. -* You can vary the resync threshold, or turn resync off completely, with the `general` `resync_threshold_in_seconds` setting. - -Tolerance ---------- -Playback synchronisation is allowed to wander — to "drift" — a small amount before attempting to correct it. The default is 0.002 seconds, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the `statistics` setting to monitor correction levels. Corrections should not greatly exceed net corrections. -* You can vary the tolerance with the `general` `drift_tolerance_in_seconds` setting. - -Some Statistics ---------------- -If you turn on the `general` `statistics` setting, a heading like this will be output to the console or log file: -``` -sync error in milliseconds, net correction in ppm, corrections in ppm, total packets, missing packets, late packets, too late packets, resend requests, min DAC queue size, min buffer occupancy, max buffer occupancy -``` -This will be followed by the statistics themselves at regular intervals, for example: -``` - -1.3, 25.9, 25.9, 3758, 0, 0, 0, 0, 4444, 263, 270 - -2.0, 96.0, 96.0, 7516, 0, 0, 0, 0, 5357, 260, 267 - -2.0, 96.0, 96.0, 11274, 0, 0, 0, 0, 4956, 262, 268 - -2.0, 94.5, 94.5, 15032, 0, 0, 0, 0, 5430, 263, 267 - -2.0, 99.8, 99.8, 18790, 0, 0, 0, 0, 5047, 261, 268 - -2.0, 91.5, 91.5, 22548, 0, 0, 0, 0, 5258, 260, 267 - -2.0, 90.7, 90.7, 26306, 0, 0, 0, 0, 5279, 262, 267 - -2.0, 96.0, 96.0, 30064, 0, 0, 0, 0, 5551, 260, 266 -``` - -"Sync error in milliseconds" is the average deviation from exact synchronisation. The first line of the example above indicates that the output is on average 1.3 milliseconds behind exact synchronisation. Sync is allowed to drift by the `general` `drift_tolerance_in_seconds` setting — (± 0.002 seconds) by default — before a correction will be made. - -"Net correction in ppm" is actually the net sum of corrections — the number of frame insertions less the number of frame deletions — given as a moving average in parts per million. After an initial settling period, it represents the drift — the divergence between the rate at which frames are generated at the source and the rate at which the output device consumes them. The first line of the example above indicates that the output device is consuming frames 25.9 ppm faster than the source is generating them. The subsequent lines indicate that the stable actual difference in the clocks is around 95 ppm. - -"Corrections in ppm" is the number of frame insertions plus the number of frame deletions (i.e. the total number of corrections), given as a moving average in parts per million. The closer this is to the net corrections, the fewer "unnecessary" corrections that are being made. Third party programs tend to have much larger levels of corrections. - -For reference, a drift of one second per day is approximately 11.57 ppm. Left uncorrected, even a drift this small between two audio outputs will be audible after a short time. The above sample is from an Ethernet-connected iMac driving a WiFi-connected Raspberry Pi 3 using an IQaudIO Pi-DigiAMP+. - -It's not unusual to have resend requests, late packets and even missing packets if some part of the connection to the Shairport Sync device is over WiFi. Late packets can sometimes be asked for and received multiple times. Sometimes late packets are sent and arrive too late, but have already been sent and received in time, so weren't needed anyway... - -"Min DAC queue size" is the minimum size the queue of samples in the output device's hardware buffer was measured at. It is meant to stand at 0.15 seconds = 6,615 frames at 44,100 frames per second, and will go low if the processor is very busy. If it goes below about 2,000 then it's an indication that the processor can't really keep up. - -WiFi Issues ---------- -If you are using WiFi, you should ensure that WiFi power management is off. See [TROUBLESHOOTING](https://github.com/mikebrady/shairport-sync/blob/development/TROUBLESHOOTING.md) for more details. - -Troubleshooting ---------------- -Please refer to [TROUBLESHOOTING](https://github.com/mikebrady/shairport-sync/blob/development/TROUBLESHOOTING.md) for a few hints, contributed by users. +**Warning** +This is the "unstable" branch. It is used for very preliminary unfinished or untested code and can change at any time and without warning. From c807513422b9344ac4439235253f1bd37ba0df1c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 24 Jan 2019 16:33:50 +0000 Subject: [PATCH 392/569] quieten a few debug messages, bring a standard one forward. --- audio_alsa.c | 16 ++++++++-------- rtsp.c | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index d77bfffa..b71d3b6b 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1248,7 +1248,7 @@ int do_close() { debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr)); // flush also closes the device - debug(1, "closing alsa handle"); + debug(2, "alsa: do_close() -- closing alsa handle"); if ((derr = snd_pcm_close(alsa_handle))) debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr)); alsa_handle = NULL; @@ -1273,12 +1273,12 @@ int play(void *buf, int samples) { if (alsa_backend_state == abm_disconnected) { ret = do_open(); if (ret == 0) - debug(1, "alsa: play() -- opened output device"); + debug(2, "alsa: play() -- opened output device"); } if (ret == 0) { if (alsa_backend_state != abm_playing) { - debug(1, "alsa: play() -- alsa_backend_state => abm_playing"); + debug(2, "alsa: play() -- alsa_backend_state => abm_playing"); alsa_backend_state = abm_playing; } ret = do_play(buf, samples); @@ -1295,15 +1295,15 @@ static void flush(void) { do_mute(1); if (alsa_backend_state != abm_disconnected) { // must be playing or connected... if (config.keep_dac_busy != 0) { - debug(1, "alsa: flush() -- alsa_backend_state => abm_connected."); + debug(2, "alsa: flush() -- alsa_backend_state => abm_connected."); alsa_backend_state = abm_connected; } else { - debug(1, "alsa: flush() -- closing the output device"); + debug(2, "alsa: flush() -- closing the output device"); do_close(); // will change the state to disconnected - debug(1, "alsa: flush() -- alsa_backend_state => abm_disconnected."); + debug(2, "alsa: flush() -- alsa_backend_state => abm_disconnected."); } } else - debug(1, "alsa: flush() -- called on a disconnected alsa backend"); + debug(3, "alsa: flush() -- called on a disconnected alsa backend"); debug_mutex_unlock(&alsa_mutex, 3); pthread_cleanup_pop(0); // release the mutex } @@ -1527,4 +1527,4 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { usleep(sleep_time_ms * 1000); // has a cancellation point in it } pthread_exit(NULL); -} \ No newline at end of file +} diff --git a/rtsp.c b/rtsp.c index d854ae1b..f7c6acce 100644 --- a/rtsp.c +++ b/rtsp.c @@ -981,7 +981,7 @@ 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(2, "Connection %d: SETUP with UDP ports Control: %d, Timing: %d and Audio: %d.", + debug(1, "Connection %d: SETUP with UDP ports Control: %d, Timing: %d and Audio: %d.", conn->connection_number, conn->local_control_port, conn->local_timing_port, conn->local_audio_port); From c998400f7cf07dbd4780e6bc8e70903cbe54156f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 26 Jan 2019 15:52:21 +0000 Subject: [PATCH 393/569] restore includes of openssl/mbedtls/polarssl includes --- metadata_hub.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/metadata_hub.c b/metadata_hub.c index 17b221a8..f399f200 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -45,6 +45,18 @@ #include "dacp.h" #include "metadata_hub.h" +#ifdef CONFIG_MBEDTLS +#include +#endif + +#ifdef CONFIG_POLARSSL +#include +#endif + +#ifdef CONFIG_OPENSSL +#include +#endif + pthread_rwlock_t metadata_hub_re_lock = PTHREAD_RWLOCK_INITIALIZER; struct track_metadata_bundle *track_metadata; // used for a temporary track metadata store From 4188eba571939f4b7434309f34ba15b395854289 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 27 Jan 2019 13:48:06 +0000 Subject: [PATCH 394/569] Fix a cause of a memory leak using OpenSSL for Base 64 encoding. --- common.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common.c b/common.c index 8430c29d..043618f1 100644 --- a/common.c +++ b/common.c @@ -2,7 +2,7 @@ * Utility routines. This file is part of Shairport. * Copyright (c) James Laird 2013 * The volume to attenuation function vol2attn copyright (c) Mike Brady 2014 - * Further changes (c) Mike Brady 2014 -- 2018 + * Further changes and additions (c) Mike Brady 2014 -- 2019 * All rights reserved. * * Permission is hereby granted, free of charge, to any person @@ -415,7 +415,7 @@ char *base64_enc(uint8_t *input, int length) { buf[bptr->length - 1] = 0; } - BIO_free_all(bmem); + BIO_free_all(b64); pthread_setcancelstate(oldState, NULL); return buf; @@ -444,7 +444,6 @@ uint8_t *base64_dec(char *input, int *outlen) { nread = BIO_read(b64, buf, bufsize); - // BIO_free_all(bmem); BIO_free_all(b64); *outlen = nread; @@ -500,6 +499,7 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { default: die("bad rsa mode"); } + RSA_free(rsa); pthread_setcancelstate(oldState, NULL); return out; } From 1f67f66735c723d3f934fd0fc605bd2151743243 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 27 Jan 2019 13:49:39 +0000 Subject: [PATCH 395/569] use the correct label -- CONFIG_OPENSSL -- rather than the incorrect CONFIG_SSL for including code to create a filename using the OpenSSL library. --- metadata_hub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metadata_hub.c b/metadata_hub.c index f399f200..f6c92ae8 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -219,7 +219,7 @@ char *metadata_write_image_file(const char *buf, int len) { uint8_t img_md5[16]; // uint8_t ap_md5[16]; -#ifdef CONFIG_SSL +#ifdef CONFIG_OPENSSL MD5_CTX ctx; MD5_Init(&ctx); MD5_Update(&ctx, buf, len); From ea770b2b13df95cb00e30366855917b78c0290ce Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 27 Jan 2019 13:50:54 +0000 Subject: [PATCH 396/569] Remove some unnecessary code "protecting" some use of the OpenSSL library. --- shairport.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/shairport.c b/shairport.c index 292acf25..15f8b9c8 100644 --- a/shairport.c +++ b/shairport.c @@ -2,7 +2,7 @@ * Shairport, an Apple Airplay receiver * Copyright (c) James Laird 2013 * All rights reserved. - * Modifications (c) Mike Brady 2014--2018 + * Modifications and additions (c) Mike Brady 2014--2019 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -1602,14 +1602,10 @@ int main(int argc, char **argv) { uint8_t ap_md5[16]; #ifdef CONFIG_OPENSSL - // can't imagine this is necessary, but here it is anyway - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable MD5_CTX ctx; MD5_Init(&ctx); MD5_Update(&ctx, config.service_name, strlen(config.service_name)); MD5_Final(ap_md5, &ctx); - pthread_setcancelstate(oldState, NULL); #endif #ifdef CONFIG_MBEDTLS From 7639bfadbf87977d1524206cec5cc33c17bac358 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 27 Jan 2019 13:52:21 +0000 Subject: [PATCH 397/569] Fix a number of memory leaks and one #include error when using the OpenSSL library. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 530bf763..af9268e8 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.3d48], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d49], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 56bf8862066d9d4974f2539f0bfbbeb6806e74bd Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 27 Jan 2019 14:19:39 +0000 Subject: [PATCH 398/569] gracefully handle situation when the output device is already in use; release it properly when finished. --- audio_alsa.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index d77bfffa..0b2a91e1 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -634,7 +634,7 @@ static int init(int argc, char **argv) { // set up default values first alsa_backend_state = abm_disconnected; // startup state - debug(1, "alsa: init() -- alsa_backend_state => abm_disconnected."); + debug(2, "alsa: init() -- alsa_backend_state => abm_disconnected."); set_period_size_request = 0; set_buffer_size_request = 0; config.alsa_use_hardware_mute = 0; // don't use it by default @@ -1166,7 +1166,7 @@ int do_play(void *buf, int samples) { } const uint64_t start_measurement_from_this_frame = - (2 * config.output_rate) / 352; // two seconds of framesÉ + (2 * config.output_rate) / 352; // two seconds of frames frame_index++; @@ -1248,7 +1248,7 @@ int do_close() { debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr)); // flush also closes the device - debug(1, "closing alsa handle"); + debug(2, "closing alsa handle"); if ((derr = snd_pcm_close(alsa_handle))) debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr)); alsa_handle = NULL; @@ -1273,12 +1273,12 @@ int play(void *buf, int samples) { if (alsa_backend_state == abm_disconnected) { ret = do_open(); if (ret == 0) - debug(1, "alsa: play() -- opened output device"); + debug(2, "alsa: play() -- opened output device"); } if (ret == 0) { if (alsa_backend_state != abm_playing) { - debug(1, "alsa: play() -- alsa_backend_state => abm_playing"); + debug(2, "alsa: play() -- alsa_backend_state => abm_playing"); alsa_backend_state = abm_playing; } ret = do_play(buf, samples); @@ -1295,15 +1295,15 @@ static void flush(void) { do_mute(1); if (alsa_backend_state != abm_disconnected) { // must be playing or connected... if (config.keep_dac_busy != 0) { - debug(1, "alsa: flush() -- alsa_backend_state => abm_connected."); + debug(2, "alsa: flush() -- alsa_backend_state => abm_connected."); alsa_backend_state = abm_connected; } else { - debug(1, "alsa: flush() -- closing the output device"); + debug(2, "alsa: flush() -- closing the output device"); do_close(); // will change the state to disconnected - debug(1, "alsa: flush() -- alsa_backend_state => abm_disconnected."); + debug(2, "alsa: flush() -- alsa_backend_state => abm_disconnected."); } } else - debug(1, "alsa: flush() -- called on a disconnected alsa backend"); + debug(3, "alsa: flush() -- called on a disconnected alsa backend"); debug_mutex_unlock(&alsa_mutex, 3); pthread_cleanup_pop(0); // release the mutex } @@ -1462,14 +1462,14 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { if ((alsa_backend_state == abm_disconnected) && (config.keep_dac_busy != 0)) { // open the dac and move to abm_connected mode if (do_open() == 0) - debug(1, "alsa: alsa_buffer_monitor_thread_code() -- output device opened; alsa_backend_state => abm_connected"); + debug(2, "alsa: alsa_buffer_monitor_thread_code() -- output device opened; alsa_backend_state => abm_connected"); } else if ((alsa_backend_state == abm_connected) && (config.keep_dac_busy == 0)) { stall_monitor_start_time = 0; frame_index = 0; measurement_data_is_valid = 0; - debug(1, "alsa: alsa_buffer_monitor_thread_code() -- closing the output device"); + debug(2, "alsa: alsa_buffer_monitor_thread_code() -- closing the output device"); do_close(); - debug(1, "alsa: alsa_buffer_monitor_thread_code() -- alsa_backend_state => abm_disconnected"); + debug(2, "alsa: alsa_buffer_monitor_thread_code() -- alsa_backend_state => abm_disconnected"); } // now, if the backend is not in the abm_disconnected state // and config.keep_dac_busy is true (at the present, this has to be the case to be in the @@ -1527,4 +1527,4 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { usleep(sleep_time_ms * 1000); // has a cancellation point in it } pthread_exit(NULL); -} \ No newline at end of file +} From 90ed88dbc556c78c0d4add533c0b6b7195927631 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 27 Jan 2019 15:15:05 +0000 Subject: [PATCH 399/569] add hooks for active mode start and end and add in the timeout into the configuration file. --- scripts/shairport-sync.conf | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index b34c8455..031ce572 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -49,15 +49,22 @@ general = // as "org.gnome.ShairportSync" on the whichever bus you specify here: "system" (default) or "session". }; -// Advanced parameters for controlling how Shairport Sync runs a play session +// Advanced parameters for controlling how Shairport Sync stays active and how it runs a session sessioncontrol = { // run_this_before_play_begins = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line // run_this_after_play_ends = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line + +// "active" mode starts when play begins and ends when the active_mode_timeout has elapsed after play ends, unless another play session starts before the timeout has fully elapsed. +// run_this_before_entering_active_mode = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line +// run_this_after_exiting_active_mode = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line +// active_mode_timeout = 10; // wait for this number of seconds after play ends before leaving the active mode, unless another play session begins. + // run_this_if_an_unfixable_error_is_detected = "/full/path/to/application and args"; // if a problem occurs that can't be cleared by Shairport Sync itself, hook a program on here to deal with it. An error code-string is passed as the last argument. // Many of these "unfixable" problems are caused by malfunctioning output devices, and sometimes it is necessary to restart the whole device to clear the problem. // You could hook on a program to do this automatically, but beware -- the device may then power off and restart without warning! // wait_for_completion = "no"; // set to "yes" to get Shairport Sync to wait until the "run_this..." applications have terminated before continuing + // allow_session_interruption = "no"; // set to "yes" to allow another device to interrupt Shairport Sync while it's playing from an existing audio source // session_timeout = 120; // wait for this number of seconds after a source disappears before terminating the session and becoming available again. }; From aa5698fce22c20fc2dcc8e05b14e0c0c2a716e29 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 27 Jan 2019 15:53:36 +0000 Subject: [PATCH 400/569] Remove the preflight call - don't seem to need it. --- audio.h | 2 -- audio_alsa.c | 10 ---------- audio_ao.c | 1 - audio_dummy.c | 1 - audio_jack.c | 3 +-- audio_pa.c | 1 - audio_pipe.c | 1 - audio_sndio.c | 1 - audio_soundio.c | 1 - audio_stdout.c | 1 - 10 files changed, 1 insertion(+), 21 deletions(-) diff --git a/audio.h b/audio.h index 1767e416..a4f0f178 100644 --- a/audio.h +++ b/audio.h @@ -22,8 +22,6 @@ typedef struct { void (*start)(int sample_rate, int sample_format); // block of samples - int (*preflight)(void *buf, - int samples); // say you are about to send these samples (before interpolation) int (*play)(void *buf, int samples); void (*stop)(void); diff --git a/audio_alsa.c b/audio_alsa.c index e9ac05f9..7fae429b 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -48,7 +48,6 @@ static int init(int argc, char **argv); static void deinit(void); static void start(int i_sample_rate, int i_sample_format); static int play(void *buf, int samples); -int preflight(void *buf, int samples); static void stop(void); static void flush(void); int delay(long *the_delay); @@ -1308,15 +1307,6 @@ static void flush(void) { pthread_cleanup_pop(0); // release the mutex } -int preflight(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) { - uint64_t time_now = - get_absolute_time_in_fp(); // this is to regulate access by the silence filler thread - uint64_t standoff_time = 60; // milliseconds - standoff_time = (standoff_time << 32) / 1000; - most_recent_write_time = time_now + standoff_time; - return 0; -} - static void stop(void) { // debug(2,"audio_alsa stop called."); flush(); // flush will also close the device if appropriate diff --git a/audio_ao.c b/audio_ao.c index 30f80d8d..42147bab 100644 --- a/audio_ao.c +++ b/audio_ao.c @@ -134,7 +134,6 @@ audio_output audio_ao = {.name = "ao", .is_running = NULL, .flush = NULL, .delay = NULL, - .preflight = NULL, .play = &play, .volume = NULL, .parameters = NULL, diff --git a/audio_dummy.c b/audio_dummy.c index 69518cd0..d8dea87f 100644 --- a/audio_dummy.c +++ b/audio_dummy.c @@ -65,7 +65,6 @@ audio_output audio_dummy = {.name = "dummy", .is_running = NULL, .flush = NULL, .delay = NULL, - .preflight = NULL, .play = &play, .volume = NULL, .parameters = NULL, diff --git a/audio_jack.c b/audio_jack.c index 9a584385..c8a8b9ee 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -66,7 +66,6 @@ audio_output audio_jack = {.name = "jack", .is_running = &jack_is_running, .flush = &jack_flush, .delay = &jack_delay, - .preflight = NULL, .play = &play, .volume = NULL, .parameters = NULL, @@ -434,4 +433,4 @@ void jack_stop(void) { // debug(1, "jack stop"); if (config.jack_auto_client_disconnect) jack_close(); -} \ No newline at end of file +} diff --git a/audio_pa.c b/audio_pa.c index 4ee20c75..cd321bd9 100644 --- a/audio_pa.c +++ b/audio_pa.c @@ -312,7 +312,6 @@ audio_output audio_pa = {.name = "pa", .flush = &flush, .delay = &pa_delay, .play = &play, - .preflight = NULL, .volume = NULL, .parameters = NULL, .mute = NULL}; diff --git a/audio_pipe.c b/audio_pipe.c index 0d4a42fd..89a09a0c 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -139,7 +139,6 @@ audio_output audio_pipe = {.name = "pipe", .flush = NULL, .delay = NULL, .play = &play, - .preflight = NULL, .volume = NULL, .parameters = NULL, .mute = NULL}; diff --git a/audio_sndio.c b/audio_sndio.c index e106b15d..f22ed5af 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -49,7 +49,6 @@ audio_output audio_sndio = {.name = "sndio", .flush = &flush, .delay = &delay, .play = &play, - .preflight = NULL, .volume = NULL, .parameters = NULL, .mute = NULL}; diff --git a/audio_soundio.c b/audio_soundio.c index 8942554d..f57f9933 100644 --- a/audio_soundio.c +++ b/audio_soundio.c @@ -222,7 +222,6 @@ audio_output audio_soundio = {.name = "soundio", .flush = &flush, .delay = NULL, .play = &play, - .preflight = NULL, .volume = NULL, .parameters = ¶meters, .mute = NULL}; diff --git a/audio_stdout.c b/audio_stdout.c index 37d6f781..0438b1af 100644 --- a/audio_stdout.c +++ b/audio_stdout.c @@ -86,7 +86,6 @@ audio_output audio_stdout = {.name = "stdout", .flush = NULL, .delay = NULL, .play = &play, - .preflight = NULL, .volume = NULL, .parameters = NULL, .mute = NULL}; From c5ff0dfed0d11605d75c4795ab059e903fa42c02 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 30 Jan 2019 09:24:10 +0000 Subject: [PATCH 401/569] Rebuild the volume control calculations. Implement software mute. Try to delay alsa initialisation as long as possible. --- audio.h | 3 +- audio_alsa.c | 224 ++++++++++++++++------------- common.h | 2 + player.c | 396 ++++++++++++++++++++++++--------------------------- player.h | 17 +-- 5 files changed, 324 insertions(+), 318 deletions(-) diff --git a/audio.h b/audio.h index a4f0f178..24536b91 100644 --- a/audio.h +++ b/audio.h @@ -47,7 +47,8 @@ typedef struct { void (*parameters)(audio_parameters *info); // may be NULL, in which case software muting is used. - void (*mute)(int do_mute); + // also, will return a 1 if it is actually using the mute facility, 0 otherwise + int (*mute)(int do_mute); } audio_output; diff --git a/audio_alsa.c b/audio_alsa.c index 7fae429b..0b94e812 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -59,7 +59,7 @@ static void volume(double vol); void do_volume(double vol); static void parameters(audio_parameters *info); -static void mute(int do_mute); +int mute(int do_mute); // returns true if it actually is allowed to use the mute static double set_volume; static int output_method_signalled = 0; @@ -93,6 +93,7 @@ static unsigned int desired_sample_rate; static enum sps_format_t sample_format; int frame_size; // in bytes for interleaved stereo +int alsa_device_initialised; // boolean to ensure the initialisation is only done once snd_pcm_t *alsa_handle = NULL; static snd_pcm_hw_params_t *alsa_params = NULL; static snd_pcm_sw_params_t *alsa_swparams = NULL; @@ -620,12 +621,117 @@ int open_alsa_device(void) { return result; } +int do_alsa_device_init_if_needed() { + debug(1,"do_alsa_device_init_if_needed()"); + int response = 0; + // do any alsa device initialisation (general case) if needed + // at present, this is only needed if a hardware mixer is being used + // if there's a hardware mixer, it needs to be initialised before first use + if (alsa_device_initialised == 0) { + alsa_device_initialised = 1; + if (hardware_mixer) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable + + if (alsa_mix_dev == NULL) + alsa_mix_dev = alsa_out_dev; + + // Now, start trying to initialise the alsa device with the settings obtained + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 1000, 1); + if (open_mixer() == 1) { + if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) < + 0) + debug(1, "Can't read mixer's [linear] min and max volumes."); + else { + if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb, + &alsa_mix_maxdb) == 0) { + + audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff + audio_alsa.parameters = ¶meters; // likewise the parameters stuff + if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) { + // For instance, the Raspberry Pi does this + debug(1, "Lowest dB value is a mute"); + mixer_volume_setting_gives_mute = 1; + alsa_mix_mute = SND_CTL_TLV_DB_GAIN_MUTE; // this may not be necessary -- it's always + // going to be SND_CTL_TLV_DB_GAIN_MUTE, right? + // debug(1, "Try minimum volume + 1 as lowest true attenuation value"); + if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1, + &alsa_mix_mindb) != 0) + debug(1, "Can't get dB value corresponding to a minimum volume + 1."); + } + debug(1, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0, + (1.0 * alsa_mix_maxdb) / 100.0); + } else { + // use the linear scale and do the db conversion ourselves + warn("The hardware mixer specified -- \"%s\" -- does not have " + "a dB volume scale.", + alsa_mix_ctrl); + + if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) { + warn("Cannot open control \"%s\"", alsa_mix_dev); + response = -1; + } + if (snd_ctl_elem_id_malloc(&elem_id) < 0) { + debug(1, "Cannot allocate memory for control \"%s\"", alsa_mix_dev); + elem_id = NULL; + response = -2; + } else { + snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_MIXER); + snd_ctl_elem_id_set_name(elem_id, alsa_mix_ctrl); + + if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) { + debug(1, "alsa: hardware mixer \"%s\" selected, with dB volume from %f to %f.", + alsa_mix_ctrl, (1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0); + has_softvol = 1; + audio_alsa.volume = + &volume; // insert the volume function now we know it can do dB stuff + audio_alsa.parameters = ¶meters; // likewise the parameters stuff + } else { + debug(1, "Cannot get the dB range from the volume control \"%s\"", alsa_mix_ctrl); + } + } + /* + debug(1, "Min and max volumes are %d and + %d.",alsa_mix_minv,alsa_mix_maxv); + alsa_mix_maxdb = 0; + if ((alsa_mix_maxv!=0) && (alsa_mix_minv!=0)) + alsa_mix_mindb = + -20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0)); + else if (alsa_mix_maxv!=0) + alsa_mix_mindb = -20*100*log10(alsa_mix_maxv*1.0); + audio_alsa.volume = &linear_volume; // insert the linear volume function + audio_alsa.parameters = ¶meters; // likewise the parameters stuff + debug(1,"Max and min dB calculated are %d and + %d.",alsa_mix_maxdb,alsa_mix_mindb); + */ + } + } + if (((config.alsa_use_hardware_mute == 1) && + (snd_mixer_selem_has_playback_switch(alsa_mix_elem))) || + mixer_volume_setting_gives_mute) { + audio_alsa.mute = &mute; // insert the mute function now we know it can do muting stuff + // debug(1, "Has mixer and mute ability we will use."); + } else { + // debug(1, "Has mixer but not using hardware mute."); + } + close_mixer(); + } + debug_mutex_unlock(&alsa_mutex, 3); // release the mutex + + pthread_cleanup_pop(0); + pthread_setcancelstate(oldState, NULL); + } + } + return response; +} + static int init(int argc, char **argv) { // for debugging snd_output_stdio_attach(&output, stdout, 0); // debug(2,"audio_alsa init called."); int response = 0; // this will be what we return to the caller. + alsa_device_initialised = 0; const char *str; int value; // double dvalue; @@ -871,101 +977,9 @@ static int init(int argc, char **argv) { } debug(1, "alsa: output device name is \"%s\".", alsa_out_dev); - - if (hardware_mixer) { - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable - - if (alsa_mix_dev == NULL) - alsa_mix_dev = alsa_out_dev; - - // Now, start trying to initialise the alsa device with the settings obtained - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 1000, 1); - if (open_mixer() == 1) { - if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) < - 0) - debug(1, "Can't read mixer's [linear] min and max volumes."); - else { - if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb, - &alsa_mix_maxdb) == 0) { - - audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff - audio_alsa.parameters = ¶meters; // likewise the parameters stuff - if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) { - // For instance, the Raspberry Pi does this - debug(1, "Lowest dB value is a mute"); - mixer_volume_setting_gives_mute = 1; - alsa_mix_mute = SND_CTL_TLV_DB_GAIN_MUTE; // this may not be necessary -- it's always - // going to be SND_CTL_TLV_DB_GAIN_MUTE, right? - // debug(1, "Try minimum volume + 1 as lowest true attenuation value"); - if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1, - &alsa_mix_mindb) != 0) - debug(1, "Can't get dB value corresponding to a minimum volume + 1."); - } - debug(1, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0, - (1.0 * alsa_mix_maxdb) / 100.0); - } else { - // use the linear scale and do the db conversion ourselves - warn("The hardware mixer specified -- \"%s\" -- does not have " - "a dB volume scale.", - alsa_mix_ctrl); - - if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) { - warn("Cannot open control \"%s\"", alsa_mix_dev); - response = -1; - } - if (snd_ctl_elem_id_malloc(&elem_id) < 0) { - debug(1, "Cannot allocate memory for control \"%s\"", alsa_mix_dev); - elem_id = NULL; - response = -2; - } else { - snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_MIXER); - snd_ctl_elem_id_set_name(elem_id, alsa_mix_ctrl); - - if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) { - debug(1, "alsa: hardware mixer \"%s\" selected, with dB volume from %f to %f.", - alsa_mix_ctrl, (1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0); - has_softvol = 1; - audio_alsa.volume = - &volume; // insert the volume function now we know it can do dB stuff - audio_alsa.parameters = ¶meters; // likewise the parameters stuff - } else { - debug(1, "Cannot get the dB range from the volume control \"%s\"", alsa_mix_ctrl); - } - } - /* - debug(1, "Min and max volumes are %d and - %d.",alsa_mix_minv,alsa_mix_maxv); - alsa_mix_maxdb = 0; - if ((alsa_mix_maxv!=0) && (alsa_mix_minv!=0)) - alsa_mix_mindb = - -20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0)); - else if (alsa_mix_maxv!=0) - alsa_mix_mindb = -20*100*log10(alsa_mix_maxv*1.0); - audio_alsa.volume = &linear_volume; // insert the linear volume function - audio_alsa.parameters = ¶meters; // likewise the parameters stuff - debug(1,"Max and min dB calculated are %d and - %d.",alsa_mix_maxdb,alsa_mix_mindb); - */ - } - } - if (((config.alsa_use_hardware_mute == 1) && - (snd_mixer_selem_has_playback_switch(alsa_mix_elem))) || - mixer_volume_setting_gives_mute) { - audio_alsa.mute = &mute; // insert the mute function now we know it can do muting stuff - // debug(1, "Has mixer and mute ability we will use."); - } else { - // debug(1, "Has mixer but not using hardware mute."); - } - close_mixer(); - } - debug_mutex_unlock(&alsa_mutex, 3); // release the mutex - - pthread_cleanup_pop(0); - pthread_setcancelstate(oldState, NULL); - } else { - debug(1, "alsa: no hardware mixer selected."); - } + + do_alsa_device_init_if_needed(); + alsa_mix_handle = NULL; // so, now, if the option to keep the DAC running has been selected, start a thread to monitor the @@ -994,7 +1008,7 @@ static void deinit(void) { } static void start(int i_sample_rate, int i_sample_format) { - // debug(2,"audio_alsa start called."); + debug(1,"audio_alsa start called."); if (i_sample_rate == 0) desired_sample_rate = 44100; // default else @@ -1010,6 +1024,10 @@ static void start(int i_sample_rate, int i_sample_format) { stall_monitor_start_time = 0; stall_monitor_frame_count = 0; + if (alsa_device_initialised == 0) { + debug(1,"alsa: start() calling do_alsa_device_init_if_needed."); + do_alsa_device_init_if_needed(); + } } int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { @@ -1380,7 +1398,10 @@ static void linear_volume(double vol) { } */ -static void mute(int mute_state_requested) { +int mute(int mute_state_requested) { + int response = 0; + if (config.alsa_use_hardware_mute == 1) + response = 1; // return true if actually using the mute facility // debug(1,"External Mute Request: %d",mute_state_requested); pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); mute_request_pending = 1; @@ -1388,6 +1409,7 @@ static void mute(int mute_state_requested) { do_mute(mute_state_requested); debug_mutex_unlock(&alsa_mutex, 3); pthread_cleanup_pop(0); // release the mutex + return response; } void do_mute(int mute_state_requested) { @@ -1447,8 +1469,12 @@ void alsa_buffer_monitor_thread_cleanup_function(__attribute__((unused)) void *a void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { while (1) { int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000); - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 20000, 1); // check possible state transitions here + if ((config.keep_dac_busy != 0) && (alsa_device_initialised == 0)) { + debug(1,"alsa: alsa_buffer_monitor_thread_code() calling do_alsa_device_init_if_needed."); + do_alsa_device_init_if_needed(); + } if ((alsa_backend_state == abm_disconnected) && (config.keep_dac_busy != 0)) { // open the dac and move to abm_connected mode if (do_open() == 0) diff --git a/common.h b/common.h index 4a111e2c..463e49b4 100644 --- a/common.h +++ b/common.h @@ -182,6 +182,8 @@ typedef struct { // into the "active" mode. uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's // native range. + int volume_range_hw_priority; // when lowering the volume, use all the hw attenuation before using + // sw attenuation enum sps_format_t output_format; enum volume_control_profile_type volume_control_profile; int output_rate; diff --git a/player.c b/player.c index 5efb679c..52356ce0 100644 --- a/player.c +++ b/player.c @@ -1662,6 +1662,7 @@ void *player_thread_func(void *arg) { (config.playback_mode == ST_mono)) conn->enable_dither = 1; + // remember, the output device may never have been initialised prior to this call config.output->start(config.output_rate, config.output_format); // will need a corresponding stop // we need an intermediate "transition" buffer @@ -2276,8 +2277,14 @@ void *player_thread_func(void *arg) { else { if (play_samples == 0) debug(1, "play_samples==0 skipping it (1)."); - else + else { + if (conn->software_mute_enabled) { + generate_zero_frames( + conn->outbuf, play_samples, + config.output_format, conn->enable_dither, conn->previous_random_number); + } config.output->play(conn->outbuf, play_samples); + } } // check for loss of sync @@ -2311,8 +2318,14 @@ void *player_thread_func(void *arg) { conn->outbuf, 0, conn->enable_dither, conn); if (conn->outbuf == NULL) debug(1, "NULL outbuf to play -- skipping it."); - else + else { + if (conn->software_mute_enabled) { + generate_zero_frames( + conn->outbuf, play_samples, + config.output_format, conn->enable_dither, conn->previous_random_number); + } config.output->play(conn->outbuf, play_samples); // remove the (short*)! + } } // mark the frame as finished @@ -2530,235 +2543,198 @@ void *player_thread_func(void *arg) { pthread_exit(NULL); } -// takes the volume as specified by the airplay protocol void player_volume_without_notification(double airplay_volume, rtsp_conn_info *conn) { + debug(1,"player_volume_without_notification %f",airplay_volume); +//first, see if we are hw only, sw only, both with hw attenuation on the top or both with sw attenuation on top - // no cancellation points here if we assume that the mute call to the back end has no cancellation - // points - - // The volume ranges -144.0 (mute) or -30 -- 0. See - // http://git.zx2c4.com/Airtunes2/about/#setting-volume - // By examination, the -30 -- 0 range is linear on the slider; i.e. the slider is calibrated in 30 - // equal increments. Since the human ear's response is roughly logarithmic, we imagine these to - // be power dB, i.e. from -30dB to 0dB. - - // We may have a hardware mixer, and if so, we will give it priority. - // If a desired volume range is given, then we will try to accommodate it from - // the top of the hardware mixer's range downwards. - - // If no desired volume range is given, we will use the native resolution of the hardware mixer, - // if any, - // or failing that, the software mixer. The software mixer has a range of from -96.3 dB up to 0 - // dB, - // corresponding to a multiplier of 1 to 65535. - - // Otherwise, we will accommodate the desired volume range in the combination of the software and - // hardware mixer - // Intuitively (!), it seems best to give the hardware mixer as big a role as possible, so - // we will use its full range and then accommodate the rest of the attenuation in software. - // A problem is that we don't know whether the lowest hardware volume actually mutes the output - // so we must assume that it does, and for this reason, the volume control goes at the "bottom" of - // the adjustment range - - // The dB range of a value from 1 to 65536 is about 96.3 dB (log10 of 65536 is 4.8164). - // Since the levels correspond with amplitude, they correspond to voltage, hence voltage dB, - // or 20 times the log of the ratio. Then multiplied by 100 for convenience. - // Thus, we ask our vol2attn function for an appropriate dB between -96.3 and 0 dB and translate - // it back to a number. - - int32_t hw_min_db, hw_max_db, hw_range_db, min_db, - max_db; // hw_range_db is a flag; if 0 means no mixer - - if (config.output->parameters) { // no cancellation points in here + enum volume_mode_type {vol_sw_only, vol_hw_only, vol_both} volume_mode; + + // take account of whether there is a hardware mixer, if a max volume has been specified and if a range has been specified + // the range might imply that both hw and software mixers are needed, so calculate this + + int32_t hw_max_db, hw_min_db; + int32_t sw_max_db = 0, sw_min_db = -9630; + if (config.output->parameters) { + volume_mode = vol_hw_only; audio_parameters audio_information; - // have a hardware mixer config.output->parameters(&audio_information); hw_max_db = audio_information.maximum_volume_dB; hw_min_db = audio_information.minimum_volume_dB; - hw_range_db = hw_max_db - hw_min_db; - } else { - // don't have a hardware mixer - hw_max_db = hw_min_db = hw_range_db = 0; - } - - int32_t sw_min_db = -9630; - int32_t sw_max_db = 0; - int32_t sw_range_db = sw_max_db - sw_min_db; - int32_t desired_range_db = 0; // this is also used as a flag; if 0 means no desired range - - if (config.volume_range_db) - desired_range_db = (int32_t)trunc(config.volume_range_db * 100); - - // This is wrong, I think -- it doesn't work properly if the volume range is composite and you - // want to set a maximum value which should affect the hardware mixer. - - if (config.volume_max_db_set) { - if (hw_range_db) { - if (((config.volume_max_db * 100) < hw_max_db) && - ((config.volume_max_db * 100) > hw_min_db)) { - hw_max_db = (int)config.volume_max_db * 100; - hw_range_db = hw_max_db - hw_min_db; - } else { - inform("The volume_max_db setting is out of range of the hardware mixers's limits of %d dB " - "to %d dB. It will be ignored.", - (int)(hw_max_db / 100), (int)(hw_min_db / 100)); - } - } else { - if (((config.volume_max_db * 100) < sw_max_db) && - ((config.volume_max_db * 100) > sw_min_db)) { - sw_max_db = (int)config.volume_max_db * 100; - sw_range_db = sw_max_db - sw_min_db; - } else { - inform("The volume_max_db setting is out of range of the software attenuation's limits of " - "0 dB to -96.3 dB. It will be ignored."); - } - } - } - - if (desired_range_db) { - // debug(1,"An attenuation range of %d is requested.",desired_range_db); - // we have a desired volume range. - if (hw_range_db) { - // we have a hardware mixer - if (hw_range_db >= desired_range_db) { - // the hardware mixer can accommodate the desired range - max_db = hw_max_db; - min_db = max_db - desired_range_db; - } else { - // we have a hardware mixer and a desired range greater than the mixer's range. - if ((hw_range_db + sw_range_db) < desired_range_db) { - inform("The volume attenuation range %f is greater than can be accommodated by the " - "hardware and software -- set to %f.", - config.volume_range_db, hw_range_db + sw_range_db); - desired_range_db = hw_range_db + sw_range_db; - } - min_db = hw_min_db; - max_db = min_db + desired_range_db; - } - } else { - // we have a desired volume range and no hardware mixer - if (sw_range_db < desired_range_db) { - inform("The volume attenuation range %f is greater than can be accommodated by the " - "software -- set to %f.", - config.volume_range_db, sw_range_db); - desired_range_db = sw_range_db; - } - max_db = sw_max_db; - min_db = max_db - desired_range_db; - } - } else { - // we do not have a desired volume range, so use the mixer's volume range, if there is one. - // debug(1,"No attenuation range requested."); - if (hw_range_db) { - min_db = hw_min_db; - max_db = hw_max_db; - } else { - min_db = sw_min_db; - max_db = sw_max_db; - } - } - - /* if (config.volume_max_db_set) { - if ((config.volume_max_db*100<=max_db) && (config.volume_max_db*100>=min_db)) { - debug(1,"Reducing the maximum volume from %d to %d.",max_db/100,config.volume_max_db); - max_db = (int)(config.volume_max_db*100); + if (((config.volume_max_db * 100) <= hw_max_db) && + ((config.volume_max_db * 100) >= hw_min_db)) + hw_max_db = (int32_t)config.volume_max_db * 100; + else if (config.volume_range_db) { + hw_max_db = hw_min_db; + sw_max_db = (config.volume_max_db * 100) - hw_min_db; } else { - inform("The value of volume_max_db is invalid. It must be in the range %d to - %d.",max_db,min_db); - } + warn("The maximum output level is outside the range of the hardware mixer -- ignored"); + } } - */ - double hardware_attenuation = 0.0, software_attenuation = 0.0; - double scaled_attenuation = hw_min_db + sw_min_db; - - // now, we can map the input to the desired output volume - if ((airplay_volume == -144.0) && (config.ignore_volume_control == 0)) { - // do a mute - // needed even with hardware mute, as when sound is unmuted it might otherwise be very loud. - hardware_attenuation = hw_min_db; - if (config.output->mute) { - // allow the audio material to reach the mixer, but mute the mixer - // it the mute is removed externally, the material with be there - software_attenuation = sw_max_db - (max_db - hw_max_db); // e.g. if the hw_max_db is +4 and - // the max is +40, this will be -36 - // (all by 100, of course) - // debug(1,"Mute, with hardware mute and software_attenuation set to - // %d.",software_attenuation); - config.output->mute(1); // use real mute if it's there - } else { - if (config.output->volume == NULL) { // if there is also no hardware volume control - software_attenuation = sw_min_db; // set any software output to zero too - // debug(1,"Mute, with no hardware mute and software_attenuation set to - // %d.",software_attenuation); - } + + // here, we have set limits on the hw_max_db and the sw_max_db + // but we haven't actually decided whether we need both hw and software attenuation + // only if a range is specified could we need both + if (config.volume_range_db) { + // see if the range requested exceeds the hardware range available + int32_t desired_range_db = (int32_t)trunc(config.volume_range_db * 100); + if ((desired_range_db) > (hw_max_db - hw_min_db)) { + volume_mode = vol_both; + int32_t desired_sw_range = desired_range_db - (hw_max_db - hw_min_db); + if ((sw_max_db - desired_sw_range) < sw_min_db) + warn("The range requested is too large to accommodate -- ignored."); + else + sw_min_db = (sw_max_db - desired_sw_range); + } } } else { - if (config.output->mute) - config.output->mute(0); // unmute mute if it's there - if (config.ignore_volume_control == 1) - scaled_attenuation = max_db; - else if (config.volume_control_profile == VCP_standard) - scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); // no cancellation points - else if (config.volume_control_profile == VCP_flat) - scaled_attenuation = flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points - else - debug(1, "Unrecognised volume control profile"); - - if (hw_range_db) { - // if there is a hardware mixer - if (scaled_attenuation <= hw_max_db) { - // the attenuation is so low that's it's in the hardware mixer's range - // debug(1,"Attenuation all taken care of by the hardware mixer."); - hardware_attenuation = scaled_attenuation; - software_attenuation = sw_max_db - (max_db - hw_max_db); // e.g. if the hw_max_db is +4 and - // the max is +40, this will be -36 - // (all by 100, of course) - } else { - // debug(1,"Attenuation taken care of by hardware and software mixer."); - hardware_attenuation = hw_max_db; // the hardware mixer is turned up full - software_attenuation = sw_max_db - (max_db - scaled_attenuation); - } - } else { - // if there is no hardware mixer, the scaled_volume is the software volume - // debug(1,"Attenuation all taken care of by the software mixer."); - software_attenuation = scaled_attenuation; + // debug(1,"has no hardware mixer"); + volume_mode = vol_sw_only; + if (config.volume_max_db_set) { + if (((config.volume_max_db * 100) <= sw_max_db) && + ((config.volume_max_db * 100) >= sw_min_db)) + sw_max_db = (int32_t)config.volume_max_db * 100; } + if (config.volume_range_db) { + // see if the range requested exceeds the software range available + int32_t desired_range_db = (int32_t)trunc(config.volume_range_db * 100); + if ((desired_range_db) > (sw_max_db - sw_min_db)) + warn("The range requested is too large to accommodate -- ignored."); + else + sw_min_db = (sw_max_db - desired_range_db); + } } + + // here, we know whether it's hw volume control only, sw only or both, and we have the hw and sw limits. + // if it's both, we haven't decided whether hw or sw should be on top + // we have to consider the settings ignore_volume_control and mute. + + if (config.ignore_volume_control == 0) { + if (airplay_volume == -144.0) { + + if ((config.output->mute) && (config.output->mute(1) != 0)) + debug(1,"hardware mute is enabled."); + else { + conn->software_mute_enabled = 1; + debug(1,"software mute is enabled."); + + } - if ((config.output->volume) && (hw_range_db)) { - config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value - // debug(1,"Hardware attenuation set to %f for airplay volume of - // %f.",hardware_attenuation,airplay_volume); - } - double temp_fix_volume = 65536.0 * pow(10, software_attenuation / 2000); - // debug(1,"Software attenuation set to %f, i.e %f out of 65,536, for airplay volume of - // %f",software_attenuation,temp_fix_volume,airplay_volume); + } else { + int32_t max_db,min_db; + switch (volume_mode) { + case vol_hw_only: + max_db = hw_max_db; + min_db = hw_min_db; + break; + case vol_sw_only: + max_db = sw_max_db; + min_db = sw_min_db; + break; + case vol_both: + debug(1,"dB range passed is hw: %d, sw: %d, total: %d", hw_max_db - hw_min_db, sw_max_db - sw_min_db, (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db)); + max_db = (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db); // this should be the range requested + min_db = 0; + break; + default: + debug(1,"error in pv -- not in a volume mode"); + break; + } + double scaled_attenuation = 0.0; + if (config.volume_control_profile == VCP_standard) + scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); // no cancellation points + else if (config.volume_control_profile == VCP_flat) + scaled_attenuation = flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points + else + debug(1, "Unrecognised volume control profile"); + + // so here we have the scaled attenuation. If it's for hw or sw only, it's straightforward. + double hardware_attenuation = 0.0; + double software_attenuation = 0.0; - conn->fix_volume = temp_fix_volume; - memory_barrier(); // no cancellation points + switch (volume_mode) { + case vol_hw_only: + hardware_attenuation = scaled_attenuation; + break; + case vol_sw_only: + software_attenuation = scaled_attenuation; + break; + case vol_both: + // here, we now the attenuation required, so we have to apportion it to the sw and hw mixers + // if we give the hw priority, that means when lowering the volume, set the hw volume to its lowest + // before using the sw attenuation. + // similarly, if we give the sw priority, that means when lowering the volume, set the sw volume to its lowest + // before using the hw attenuation. + // one imagines that hw priority is likely to be much better + if (config.volume_range_hw_priority) { + // hw priority + if ((sw_max_db - sw_min_db) > scaled_attenuation) { + software_attenuation = sw_min_db + scaled_attenuation; + hardware_attenuation = hw_min_db; + } else { + software_attenuation = sw_max_db; + hardware_attenuation = hw_min_db + scaled_attenuation - (sw_max_db - sw_min_db); + } + } else { + // sw priority + if ((hw_max_db - hw_min_db) > scaled_attenuation) { + hardware_attenuation = hw_min_db + scaled_attenuation; + software_attenuation = sw_min_db; + } else { + hardware_attenuation = hw_max_db; + software_attenuation = sw_min_db + scaled_attenuation - (hw_max_db - hw_min_db); + } + } + break; + default: + debug(1,"error in pv -- not in a volume mode"); + break; + } + + + if (((volume_mode == vol_hw_only) || (volume_mode == vol_both)) && (config.output->volume)) + config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value + // debug(1,"Hardware attenuation set to %f for airplay volume of + // %f.",hardware_attenuation,airplay_volume); + + if ((volume_mode == vol_sw_only) || (volume_mode == vol_both)) { + double temp_fix_volume = 65536.0 * pow(10, software_attenuation / 2000); + // debug(1,"Software attenuation set to %f, i.e %f out of 65,536, for airplay volume of + // %f",software_attenuation,temp_fix_volume,airplay_volume); - if (config.loudness) - loudness_set_volume(software_attenuation / 100); + conn->fix_volume = temp_fix_volume; + memory_barrier(); // no cancellation points - if (config.logOutputLevel) { - inform("Output Level set to: %.2f dB.", scaled_attenuation / 100.0); - } + if (config.loudness) + loudness_set_volume(software_attenuation / 100); + } + + + if (config.logOutputLevel) { + inform("Output Level set to: %.2f dB.", scaled_attenuation / 100.0); + } #ifdef CONFIG_METADATA - char *dv = malloc(128); // will be freed in the metadata thread - if (dv) { - memset(dv, 0, 128); - if (config.ignore_volume_control == 1) - snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.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); - } + char *dv = malloc(128); // will be freed in the metadata thread + if (dv) { + memset(dv, 0, 128); + if (config.ignore_volume_control == 1) + snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.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 + // here, store the volume for possible use in the future - // here, store the volume for possible use in the future + if (config.output->mute) + config.output->mute(0); + conn->software_mute_enabled = 0; + + debug(1,"pv: volume mode is %d, software_attenuation: %f, hardware_attenuation: %f, muting is disabled.", volume_mode, software_attenuation, hardware_attenuation); + } + } config.airplay_volume = airplay_volume; } diff --git a/player.h b/player.h index 84521893..00793a92 100644 --- a/player.h +++ b/player.h @@ -68,14 +68,15 @@ typedef struct { } stream_cfg; typedef struct { - int connection_number; // for debug ID purposes, nothing else... - int resend_interval; // this is really just for debugging - int AirPlayVersion; // zero if not an AirPlay session. Used to help calculate latency - uint32_t latency; // the actual latency used for this play session - uint32_t minimum_latency; // set if an a=min-latency: line appears in the ANNOUNCE message; zero - // otherwise - uint32_t maximum_latency; // set if an a=max-latency: line appears in the ANNOUNCE message; zero - // otherwise + int connection_number; // for debug ID purposes, nothing else... + int resend_interval; // this is really just for debugging + int AirPlayVersion; // zero if not an AirPlay session. Used to help calculate latency + uint32_t latency; // the actual latency used for this play session + uint32_t minimum_latency; // set if an a=min-latency: line appears in the ANNOUNCE message; zero + // otherwise + uint32_t maximum_latency; // set if an a=max-latency: line appears in the ANNOUNCE message; zero + // otherwise + int software_mute_enabled; // if we don't have a real mute that we can use int fd; int authorized; // set if a password is required and has been supplied From fd08511f1f41c06a8fe0d230d666cd17227ef030 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 30 Jan 2019 17:43:15 +0000 Subject: [PATCH 402/569] Make it work with deferred initialisation of the alsa device -- deferred to first use. Investigate hardware mute again. --- audio_alsa.c | 16 ++++++++-------- player.c | 4 +++- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 0b94e812..ca636708 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -977,10 +977,8 @@ static int init(int argc, char **argv) { } debug(1, "alsa: output device name is \"%s\".", alsa_out_dev); - - do_alsa_device_init_if_needed(); - - alsa_mix_handle = NULL; + + alsa_mix_handle = NULL; // indicates alsa handle isn't open // so, now, if the option to keep the DAC running has been selected, start a thread to monitor the // length of the queue @@ -1297,6 +1295,7 @@ int play(void *buf, int samples) { if (alsa_backend_state != abm_playing) { debug(2, "alsa: play() -- alsa_backend_state => abm_playing"); alsa_backend_state = abm_playing; + do_mute(0); } ret = do_play(buf, samples); } @@ -1399,6 +1398,7 @@ static void linear_volume(double vol) { */ int mute(int mute_state_requested) { + debug(1,"alsa: mute(%d)",mute_state_requested); int response = 0; if (config.alsa_use_hardware_mute == 1) response = 1; // return true if actually using the mute facility @@ -1413,7 +1413,7 @@ int mute(int mute_state_requested) { } void do_mute(int mute_state_requested) { - debug(3, "Setting mute to %d.", mute_state_requested); + debug(1, "alsa: do_mute(%d).", mute_state_requested); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable @@ -1468,13 +1468,13 @@ void alsa_buffer_monitor_thread_cleanup_function(__attribute__((unused)) void *a void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { while (1) { - int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000); - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 20000, 1); - // check possible state transitions here if ((config.keep_dac_busy != 0) && (alsa_device_initialised == 0)) { debug(1,"alsa: alsa_buffer_monitor_thread_code() calling do_alsa_device_init_if_needed."); do_alsa_device_init_if_needed(); } + int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 20000, 1); + // check possible state transitions here if ((alsa_backend_state == abm_disconnected) && (config.keep_dac_busy != 0)) { // open the dac and move to abm_connected mode if (do_open() == 0) diff --git a/player.c b/player.c index 52356ce0..642b6e2c 100644 --- a/player.c +++ b/player.c @@ -2692,10 +2692,12 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c } - if (((volume_mode == vol_hw_only) || (volume_mode == vol_both)) && (config.output->volume)) + if (((volume_mode == vol_hw_only) || (volume_mode == vol_both)) && (config.output->volume)) { config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value // debug(1,"Hardware attenuation set to %f for airplay volume of // %f.",hardware_attenuation,airplay_volume); + conn->fix_volume = 0x10000; + } if ((volume_mode == vol_sw_only) || (volume_mode == vol_both)) { double temp_fix_volume = 65536.0 * pow(10, software_attenuation / 2000); From 555bb97a39757025ae2ce1e696a3ad1500d45cd9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 31 Jan 2019 09:01:35 +0000 Subject: [PATCH 403/569] Rebuild the hardware mute code -- clear up the logic. Needs to check if disconnected though. --- audio_alsa.c | 512 ++++++++++++++++++++++++++++----------------------- player.c | 331 +++++++++++++++++---------------- 2 files changed, 452 insertions(+), 391 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index ca636708..9be90890 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -51,7 +51,6 @@ static int play(void *buf, int samples); static void stop(void); static void flush(void); int delay(long *the_delay); -void do_mute(int request); int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played); void *alsa_buffer_monitor_thread_code(void *arg); @@ -61,7 +60,7 @@ void do_volume(double vol); static void parameters(audio_parameters *info); int mute(int do_mute); // returns true if it actually is allowed to use the mute static double set_volume; -static int output_method_signalled = 0; +static int output_method_signalled = 0; // for reporting whether it's using mmap or not audio_output audio_alsa = { .name = "alsa", @@ -75,25 +74,40 @@ audio_output audio_alsa = { .delay = &delay, .play = &play, .rate_info = &get_rate_information, - .mute = NULL, // a function will be provided if it can, and is allowed to, do hardware mute - .volume = NULL, // a function will be provided if it can do hardware volume + .mute = NULL, // a function will be provided if it can, and is allowed to, + // do hardware mute + .volume = NULL, // a function will be provided if it can do hardware volume .parameters = NULL}; // a function will be provided if it can do hardware volume static pthread_mutex_t alsa_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_t alsa_buffer_monitor_thread; +// for deciding when to activate mute +// there are two sources of requests to mute -- the backend itself, e.g. when it +// is flushing +// and the player, e.g. when volume goes down to -144, i.e. mute. + +// we may not be allowed to use hardware mute, so we must reflect that too. + +int mute_requested_externally = 0; +int mute_requested_internally = 0; + // for tracking how long the output device has stalled -uint64_t stall_monitor_start_time; // zero if not initialised / not started / zeroed by flush -long stall_monitor_frame_count; // set to delay at start of time, incremented by any writes -uint64_t stall_monitor_error_threshold; // if the time is longer than this, it's an error +uint64_t stall_monitor_start_time; // zero if not initialised / not started / + // zeroed by flush +long stall_monitor_frame_count; // set to delay at start of time, incremented by + // any writes +uint64_t stall_monitor_error_threshold; // if the time is longer than this, it's + // an error static snd_output_t *output = NULL; static unsigned int desired_sample_rate; static enum sps_format_t sample_format; int frame_size; // in bytes for interleaved stereo -int alsa_device_initialised; // boolean to ensure the initialisation is only done once +int alsa_device_initialised; // boolean to ensure the initialisation is only + // done once snd_pcm_t *alsa_handle = NULL; static snd_pcm_hw_params_t *alsa_params = NULL; static snd_pcm_sw_params_t *alsa_swparams = NULL; @@ -114,13 +128,13 @@ static int has_softvol = 0; int64_t dither_random_number_store = 0; -static int volume_set_request = 0; // set when an external request is made to set the volume. -int mute_request_pending = 0; // set when an external request is made to mute or unmute. -int overriding_mute_state_requested = 0; // 1 = mute; 0 = unmute requested -int mixer_volume_setting_gives_mute = - 0; // set when it is discovered that particular mixer volume setting causes a mute. -long alsa_mix_mute; // setting the volume to this value mutes output, if - // mixer_volume_setting_gives_mute is true +static int volume_set_request = 0; // set when an external request is made to set the volume. + +int mixer_volume_setting_gives_mute = 0; // set when it is discovered that + // particular mixer volume setting + // causes a mute. +long alsa_mix_mute; // setting the volume to this value mutes output, if + // mixer_volume_setting_gives_mute is true int volume_based_mute_is_active = 0; // set when muting is being done by a setting the volume to a magic value @@ -223,8 +237,9 @@ void actual_close_alsa_device() { if (alsa_handle) { int derr; if ((derr = snd_pcm_hw_free(alsa_handle))) - debug(1, "Error %d (\"%s\") freeing the output device hardware while closing it.", derr, - snd_strerror(derr)); + debug(1, "Error %d (\"%s\") freeing the output device hardware while " + "closing it.", + derr, snd_strerror(derr)); if ((derr = snd_pcm_close(alsa_handle))) debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr)); @@ -241,7 +256,8 @@ int actual_open_alsa_device(void) { /* const snd_pcm_uframes_t requested_buffer_headroom = minimal_buffer_headroom + 2048; // we ask for this much headroom in the - // hardware buffer, but we'll accept less + // hardware buffer, but we'll accept + less */ int ret, dir = 0; @@ -250,17 +266,20 @@ int actual_open_alsa_device(void) { snd_pcm_uframes_t actual_buffer_length; snd_pcm_access_t access; - // ensure no calls are made to the alsa device enquiring about the buffer length if + // ensure no calls are made to the alsa device enquiring about the buffer + // length if // synchronisation is disabled. if (config.no_sync != 0) audio_alsa.delay = NULL; - // ensure no calls are made to the alsa device enquiring about the buffer length if + // ensure no calls are made to the alsa device enquiring about the buffer + // length if // synchronisation is disabled. if (config.no_sync != 0) audio_alsa.delay = NULL; - // ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + // ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, + // SND_PCM_NONBLOCK); ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, 0); if (ret < 0) return ret; @@ -369,7 +388,8 @@ int actual_open_alsa_device(void) { snd_pcm_uframes_t actual_period_size; snd_pcm_hw_params_get_period_size(alsa_params, &actual_period_size, &dir); if (actual_period_size != period_size_requested) - inform("Actual period size set to a different value than requested. Requested: %lu, actual " + inform("Actual period size set to a different value than requested. " + "Requested: %lu, actual " "setting: %lu", period_size_requested, actual_period_size); } @@ -386,7 +406,8 @@ int actual_open_alsa_device(void) { snd_pcm_uframes_t actual_buffer_size; snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_size); if (actual_buffer_size != buffer_size_requested) - inform("Actual period size set to a different value than requested. Requested: %lu, actual " + inform("Actual period size set to a different value than requested. " + "Requested: %lu, actual " "setting: %lu", buffer_size_requested, actual_buffer_size); } @@ -405,7 +426,8 @@ int actual_open_alsa_device(void) { snd_pcm_uframes_t actual_period_size; snd_pcm_hw_params_get_period_size(alsa_params, &actual_period_size, &dir); if (actual_period_size != period_size_requested) - inform("Actual period size set to a different value than requested. Requested: %lu, actual " + inform("Actual period size set to a different value than requested. " + "Requested: %lu, actual " "setting: %lu", period_size_requested, actual_period_size); } @@ -414,7 +436,8 @@ int actual_open_alsa_device(void) { snd_pcm_uframes_t actual_buffer_size; snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_size); if (actual_buffer_size != buffer_size_requested) - inform("Actual period size set to a different value than requested. Requested: %lu, actual " + inform("Actual period size set to a different value than requested. " + "Requested: %lu, actual " "setting: %lu", buffer_size_requested, actual_buffer_size); } @@ -433,8 +456,9 @@ int actual_open_alsa_device(void) { ret = snd_pcm_sw_params_current(alsa_handle, alsa_swparams); if (ret < 0) { - warn("audio_alsa: Unable to get current sw parameters for device \"%s\": %s.", alsa_out_dev, - snd_strerror(ret)); + warn("audio_alsa: Unable to get current sw parameters for device \"%s\": " + "%s.", + alsa_out_dev, snd_strerror(ret)); return ret; } @@ -476,9 +500,9 @@ int actual_open_alsa_device(void) { buffer_size); } */ - debug(1, - "The alsa buffer is smaller (%lu bytes) than the desired backend buffer " - "length (%ld) you have chosen.", + debug(1, "The alsa buffer is smaller (%lu bytes) than the desired backend " + "buffer " + "length (%ld) you have chosen.", actual_buffer_length, config.audio_backend_buffer_desired_length); } @@ -499,7 +523,10 @@ int actual_open_alsa_device(void) { // ret = snd_pcm_hw_params_any(alsa_handle, alsa_params); // if (ret < 0) { - // die("audio_alsa: Cannpot get configuration for device \"%s\": no + // die("audio_alsa: Cannpot get configuration for + // device + //\"%s\": + // no // configurations //" // "available", @@ -622,105 +649,116 @@ int open_alsa_device(void) { } int do_alsa_device_init_if_needed() { - debug(1,"do_alsa_device_init_if_needed()"); + debug(1, "do_alsa_device_init_if_needed()"); int response = 0; // do any alsa device initialisation (general case) if needed // at present, this is only needed if a hardware mixer is being used // if there's a hardware mixer, it needs to be initialised before first use if (alsa_device_initialised == 0) { - alsa_device_initialised = 1; - if (hardware_mixer) { - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable + alsa_device_initialised = 1; + if (hardware_mixer) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable - if (alsa_mix_dev == NULL) - alsa_mix_dev = alsa_out_dev; + if (alsa_mix_dev == NULL) + alsa_mix_dev = alsa_out_dev; - // Now, start trying to initialise the alsa device with the settings obtained - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 1000, 1); - if (open_mixer() == 1) { - if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) < - 0) - debug(1, "Can't read mixer's [linear] min and max volumes."); - else { - if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb, - &alsa_mix_maxdb) == 0) { + // Now, start trying to initialise the alsa device with the settings + // obtained + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 1000, 1); + if (open_mixer() == 1) { + if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, + &alsa_mix_maxv) < 0) + debug(1, "Can't read mixer's [linear] min and max volumes."); + else { + if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb, + &alsa_mix_maxdb) == 0) { - audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff - audio_alsa.parameters = ¶meters; // likewise the parameters stuff - if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) { - // For instance, the Raspberry Pi does this - debug(1, "Lowest dB value is a mute"); - mixer_volume_setting_gives_mute = 1; - alsa_mix_mute = SND_CTL_TLV_DB_GAIN_MUTE; // this may not be necessary -- it's always - // going to be SND_CTL_TLV_DB_GAIN_MUTE, right? - // debug(1, "Try minimum volume + 1 as lowest true attenuation value"); - if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1, - &alsa_mix_mindb) != 0) - debug(1, "Can't get dB value corresponding to a minimum volume + 1."); - } - debug(1, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0, - (1.0 * alsa_mix_maxdb) / 100.0); - } else { - // use the linear scale and do the db conversion ourselves - warn("The hardware mixer specified -- \"%s\" -- does not have " - "a dB volume scale.", - alsa_mix_ctrl); - - if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) { - warn("Cannot open control \"%s\"", alsa_mix_dev); - response = -1; - } - if (snd_ctl_elem_id_malloc(&elem_id) < 0) { - debug(1, "Cannot allocate memory for control \"%s\"", alsa_mix_dev); - elem_id = NULL; - response = -2; - } else { - snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_MIXER); - snd_ctl_elem_id_set_name(elem_id, alsa_mix_ctrl); - - if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) { - debug(1, "alsa: hardware mixer \"%s\" selected, with dB volume from %f to %f.", - alsa_mix_ctrl, (1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0); - has_softvol = 1; - audio_alsa.volume = - &volume; // insert the volume function now we know it can do dB stuff - audio_alsa.parameters = ¶meters; // likewise the parameters stuff - } else { - debug(1, "Cannot get the dB range from the volume control \"%s\"", alsa_mix_ctrl); + audio_alsa.volume = &volume; // insert the volume function now we + // know it can do dB stuff + audio_alsa.parameters = ¶meters; // likewise the parameters stuff + if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) { + // For instance, the Raspberry Pi does this + debug(1, "Lowest dB value is a mute"); + mixer_volume_setting_gives_mute = 1; + alsa_mix_mute = SND_CTL_TLV_DB_GAIN_MUTE; // this may not be + // necessary -- it's + // always + // going to be SND_CTL_TLV_DB_GAIN_MUTE, right? + // debug(1, "Try minimum volume + 1 as lowest true attenuation + // value"); + if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1, + &alsa_mix_mindb) != 0) + debug(1, "Can't get dB value corresponding to a minimum volume " + "+ 1."); } - } - /* - debug(1, "Min and max volumes are %d and - %d.",alsa_mix_minv,alsa_mix_maxv); - alsa_mix_maxdb = 0; - if ((alsa_mix_maxv!=0) && (alsa_mix_minv!=0)) - alsa_mix_mindb = - -20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0)); - else if (alsa_mix_maxv!=0) - alsa_mix_mindb = -20*100*log10(alsa_mix_maxv*1.0); - audio_alsa.volume = &linear_volume; // insert the linear volume function - audio_alsa.parameters = ¶meters; // likewise the parameters stuff - debug(1,"Max and min dB calculated are %d and - %d.",alsa_mix_maxdb,alsa_mix_mindb); - */ - } - } - if (((config.alsa_use_hardware_mute == 1) && - (snd_mixer_selem_has_playback_switch(alsa_mix_elem))) || - mixer_volume_setting_gives_mute) { - audio_alsa.mute = &mute; // insert the mute function now we know it can do muting stuff - // debug(1, "Has mixer and mute ability we will use."); - } else { - // debug(1, "Has mixer but not using hardware mute."); - } - close_mixer(); - } - debug_mutex_unlock(&alsa_mutex, 3); // release the mutex + debug(1, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0, + (1.0 * alsa_mix_maxdb) / 100.0); + } else { + // use the linear scale and do the db conversion ourselves + warn("The hardware mixer specified -- \"%s\" -- does not have " + "a dB volume scale.", + alsa_mix_ctrl); - pthread_cleanup_pop(0); - pthread_setcancelstate(oldState, NULL); - } + if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) { + warn("Cannot open control \"%s\"", alsa_mix_dev); + response = -1; + } + if (snd_ctl_elem_id_malloc(&elem_id) < 0) { + debug(1, "Cannot allocate memory for control \"%s\"", alsa_mix_dev); + elem_id = NULL; + response = -2; + } else { + snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_MIXER); + snd_ctl_elem_id_set_name(elem_id, alsa_mix_ctrl); + + if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) { + debug(1, "alsa: hardware mixer \"%s\" selected, with dB volume " + "from %f to %f.", + alsa_mix_ctrl, (1.0 * alsa_mix_mindb) / 100.0, + (1.0 * alsa_mix_maxdb) / 100.0); + has_softvol = 1; + audio_alsa.volume = &volume; // insert the volume function now + // we know it can do dB stuff + audio_alsa.parameters = ¶meters; // likewise the parameters stuff + } else { + debug(1, "Cannot get the dB range from the volume control \"%s\"", alsa_mix_ctrl); + } + } + /* + debug(1, "Min and max volumes are %d and + %d.",alsa_mix_minv,alsa_mix_maxv); + alsa_mix_maxdb = 0; + if ((alsa_mix_maxv!=0) && (alsa_mix_minv!=0)) + alsa_mix_mindb = + -20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0)); + else if (alsa_mix_maxv!=0) + alsa_mix_mindb = -20*100*log10(alsa_mix_maxv*1.0); + audio_alsa.volume = &linear_volume; // insert the linear volume + function + audio_alsa.parameters = ¶meters; // likewise the parameters + stuff + debug(1,"Max and min dB calculated are %d and + %d.",alsa_mix_maxdb,alsa_mix_mindb); + */ + } + } + if (((config.alsa_use_hardware_mute == 1) && + (snd_mixer_selem_has_playback_switch(alsa_mix_elem))) || + mixer_volume_setting_gives_mute) { + audio_alsa.mute = &mute; // insert the mute function now we know it + // can do muting stuff + // debug(1, "Has mixer and mute ability we will use."); + } else { + // debug(1, "Has mixer but not using hardware mute."); + } + close_mixer(); + } + debug_mutex_unlock(&alsa_mutex, 3); // release the mutex + + pthread_cleanup_pop(0); + pthread_setcancelstate(oldState, NULL); + } } return response; } @@ -766,7 +804,8 @@ static int init(int argc, char **argv) { // get settings from settings file first, allow them to be overridden by // command line options - // do the "general" audio options. Note, these options are in the "general" stanza! + // do the "general" audio options. Note, these options are in the "general" + // stanza! parse_general_audio_options(); if (config.cfg != NULL) { @@ -803,7 +842,8 @@ static int init(int argc, char **argv) { else if (strcasecmp(str, "yes") == 0) config.no_sync = 1; else { - warn("Invalid disable_synchronization option choice \"%s\". It should be \"yes\" or " + warn("Invalid disable_synchronization option choice \"%s\". It should " + "be \"yes\" or " "\"no\". It is set to \"no\"."); config.no_sync = 0; } @@ -818,7 +858,8 @@ static int init(int argc, char **argv) { else if (strcasecmp(str, "yes") == 0) config.alsa_use_hardware_mute = 1; else { - warn("Invalid mute_using_playback_switch option choice \"%s\". It should be \"yes\" or " + warn("Invalid mute_using_playback_switch option choice \"%s\". It " + "should be \"yes\" or " "\"no\". It is set to \"no\"."); config.alsa_use_hardware_mute = 0; } @@ -831,7 +872,8 @@ static int init(int argc, char **argv) { else if (strcasecmp(str, "yes") == 0) config.alsa_use_hardware_mute = 1; else { - warn("Invalid use_hardware_mute_if_available option choice \"%s\". It should be \"yes\" or " + warn("Invalid use_hardware_mute_if_available option choice \"%s\". It " + "should be \"yes\" or " "\"no\". It is set to \"no\"."); config.alsa_use_hardware_mute = 0; } @@ -854,7 +896,8 @@ static int init(int argc, char **argv) { else if (strcasecmp(str, "S8") == 0) config.output_format = SPS_FORMAT_S8; else { - warn("Invalid output format \"%s\". It should be \"U8\", \"S8\", \"S16\", \"S24\", " + warn("Invalid output format \"%s\". It should be \"U8\", \"S8\", " + "\"S16\", \"S24\", " "\"S24_3LE\", \"S24_3BE\" or " "\"S32\". It is set to \"S16\".", str); @@ -873,7 +916,8 @@ static int init(int argc, char **argv) { config.output_rate = value; break; default: - warn("Invalid output rate \"%d\". It should be a multiple of 44,100 up to 352,800. It is " + warn("Invalid output rate \"%d\". It should be a multiple of 44,100 up " + "to 352,800. It is " "set to 44,100", value); config.output_rate = 44100; @@ -887,7 +931,8 @@ static int init(int argc, char **argv) { else if (strcasecmp(str, "yes") == 0) config.no_mmap = 0; else { - warn("Invalid use_mmap_if_available option choice \"%s\". It should be \"yes\" or \"no\". " + warn("Invalid use_mmap_if_available option choice \"%s\". It should be " + "\"yes\" or \"no\". " "It is set to \"yes\"."); config.no_mmap = 0; } @@ -977,10 +1022,11 @@ static int init(int argc, char **argv) { } debug(1, "alsa: output device name is \"%s\".", alsa_out_dev); - + alsa_mix_handle = NULL; // indicates alsa handle isn't open - // so, now, if the option to keep the DAC running has been selected, start a thread to monitor the + // so, now, if the option to keep the DAC running has been selected, start a + // thread to monitor the // length of the queue // if the queue gets too short, stuff it with silence @@ -1005,8 +1051,39 @@ static void deinit(void) { pthread_setcancelstate(oldState, NULL); } +int set_mute_state() { + debug(1, "set_mute_state"); + int response = 1; // some problem expected, e.g. no mixer or not allowed to use it + if (config.alsa_use_hardware_mute == 1) { + int mute = 0; + if ((mute_requested_externally != 0) || (mute_requested_internally != 0)) + mute = 1; + if (open_mixer() == 1) { + response = 0; // okay if actually using the mute facility + if (mute == 1) { + debug(1, "Hardware mute switched on"); + if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) + snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 0); + else { + volume_based_mute_is_active = 1; + do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, alsa_mix_mute); + } + } else { + debug(1, "Hardware mute switched off"); + if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) + snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 1); + else { + volume_based_mute_is_active = 0; + do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, set_volume); + } + } + close_mixer(); + } + } + return response; +} static void start(int i_sample_rate, int i_sample_format) { - debug(1,"audio_alsa start called."); + debug(1, "audio_alsa start called."); if (i_sample_rate == 0) desired_sample_rate = 44100; // default else @@ -1023,7 +1100,7 @@ static void start(int i_sample_rate, int i_sample_format) { stall_monitor_start_time = 0; stall_monitor_frame_count = 0; if (alsa_device_initialised == 0) { - debug(1,"alsa: start() calling do_alsa_device_init_if_needed."); + debug(1, "alsa: start() calling do_alsa_device_init_if_needed."); do_alsa_device_init_if_needed(); } } @@ -1065,10 +1142,9 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) || ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) { - debug(2, - "DAC seems to have stalled with time_now_ns: %" PRIX64 - ", update_timestamp_ns: %" PRIX64 ", stall_monitor_start_time %" PRIX64 - ", stall_monitor_error_threshold %" PRIX64 ".", + debug(2, "DAC seems to have stalled with time_now_ns: %" PRIX64 + ", update_timestamp_ns: %" PRIX64 ", stall_monitor_start_time %" PRIX64 + ", stall_monitor_error_threshold %" PRIX64 ".", time_now_ns, update_timestamp_ns, stall_monitor_start_time, stall_monitor_error_threshold); ret = sps_extra_code_output_stalled; @@ -1088,12 +1164,14 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { *delay = *delay - frames_played_since_last_interrupt_sized; } - } else { // not running, thus no delay information, thus can't check for stall + } else { // not running, thus no delay information, thus can't check for + // stall *delay = 0; stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes - // not running, thus no delay information, thus can't check for frame rates + // not running, thus no delay information, thus can't check for frame + // rates frame_index = 0; // we'll be starting over... measurement_data_is_valid = 0; } @@ -1104,13 +1182,15 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { } int delay(long *the_delay) { - // returns 0 if the device is in a valid state -- SND_PCM_STATE_RUNNING or SND_PCM_STATE_PREPARED + // returns 0 if the device is in a valid state -- SND_PCM_STATE_RUNNING or + // SND_PCM_STATE_PREPARED // or SND_PCM_STATE_DRAINING // and returns the actual delay if running or 0 if prepared in *the_delay // otherwise return an error code // the error code could be a Unix errno code or a snderror code, or - // the sps_extra_code_output_stalled or the sps_extra_code_output_state_cannot_make_ready codes + // the sps_extra_code_output_stalled or the + // sps_extra_code_output_state_cannot_make_ready codes int ret = 0; *the_delay = 0; if (alsa_handle == NULL) @@ -1212,7 +1292,8 @@ int do_play(void *buf, int samples) { while ((ret = snd_pcm_resume(alsa_handle)) == -EAGAIN) { sleep(1); /* wait until the suspend flag is released */ if (ret < 0) { - warn("alsa: can't recover from SND_PCM_STATE_SUSPENDED state, snd_pcm_prepare() " + warn("alsa: can't recover from SND_PCM_STATE_SUSPENDED state, " + "snd_pcm_prepare() " "failed: %s.", snd_strerror(ret)); } @@ -1221,8 +1302,9 @@ int do_play(void *buf, int samples) { } } } else { - debug(1, "alsa: device status returns fault status %d and SND_PCM_STATE_* %d for play.", ret, - state); + debug(1, "alsa: device status returns fault status %d and SND_PCM_STATE_* " + "%d for play.", + ret, state); frame_index = 0; measurement_data_is_valid = 0; } @@ -1234,15 +1316,21 @@ int do_play(void *buf, int samples) { int do_open() { int ret = 0; if (alsa_backend_state != abm_disconnected) - debug(1, "alsa: do_open() -- opening the output device when it is already connected"); + debug(1, "alsa: do_open() -- opening the output device when it is already " + "connected"); if (alsa_handle == NULL) { // debug(1,"alsa: do_open() -- opening the output device"); ret = open_alsa_device(); if (ret == 0) { + mute_requested_internally = 0; if (audio_alsa.volume) do_volume(set_volume); - if (audio_alsa.mute) - do_mute(0); + if (audio_alsa.mute) { + debug(1, "do_open() set_mute_state"); + set_mute_state(); // the mute_requested_externally flag will have been + // set accordingly + // do_mute(0); // complete unmute + } alsa_backend_state = abm_connected; // only do this if it really opened it. } } else { @@ -1253,7 +1341,8 @@ int do_open() { int do_close() { if (alsa_backend_state == abm_disconnected) - debug(1, "alsa: do_close() -- closing the output device when it is already disconnected"); + debug(1, "alsa: do_close() -- closing the output device when it is already " + "disconnected"); int derr = 0; if (alsa_handle) { // debug(1,"alsa: do_close() -- closing the output device"); @@ -1277,7 +1366,8 @@ int do_close() { int play(void *buf, int samples) { // play() will change the state of the alsa_backend_mode to abm_playing - // also, if the present alsa_backend_state is abm_disconnected, then first the DAC must be + // also, if the present alsa_backend_state is abm_disconnected, then first the + // DAC must be // connected // debug(3,"audio_alsa play called."); @@ -1289,13 +1379,17 @@ int play(void *buf, int samples) { ret = do_open(); if (ret == 0) debug(2, "alsa: play() -- opened output device"); - } + } if (ret == 0) { if (alsa_backend_state != abm_playing) { debug(2, "alsa: play() -- alsa_backend_state => abm_playing"); alsa_backend_state = abm_playing; - do_mute(0); + mute_requested_internally = 0; // stop requesting a mute for backend's own + // reasons, which might have been a flush + debug(1, "play() set_mute_state"); + set_mute_state(); // try to action the request and return a status + // do_mute(0); // unmute for backend's reason } ret = do_play(buf, samples); } @@ -1308,7 +1402,10 @@ int play(void *buf, int samples) { static void flush(void) { // debug(2,"audio_alsa flush called."); pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); - do_mute(1); + mute_requested_internally = 1; // request a mute for backend's reasons + debug(1, "flush() set_mute_state"); + set_mute_state(); + // do_mute(1); // mute for backend's own reasons if (alsa_backend_state != abm_disconnected) { // must be playing or connected... if (config.keep_dac_busy != 0) { debug(2, "alsa: flush() -- alsa_backend_state => abm_connected."); @@ -1334,7 +1431,8 @@ static void parameters(audio_parameters *info) { info->maximum_volume_dB = alsa_mix_maxdb; } -void do_volume(double vol) { // caller is assumed to have the alsa_mutex when using this function +void do_volume(double vol) { // caller is assumed to have the alsa_mutex when + // using this function debug(3, "Setting volume db to %f.", vol); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable @@ -1386,7 +1484,8 @@ static void linear_volume(double vol) { if (hardware_mixer && alsa_mix_handle) { double linear_volume = pow(10, vol); // debug(1,"Linear volume is %f.",linear_volume); - long int_vol = alsa_mix_minv + (alsa_mix_maxv - alsa_mix_minv) * linear_volume; + long int_vol = alsa_mix_minv + (alsa_mix_maxv - alsa_mix_minv) * +linear_volume; // debug(1,"Setting volume to %ld, for volume input of %f.",int_vol,vol); if (alsa_mix_handle) { if (snd_mixer_selem_set_playback_volume_all(alsa_mix_elem, int_vol) != 0) @@ -1397,71 +1496,17 @@ static void linear_volume(double vol) { } */ -int mute(int mute_state_requested) { - debug(1,"alsa: mute(%d)",mute_state_requested); - int response = 0; - if (config.alsa_use_hardware_mute == 1) - response = 1; // return true if actually using the mute facility - // debug(1,"External Mute Request: %d",mute_state_requested); - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); - mute_request_pending = 1; - overriding_mute_state_requested = mute_state_requested; - do_mute(mute_state_requested); - debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(0); // release the mutex - return response; +int mute(int mute_state_requested) { // these would be for external reasons, not + // because of the + // state of the backend. + debug(1, "alsa: mute(%d)", mute_state_requested); + mute_requested_externally = mute_state_requested; // request a mute for external reasons + debug(1, "mute(%d) set_mute_state", mute_state_requested); + return set_mute_state(); } - -void do_mute(int mute_state_requested) { - debug(1, "alsa: do_mute(%d).", mute_state_requested); - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable - - // if a mute is requested now, then - // if an external mute request is in place, leave everything muted - // otherwise, if an external mute request is pending, action it - // otherwise, action the do_mute request - - int local_mute_state_requested = - overriding_mute_state_requested; // go with whatever was asked by the external "mute" call - - // The mute state requested will be actioned unless mute_request_pending is set - // If it is set, then that the pending request will be actioned. - // If the hardware isn't there, or we are not allowed to use it, nothing will be done - // The caller must have the alsa mutex - - if (config.alsa_use_hardware_mute == 1) { - if (mute_request_pending == 0) - local_mute_state_requested = mute_state_requested; - if (open_mixer() == 1) { - if (local_mute_state_requested) { - // debug(1,"Playback Switch mute actually done"); - if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) - snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 0); - else { - // debug(1,"Activating volume-based mute."); - volume_based_mute_is_active = 1; - do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, alsa_mix_mute); - } - } else if (overriding_mute_state_requested == 0) { - // debug(1,"Playback Switch unmute actually done"); - if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) - snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 1); - else { - // debug(1,"Deactivating volume-based mute."); - volume_based_mute_is_active = 0; - do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, set_volume); - } - } - close_mixer(); - } - } - mute_request_pending = 0; - pthread_setcancelstate(oldState, NULL); -} - /* -void alsa_buffer_monitor_thread_cleanup_function(__attribute__((unused)) void *arg) { +void alsa_buffer_monitor_thread_cleanup_function(__attribute__((unused)) void +*arg) { debug(1, "alsa: alsa_buffer_monitor_thread_cleanup_function called."); } */ @@ -1469,7 +1514,8 @@ void alsa_buffer_monitor_thread_cleanup_function(__attribute__((unused)) void *a void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { while (1) { if ((config.keep_dac_busy != 0) && (alsa_device_initialised == 0)) { - debug(1,"alsa: alsa_buffer_monitor_thread_code() calling do_alsa_device_init_if_needed."); + debug(1, "alsa: alsa_buffer_monitor_thread_code() calling " + "do_alsa_device_init_if_needed."); do_alsa_device_init_if_needed(); } int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000); @@ -1478,18 +1524,23 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { if ((alsa_backend_state == abm_disconnected) && (config.keep_dac_busy != 0)) { // open the dac and move to abm_connected mode if (do_open() == 0) - debug(2, "alsa: alsa_buffer_monitor_thread_code() -- output device opened; alsa_backend_state => abm_connected"); + debug(2, "alsa: alsa_buffer_monitor_thread_code() -- output device opened; " + "alsa_backend_state => abm_connected"); } else if ((alsa_backend_state == abm_connected) && (config.keep_dac_busy == 0)) { stall_monitor_start_time = 0; frame_index = 0; measurement_data_is_valid = 0; - debug(2, "alsa: alsa_buffer_monitor_thread_code() -- closing the output device"); + debug(2, "alsa: alsa_buffer_monitor_thread_code() -- closing the output " + "device"); do_close(); - debug(2, "alsa: alsa_buffer_monitor_thread_code() -- alsa_backend_state => abm_disconnected"); + debug(2, "alsa: alsa_buffer_monitor_thread_code() -- alsa_backend_state " + "=> abm_disconnected"); } // now, if the backend is not in the abm_disconnected state - // and config.keep_dac_busy is true (at the present, this has to be the case to be in the - // abm_connected state in the first place...) then do the silence-filling thing, if needed + // and config.keep_dac_busy is true (at the present, this has to be the case + // to be in the + // abm_connected state in the first place...) then do the silence-filling + // thing, if needed if ((alsa_backend_state != abm_disconnected) && (config.keep_dac_busy != 0)) { int reply; long buffer_size = 0; @@ -1511,15 +1562,18 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { sleep_time_in_fp = sleep_time_in_fp << 32; sleep_time_in_fp = sleep_time_in_fp / 1000; // debug(1,"alsa: sleep_time: %d ms or 0x%" PRIx64 " in fp - // form.",sleep_time_ms,sleep_time_in_fp); int frames_of_silence = (desired_sample_rate * + // form.",sleep_time_ms,sleep_time_in_fp); int frames_of_silence = + // (desired_sample_rate * // sleep_time_ms * 2) / 1000; int frames_of_silence = 1024; size_t size_of_silence_buffer = frames_of_silence * frame_size; - // debug(1, "alsa: alsa_buffer_monitor_thread_code -- silence buffer length: %u bytes.", + // debug(1, "alsa: alsa_buffer_monitor_thread_code -- silence buffer + // length: %u bytes.", // size_of_silence_buffer); void *silence = malloc(size_of_silence_buffer); if (silence == NULL) { - debug(1, "alsa: alsa_buffer_monitor_thread_code -- failed to allocate memory for a " + debug(1, "alsa: alsa_buffer_monitor_thread_code -- failed to " + "allocate memory for a " "silent frame buffer."); } else { pthread_cleanup_push(malloc_cleanup, silence); @@ -1527,10 +1581,12 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && (config.airplay_volume != 0.0)) use_dither = 1; - dither_random_number_store = generate_zero_frames( - silence, frames_of_silence, config.output_format, use_dither, // i.e. with dither - dither_random_number_store); - // debug(1,"Play %d frames of silence with most_recent_write_time of %" PRIx64 ".", + dither_random_number_store = + generate_zero_frames(silence, frames_of_silence, config.output_format, + use_dither, // i.e. with dither + dither_random_number_store); + // debug(1,"Play %d frames of silence with most_recent_write_time of + // %" PRIx64 ".", // frames_of_silence,most_recent_write_time); do_play(silence, frames_of_silence); pthread_cleanup_pop(1); diff --git a/player.c b/player.c index 642b6e2c..eb2be192 100644 --- a/player.c +++ b/player.c @@ -285,18 +285,16 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_co } if (outsize > toutsize) { - debug(2, - "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- " - "truncated, but buffer overflow possible! Encrypted = %d.", + debug(2, "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- " + "truncated, but buffer overflow possible! Encrypted = %d.", outsize, toutsize, conn->stream.encrypted); reply = -1; // output packet is the wrong size } *destlen = outsize / conn->input_bytes_per_frame; if ((outsize % conn->input_bytes_per_frame) != 0) - debug(1, - "Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) " - "and the audio frame size (%d).", + debug(1, "Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) " + "and the audio frame size (%d).", *destlen, outsize, conn->input_bytes_per_frame); return reply; } @@ -456,10 +454,9 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in if ((conn->flush_rtp_timestamp != 0) && (actual_timestamp != conn->flush_rtp_timestamp) && (modulo_32_offset(actual_timestamp, conn->flush_rtp_timestamp) < conn->input_rate * 10)) { // if it's less than 10 seconds - debug(3, - "Dropping flushed packet in player_put_packet, seqno %u, timestamp %" PRIu32 - ", flushing to " - "timestamp: %" PRIu32 ".", + debug(3, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %" PRIu32 + ", flushing to " + "timestamp: %" PRIu32 ".", seqno, actual_timestamp, conn->flush_rtp_timestamp); conn->initial_reference_time = 0; conn->initial_reference_timestamp = 0; @@ -852,10 +849,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { (curframe->given_timestamp != conn->flush_rtp_timestamp) && (modulo_32_offset(curframe->given_timestamp, conn->flush_rtp_timestamp) < conn->input_rate * 10)) { // if it's less than ten seconds - debug(3, - "Dropping flushed packet in buffer_get_frame, seqno %u, timestamp %" PRIu32 - ", flushing to " - "timestamp: %" PRIu32 ".", + debug(3, "Dropping flushed packet in buffer_get_frame, seqno %u, timestamp %" PRIu32 + ", flushing to " + "timestamp: %" PRIu32 ".", curframe->sequence_number, curframe->given_timestamp, conn->flush_rtp_timestamp); curframe->ready = 0; curframe->resend_level = 0; @@ -938,7 +934,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { frame_to_local_time(conn->first_packet_timestamp + conn->latency + (uint32_t)(config.audio_backend_latency_offset * conn->input_rate), // this will go modulo 2^32 - &should_be_time, conn); + &should_be_time, + conn); conn->first_packet_time_to_play = should_be_time; @@ -959,7 +956,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { frame_to_local_time(conn->first_packet_timestamp + conn->latency + (uint32_t)(config.audio_backend_latency_offset * conn->input_rate), // this should go modulo 2^32 - &should_be_time, conn); + &should_be_time, + conn); conn->first_packet_time_to_play = should_be_time; @@ -1030,9 +1028,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // this might happen if a big clock adjustment was made at just the wrong // time. - debug(1, - "Run a bit past the exact start time by %" PRId64 - " frames with a DAC delay of %ld frames.", + debug(1, "Run a bit past the exact start time by %" PRId64 + " frames with a DAC delay of %ld frames.", -exact_frame_gap, dac_delay); if (config.output->flush) config.output->flush(); @@ -1190,7 +1187,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { (uint32_t)(config.audio_backend_latency_offset * conn->input_rate) - (uint32_t)(config.audio_backend_buffer_desired_length * conn->input_rate), // this will go modulo 2^32 - &time_to_play, conn); + &time_to_play, + conn); if (local_time_now >= time_to_play) { do_wait = 0; @@ -1670,17 +1668,17 @@ void *player_thread_func(void *arg) { // if ((input_rate!=config.output_rate) || (input_bit_depth!=output_bit_depth)) { // debug(1,"Define tbuf of length // %d.",output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); - conn->tbuf = malloc( - sizeof(int32_t) * 2 * - (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change)); + conn->tbuf = + malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + + conn->max_frame_size_change)); if (conn->tbuf == NULL) die("Failed to allocate memory for the transition buffer."); // initialise this, because soxr stuffing might be chosen later - conn->sbuf = malloc( - sizeof(int32_t) * 2 * - (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change)); + conn->sbuf = + malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + + conn->max_frame_size_change)); if (conn->sbuf == NULL) die("Failed to allocate memory for the sbuf buffer."); @@ -1968,9 +1966,8 @@ void *player_thread_func(void *arg) { SUCCESSOR(conn->last_seqno_read); // int32_t from seq_t, i.e. uint16_t, so okay. if (inframe->sequence_number != conn->last_seqno_read) { // seq_t, ei.e. uint16_t and int32_t, so okay - debug(2, - "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u " - "and ab_write: %u.", + debug(2, "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u " + "and ab_write: %u.", conn->last_seqno_read, inframe->sequence_number, conn->ab_read, conn->ab_write); conn->last_seqno_read = inframe->sequence_number; // reset warning... } @@ -2195,7 +2192,7 @@ void *player_thread_func(void *arg) { #ifdef CONFIG_CONVOLUTION || config.convolution #endif - ) { + ) { int32_t *tbuf32 = (int32_t *)conn->tbuf; float fbuf_l[inbuflength]; float fbuf_r[inbuflength]; @@ -2279,9 +2276,8 @@ void *player_thread_func(void *arg) { debug(1, "play_samples==0 skipping it (1)."); else { if (conn->software_mute_enabled) { - generate_zero_frames( - conn->outbuf, play_samples, - config.output_format, conn->enable_dither, conn->previous_random_number); + generate_zero_frames(conn->outbuf, play_samples, config.output_format, + conn->enable_dither, conn->previous_random_number); } config.output->play(conn->outbuf, play_samples); } @@ -2320,9 +2316,8 @@ void *player_thread_func(void *arg) { debug(1, "NULL outbuf to play -- skipping it."); else { if (conn->software_mute_enabled) { - generate_zero_frames( - conn->outbuf, play_samples, - config.output_format, conn->enable_dither, conn->previous_random_number); + generate_zero_frames(conn->outbuf, play_samples, config.output_format, + conn->enable_dither, conn->previous_random_number); } config.output->play(conn->outbuf, play_samples); // remove the (short*)! } @@ -2445,41 +2440,42 @@ void *player_thread_func(void *arg) { if ((config.output->delay)) { if (config.no_sync == 0) { - inform( - "%*.2f," /* Sync error in milliseconds */ - "%*.1f," /* net correction in ppm */ - "%*.1f," /* corrections in ppm */ - "%*d," /* total packets */ - "%*" PRIu64 "," /* missing packets */ - "%*" PRIu64 "," /* late packets */ - "%*" PRIu64 "," /* too late packets */ - "%*" PRIu64 "," /* resend requests */ - "%*" PRId64 "," /* min DAC queue size */ - "%*" PRId32 "," /* min buffer occupancy */ - "%*" PRId32 "," /* max buffer occupancy */ - "%*.2f," /* source nominal frame rate */ - "%*.2f," /* source actual (average) frame rate */ - "%*.2f," /* output frame rate */ - "%*.2f," /* source clock drift */ - "%*d," /* source clock drift sample count */ - "%*.2f", /* rough calculated correction in ppm */ - 10, 1000 * moving_average_sync_error / config.output_rate, 10, - moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), 10, - moving_average_insertions_plus_deletions * 1000000 / - (352 * conn->output_sample_ratio), - 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, - conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, - 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy, 11, - conn->remote_frame_rate, 11, conn->input_frame_rate, 11, conn->frame_rate, 10, - (conn->local_to_remote_time_gradient - 1.0) * 1000000, 6, - conn->local_to_remote_time_gradient_sample_count, 10, - (conn->frame_rate > 0.0) - ? ((conn->frame_rate - conn->remote_frame_rate * - conn->output_sample_ratio * - conn->local_to_remote_time_gradient) * - 1000000) / - conn->frame_rate - : 0.0); + inform("%*.2f," /* Sync error in milliseconds */ + "%*.1f," /* net correction in ppm */ + "%*.1f," /* corrections in ppm */ + "%*d," /* total packets */ + "%*" PRIu64 "," /* missing packets */ + "%*" PRIu64 "," /* late packets */ + "%*" PRIu64 "," /* too late packets */ + "%*" PRIu64 "," /* resend requests */ + "%*" PRId64 "," /* min DAC queue size */ + "%*" PRId32 "," /* min buffer occupancy */ + "%*" PRId32 "," /* max buffer occupancy */ + "%*.2f," /* source nominal frame rate */ + "%*.2f," /* source actual (average) frame rate */ + "%*.2f," /* output frame rate */ + "%*.2f," /* source clock drift */ + "%*d," /* source clock drift sample count */ + "%*.2f", /* rough calculated correction in ppm */ + 10, + 1000 * moving_average_sync_error / config.output_rate, 10, + moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), + 10, moving_average_insertions_plus_deletions * 1000000 / + (352 * conn->output_sample_ratio), + 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, + conn->too_late_packets, 7, conn->resend_requests, 7, + minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, + maximum_buffer_occupancy, 11, conn->remote_frame_rate, 11, + conn->input_frame_rate, 11, conn->frame_rate, 10, + (conn->local_to_remote_time_gradient - 1.0) * 1000000, 6, + conn->local_to_remote_time_gradient_sample_count, 10, + (conn->frame_rate > 0.0) + ? ((conn->frame_rate - + conn->remote_frame_rate * conn->output_sample_ratio * + conn->local_to_remote_time_gradient) * + 1000000) / + conn->frame_rate + : 0.0); } else { inform("%*.2f," /* Sync error in milliseconds */ "%*d," /* total packets */ @@ -2494,9 +2490,10 @@ void *player_thread_func(void *arg) { "%*.2f," /* source actual (average) frame rate */ "%*.2f," /* source clock drift */ "%*d", /* source clock drift sample count */ - 10, 1000 * moving_average_sync_error / config.output_rate, 12, play_number, - 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, - 7, conn->resend_requests, 7, minimum_dac_queue_size, 5, + 10, + 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, + conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, + conn->resend_requests, 7, minimum_dac_queue_size, 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, @@ -2515,9 +2512,10 @@ void *player_thread_func(void *arg) { "%*.2f," /* source actual (average) frame rate */ "%*.2f," /* source clock drift */ "%*d", /* source clock drift sample count */ - 10, 1000 * moving_average_sync_error / config.output_rate, 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, + 10, + 1000 * moving_average_sync_error / config.output_rate, 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, @@ -2544,14 +2542,16 @@ void *player_thread_func(void *arg) { } void player_volume_without_notification(double airplay_volume, rtsp_conn_info *conn) { - debug(1,"player_volume_without_notification %f",airplay_volume); -//first, see if we are hw only, sw only, both with hw attenuation on the top or both with sw attenuation on top + debug(1, "player_volume_without_notification %f", airplay_volume); + // first, see if we are hw only, sw only, both with hw attenuation on the top or both with sw + // attenuation on top - enum volume_mode_type {vol_sw_only, vol_hw_only, vol_both} volume_mode; - - // take account of whether there is a hardware mixer, if a max volume has been specified and if a range has been specified + enum volume_mode_type { vol_sw_only, vol_hw_only, vol_both } volume_mode; + + // take account of whether there is a hardware mixer, if a max volume has been specified and if a + // range has been specified // the range might imply that both hw and software mixers are needed, so calculate this - + int32_t hw_max_db, hw_min_db; int32_t sw_max_db = 0, sw_min_db = -9630; if (config.output->parameters) { @@ -2569,9 +2569,9 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c sw_max_db = (config.volume_max_db * 100) - hw_min_db; } else { warn("The maximum output level is outside the range of the hardware mixer -- ignored"); - } + } } - + // here, we have set limits on the hw_max_db and the sw_max_db // but we haven't actually decided whether we need both hw and software attenuation // only if a range is specified could we need both @@ -2583,9 +2583,9 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c int32_t desired_sw_range = desired_range_db - (hw_max_db - hw_min_db); if ((sw_max_db - desired_sw_range) < sw_min_db) warn("The range requested is too large to accommodate -- ignored."); - else + else sw_min_db = (sw_max_db - desired_sw_range); - } + } } } else { // debug(1,"has no hardware mixer"); @@ -2593,112 +2593,116 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c if (config.volume_max_db_set) { if (((config.volume_max_db * 100) <= sw_max_db) && ((config.volume_max_db * 100) >= sw_min_db)) - sw_max_db = (int32_t)config.volume_max_db * 100; + sw_max_db = (int32_t)config.volume_max_db * 100; } if (config.volume_range_db) { - // see if the range requested exceeds the software range available - int32_t desired_range_db = (int32_t)trunc(config.volume_range_db * 100); - if ((desired_range_db) > (sw_max_db - sw_min_db)) - warn("The range requested is too large to accommodate -- ignored."); - else - sw_min_db = (sw_max_db - desired_range_db); - } + // see if the range requested exceeds the software range available + int32_t desired_range_db = (int32_t)trunc(config.volume_range_db * 100); + if ((desired_range_db) > (sw_max_db - sw_min_db)) + warn("The range requested is too large to accommodate -- ignored."); + else + sw_min_db = (sw_max_db - desired_range_db); + } } - - // here, we know whether it's hw volume control only, sw only or both, and we have the hw and sw limits. + + // here, we know whether it's hw volume control only, sw only or both, and we have the hw and sw + // limits. // if it's both, we haven't decided whether hw or sw should be on top // we have to consider the settings ignore_volume_control and mute. - + if (config.ignore_volume_control == 0) { if (airplay_volume == -144.0) { - - if ((config.output->mute) && (config.output->mute(1) != 0)) - debug(1,"hardware mute is enabled."); + + if ((config.output->mute) && (config.output->mute(1) == 0)) + debug(1, "player: hardware mute is enabled."); else { conn->software_mute_enabled = 1; - debug(1,"software mute is enabled."); - + debug(1, "player: software mute is enabled."); } } else { - int32_t max_db,min_db; + int32_t max_db, min_db; switch (volume_mode) { - case vol_hw_only: - max_db = hw_max_db; - min_db = hw_min_db; - break; - case vol_sw_only: - max_db = sw_max_db; - min_db = sw_min_db; - break; - case vol_both: - debug(1,"dB range passed is hw: %d, sw: %d, total: %d", hw_max_db - hw_min_db, sw_max_db - sw_min_db, (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db)); - max_db = (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db); // this should be the range requested - min_db = 0; - break; - default: - debug(1,"error in pv -- not in a volume mode"); - break; + case vol_hw_only: + max_db = hw_max_db; + min_db = hw_min_db; + break; + case vol_sw_only: + max_db = sw_max_db; + min_db = sw_min_db; + break; + case vol_both: + debug(1, "dB range passed is hw: %d, sw: %d, total: %d", hw_max_db - hw_min_db, + sw_max_db - sw_min_db, (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db)); + max_db = + (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db); // this should be the range requested + min_db = 0; + break; + default: + debug(1, "error in pv -- not in a volume mode"); + break; } double scaled_attenuation = 0.0; if (config.volume_control_profile == VCP_standard) scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); // no cancellation points else if (config.volume_control_profile == VCP_flat) - scaled_attenuation = flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points + scaled_attenuation = + flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points else debug(1, "Unrecognised volume control profile"); - + // so here we have the scaled attenuation. If it's for hw or sw only, it's straightforward. double hardware_attenuation = 0.0; double software_attenuation = 0.0; switch (volume_mode) { - case vol_hw_only: - hardware_attenuation = scaled_attenuation; - break; - case vol_sw_only: - software_attenuation = scaled_attenuation; - break; - case vol_both: - // here, we now the attenuation required, so we have to apportion it to the sw and hw mixers - // if we give the hw priority, that means when lowering the volume, set the hw volume to its lowest - // before using the sw attenuation. - // similarly, if we give the sw priority, that means when lowering the volume, set the sw volume to its lowest - // before using the hw attenuation. - // one imagines that hw priority is likely to be much better - if (config.volume_range_hw_priority) { - // hw priority - if ((sw_max_db - sw_min_db) > scaled_attenuation) { - software_attenuation = sw_min_db + scaled_attenuation; - hardware_attenuation = hw_min_db; - } else { - software_attenuation = sw_max_db; - hardware_attenuation = hw_min_db + scaled_attenuation - (sw_max_db - sw_min_db); - } - } else { - // sw priority - if ((hw_max_db - hw_min_db) > scaled_attenuation) { - hardware_attenuation = hw_min_db + scaled_attenuation; - software_attenuation = sw_min_db; - } else { - hardware_attenuation = hw_max_db; - software_attenuation = sw_min_db + scaled_attenuation - (hw_max_db - hw_min_db); - } + case vol_hw_only: + hardware_attenuation = scaled_attenuation; + break; + case vol_sw_only: + software_attenuation = scaled_attenuation; + break; + case vol_both: + // here, we now the attenuation required, so we have to apportion it to the sw and hw mixers + // if we give the hw priority, that means when lowering the volume, set the hw volume to its + // lowest + // before using the sw attenuation. + // similarly, if we give the sw priority, that means when lowering the volume, set the sw + // volume to its lowest + // before using the hw attenuation. + // one imagines that hw priority is likely to be much better + if (config.volume_range_hw_priority) { + // hw priority + if ((sw_max_db - sw_min_db) > scaled_attenuation) { + software_attenuation = sw_min_db + scaled_attenuation; + hardware_attenuation = hw_min_db; + } else { + software_attenuation = sw_max_db; + hardware_attenuation = hw_min_db + scaled_attenuation - (sw_max_db - sw_min_db); } - break; - default: - debug(1,"error in pv -- not in a volume mode"); - break; + } else { + // sw priority + if ((hw_max_db - hw_min_db) > scaled_attenuation) { + hardware_attenuation = hw_min_db + scaled_attenuation; + software_attenuation = sw_min_db; + } else { + hardware_attenuation = hw_max_db; + software_attenuation = sw_min_db + scaled_attenuation - (hw_max_db - hw_min_db); + } + } + break; + default: + debug(1, "error in pv -- not in a volume mode"); + break; } - - + if (((volume_mode == vol_hw_only) || (volume_mode == vol_both)) && (config.output->volume)) { config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value // debug(1,"Hardware attenuation set to %f for airplay volume of // %f.",hardware_attenuation,airplay_volume); conn->fix_volume = 0x10000; } - + if ((volume_mode == vol_sw_only) || (volume_mode == vol_both)) { double temp_fix_volume = 65536.0 * pow(10, software_attenuation / 2000); // debug(1,"Software attenuation set to %f, i.e %f out of 65,536, for airplay volume of @@ -2711,7 +2715,6 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c loudness_set_volume(software_attenuation / 100); } - if (config.logOutputLevel) { inform("Output Level set to: %.2f dB.", scaled_attenuation / 100.0); } @@ -2733,8 +2736,10 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c if (config.output->mute) config.output->mute(0); conn->software_mute_enabled = 0; - - debug(1,"pv: volume mode is %d, software_attenuation: %f, hardware_attenuation: %f, muting is disabled.", volume_mode, software_attenuation, hardware_attenuation); + + debug(1, "pv: volume mode is %d, software_attenuation: %f, hardware_attenuation: %f, muting " + "is disabled.", + volume_mode, software_attenuation, hardware_attenuation); } } config.airplay_volume = airplay_volume; From 60487c86b1ca8409faae05037dcb9818ee7d1cf0 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 31 Jan 2019 13:35:11 +0000 Subject: [PATCH 404/569] Make mute work only if not diconnected. Clewan up some debug messages. --- audio_alsa.c | 74 +++++++++++++++++++++++++--------------------------- player.c | 16 ++++++------ 2 files changed, 44 insertions(+), 46 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 9be90890..a9f89cfe 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -521,17 +521,17 @@ int actual_open_alsa_device(void) { debug(log_level, "PCM handle name = '%s'", snd_pcm_name(alsa_handle)); - // ret = snd_pcm_hw_params_any(alsa_handle, alsa_params); - // if (ret < 0) { - // die("audio_alsa: Cannpot get configuration for + // ret = snd_pcm_hw_params_any(alsa_handle, alsa_params); + // if (ret < 0) { + // die("audio_alsa: Cannpot get configuration for // device //\"%s\": // no // configurations //" - // "available", - // alsa_out_dev); - // } + // "available", + // alsa_out_dev); + // } debug(log_level, "alsa device parameters:"); @@ -649,7 +649,6 @@ int open_alsa_device(void) { } int do_alsa_device_init_if_needed() { - debug(1, "do_alsa_device_init_if_needed()"); int response = 0; // do any alsa device initialisation (general case) if needed // at present, this is only needed if a hardware mixer is being used @@ -657,6 +656,7 @@ int do_alsa_device_init_if_needed() { if (alsa_device_initialised == 0) { alsa_device_initialised = 1; if (hardware_mixer) { + debug(2, "alsa: hardware mixer init"); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable @@ -1052,38 +1052,37 @@ static void deinit(void) { } int set_mute_state() { - debug(1, "set_mute_state"); - int response = 1; // some problem expected, e.g. no mixer or not allowed to use it - if (config.alsa_use_hardware_mute == 1) { + int response = 1; // some problem expected, e.g. no mixer or not allowed to use it or disconnected + if ((alsa_backend_state != abm_disconnected) && (config.alsa_use_hardware_mute == 1) && (open_mixer() == 1)) { + response = 0; // okay if actually using the mute facility + debug(1, "set_mute_state"); int mute = 0; if ((mute_requested_externally != 0) || (mute_requested_internally != 0)) - mute = 1; - if (open_mixer() == 1) { - response = 0; // okay if actually using the mute facility - if (mute == 1) { - debug(1, "Hardware mute switched on"); - if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) - snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 0); - else { - volume_based_mute_is_active = 1; - do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, alsa_mix_mute); - } - } else { - debug(1, "Hardware mute switched off"); - if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) - snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 1); - else { - volume_based_mute_is_active = 0; - do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, set_volume); - } + mute = 1; + if (mute == 1) { + debug(1, "Hardware mute switched on"); + if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) + snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 0); + else { + volume_based_mute_is_active = 1; + do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, alsa_mix_mute); + } + } else { + debug(1, "Hardware mute switched off"); + if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) + snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 1); + else { + volume_based_mute_is_active = 0; + do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, set_volume); } - close_mixer(); } + close_mixer(); } return response; } + static void start(int i_sample_rate, int i_sample_format) { - debug(1, "audio_alsa start called."); + debug(3, "audio_alsa start called."); if (i_sample_rate == 0) desired_sample_rate = 44100; // default else @@ -1100,7 +1099,7 @@ static void start(int i_sample_rate, int i_sample_format) { stall_monitor_start_time = 0; stall_monitor_frame_count = 0; if (alsa_device_initialised == 0) { - debug(1, "alsa: start() calling do_alsa_device_init_if_needed."); + debug(2, "alsa: start() calling do_alsa_device_init_if_needed."); do_alsa_device_init_if_needed(); } } @@ -1326,7 +1325,7 @@ int do_open() { if (audio_alsa.volume) do_volume(set_volume); if (audio_alsa.mute) { - debug(1, "do_open() set_mute_state"); + debug(2, "do_open() set_mute_state"); set_mute_state(); // the mute_requested_externally flag will have been // set accordingly // do_mute(0); // complete unmute @@ -1387,7 +1386,7 @@ int play(void *buf, int samples) { alsa_backend_state = abm_playing; mute_requested_internally = 0; // stop requesting a mute for backend's own // reasons, which might have been a flush - debug(1, "play() set_mute_state"); + debug(2, "play() set_mute_state"); set_mute_state(); // try to action the request and return a status // do_mute(0); // unmute for backend's reason } @@ -1403,7 +1402,7 @@ static void flush(void) { // debug(2,"audio_alsa flush called."); pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); mute_requested_internally = 1; // request a mute for backend's reasons - debug(1, "flush() set_mute_state"); + debug(2, "flush() set_mute_state"); set_mute_state(); // do_mute(1); // mute for backend's own reasons if (alsa_backend_state != abm_disconnected) { // must be playing or connected... @@ -1499,9 +1498,8 @@ linear_volume; int mute(int mute_state_requested) { // these would be for external reasons, not // because of the // state of the backend. - debug(1, "alsa: mute(%d)", mute_state_requested); mute_requested_externally = mute_state_requested; // request a mute for external reasons - debug(1, "mute(%d) set_mute_state", mute_state_requested); + debug(2, "mute(%d) set_mute_state", mute_state_requested); return set_mute_state(); } /* @@ -1514,7 +1512,7 @@ void alsa_buffer_monitor_thread_cleanup_function(__attribute__((unused)) void void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { while (1) { if ((config.keep_dac_busy != 0) && (alsa_device_initialised == 0)) { - debug(1, "alsa: alsa_buffer_monitor_thread_code() calling " + debug(2, "alsa: alsa_buffer_monitor_thread_code() calling " "do_alsa_device_init_if_needed."); do_alsa_device_init_if_needed(); } diff --git a/player.c b/player.c index eb2be192..fa2e2c3c 100644 --- a/player.c +++ b/player.c @@ -2542,7 +2542,7 @@ void *player_thread_func(void *arg) { } void player_volume_without_notification(double airplay_volume, rtsp_conn_info *conn) { - debug(1, "player_volume_without_notification %f", airplay_volume); + debug(2, "player_volume_without_notification %f", airplay_volume); // first, see if we are hw only, sw only, both with hw attenuation on the top or both with sw // attenuation on top @@ -2614,10 +2614,10 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c if (airplay_volume == -144.0) { if ((config.output->mute) && (config.output->mute(1) == 0)) - debug(1, "player: hardware mute is enabled."); + debug(2, "player_volume_without_notification: volume mode is %d, airplay_volume is %f, hardware mute is enabled.", volume_mode, airplay_volume); else { conn->software_mute_enabled = 1; - debug(1, "player: software mute is enabled."); + debug(2, "player_volume_without_notification: volume mode is %d, airplay_volume is %f, software mute is enabled.", volume_mode, airplay_volume); } } else { @@ -2639,7 +2639,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c min_db = 0; break; default: - debug(1, "error in pv -- not in a volume mode"); + debug(1, "player_volume_without_notification: error: not in a volume mode"); break; } double scaled_attenuation = 0.0; @@ -2649,7 +2649,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c scaled_attenuation = flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points else - debug(1, "Unrecognised volume control profile"); + debug(1, "player_volume_without_notification: unrecognised volume control profile"); // so here we have the scaled attenuation. If it's for hw or sw only, it's straightforward. double hardware_attenuation = 0.0; @@ -2692,7 +2692,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c } break; default: - debug(1, "error in pv -- not in a volume mode"); + debug(1, "player_volume_without_notification: error: not in a volume mode"); break; } @@ -2737,9 +2737,9 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c config.output->mute(0); conn->software_mute_enabled = 0; - debug(1, "pv: volume mode is %d, software_attenuation: %f, hardware_attenuation: %f, muting " + debug(2, "player_volume_without_notification: volume mode is %d, airplay volume is %f, software_attenuation: %f, hardware_attenuation: %f, muting " "is disabled.", - volume_mode, software_attenuation, hardware_attenuation); + volume_mode, airplay_volume, software_attenuation, hardware_attenuation); } } config.airplay_volume = airplay_volume; From 2c0250b32a9a01b584785d7e55598eb1570d8363 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 31 Jan 2019 17:11:33 +0000 Subject: [PATCH 405/569] Don't do a hardware mute when doing a flush -- if disable_standby is true, the mode goes to connected and then if player goes back to play immediately, you might hear some of the sound still moving through the output buffer. This happens due to a bug in IOS player. --- audio_alsa.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index a9f89cfe..ddb393d5 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1053,12 +1053,13 @@ static void deinit(void) { int set_mute_state() { int response = 1; // some problem expected, e.g. no mixer or not allowed to use it or disconnected - if ((alsa_backend_state != abm_disconnected) && (config.alsa_use_hardware_mute == 1) && (open_mixer() == 1)) { + if ((alsa_backend_state != abm_disconnected) && (config.alsa_use_hardware_mute == 1) && + (open_mixer() == 1)) { response = 0; // okay if actually using the mute facility debug(1, "set_mute_state"); int mute = 0; if ((mute_requested_externally != 0) || (mute_requested_internally != 0)) - mute = 1; + mute = 1; if (mute == 1) { debug(1, "Hardware mute switched on"); if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) @@ -1401,9 +1402,9 @@ int play(void *buf, int samples) { static void flush(void) { // debug(2,"audio_alsa flush called."); pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); - mute_requested_internally = 1; // request a mute for backend's reasons - debug(2, "flush() set_mute_state"); - set_mute_state(); + // mute_requested_internally = 1; // request a mute for backend's reasons + // debug(2, "flush() set_mute_state"); + // set_mute_state(); // do_mute(1); // mute for backend's own reasons if (alsa_backend_state != abm_disconnected) { // must be playing or connected... if (config.keep_dac_busy != 0) { @@ -1495,9 +1496,9 @@ linear_volume; } */ -int mute(int mute_state_requested) { // these would be for external reasons, not - // because of the - // state of the backend. +int mute(int mute_state_requested) { // these would be for external reasons, not + // because of the + // state of the backend. mute_requested_externally = mute_state_requested; // request a mute for external reasons debug(2, "mute(%d) set_mute_state", mute_state_requested); return set_mute_state(); From 0c5521488c77ca9b618bbbb68f13e1552b011ec9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 1 Feb 2019 12:38:45 +0000 Subject: [PATCH 406/569] add a special mixer-only mutex, do make open/close mixer stuff safer and probably quicker, not waiting for the main alsa mutex. --- audio_alsa.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index ddb393d5..c85712f8 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -80,6 +80,7 @@ audio_output audio_alsa = { .parameters = NULL}; // a function will be provided if it can do hardware volume static pthread_mutex_t alsa_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t alsa_mixer_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_t alsa_buffer_monitor_thread; @@ -665,7 +666,7 @@ int do_alsa_device_init_if_needed() { // Now, start trying to initialise the alsa device with the settings // obtained - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 1000, 1); + pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 1000, 1); if (open_mixer() == 1) { if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) < 0) @@ -754,8 +755,7 @@ int do_alsa_device_init_if_needed() { } close_mixer(); } - debug_mutex_unlock(&alsa_mutex, 3); // release the mutex - + debug_mutex_unlock(&alsa_mixer_mutex, 3); // release the mutex pthread_cleanup_pop(0); pthread_setcancelstate(oldState, NULL); } @@ -1023,8 +1023,6 @@ static int init(int argc, char **argv) { debug(1, "alsa: output device name is \"%s\".", alsa_out_dev); - alsa_mix_handle = NULL; // indicates alsa handle isn't open - // so, now, if the option to keep the DAC running has been selected, start a // thread to monitor the // length of the queue @@ -1053,6 +1051,7 @@ static void deinit(void) { int set_mute_state() { int response = 1; // some problem expected, e.g. no mixer or not allowed to use it or disconnected + pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 10000, 0); if ((alsa_backend_state != abm_disconnected) && (config.alsa_use_hardware_mute == 1) && (open_mixer() == 1)) { response = 0; // okay if actually using the mute facility @@ -1079,6 +1078,8 @@ int set_mute_state() { } close_mixer(); } + debug_mutex_unlock(&alsa_mixer_mutex, 3); // release the mutex + pthread_cleanup_pop(0); // release the mutex return response; } @@ -1385,10 +1386,11 @@ int play(void *buf, int samples) { if (alsa_backend_state != abm_playing) { debug(2, "alsa: play() -- alsa_backend_state => abm_playing"); alsa_backend_state = abm_playing; - mute_requested_internally = 0; // stop requesting a mute for backend's own + + // mute_requested_internally = 0; // stop requesting a mute for backend's own // reasons, which might have been a flush - debug(2, "play() set_mute_state"); - set_mute_state(); // try to action the request and return a status + //debug(2, "play() set_mute_state"); + //set_mute_state(); // try to action the request and return a status // do_mute(0); // unmute for backend's reason } ret = do_play(buf, samples); @@ -1437,6 +1439,7 @@ void do_volume(double vol) { // caller is assumed to have the alsa_mutex when int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable set_volume = vol; + pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 1000, 1); if (volume_set_request && (open_mixer() == 1)) { if (has_softvol) { if (ctl && elem_id) { @@ -1466,15 +1469,14 @@ void do_volume(double vol) { // caller is assumed to have the alsa_mutex when volume_set_request = 0; // any external request that has been made is now satisfied close_mixer(); } + debug_mutex_unlock(&alsa_mixer_mutex, 3); + pthread_cleanup_pop(0); // release the mutex pthread_setcancelstate(oldState, NULL); } void volume(double vol) { - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 1000, 1); volume_set_request = 1; // an external request has been made to set the volume do_volume(vol); - debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(0); // release the mutex } /* From 02bc269a4a1f4ec3d77729ffca94eb57d2652068 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 1 Feb 2019 16:17:53 +0000 Subject: [PATCH 407/569] Fix a situation where cancellation could lose a malloced buffer. --- rtsp.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/rtsp.c b/rtsp.c index f7c6acce..c60f859e 100644 --- a/rtsp.c +++ b/rtsp.c @@ -576,13 +576,13 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes enum rtsp_read_request_response reply = rtsp_read_request_response_ok; ssize_t buflen = 4096; + int release_buffer = 0; // on exit, don't deallocate the buffer if everything was okay char *buf = malloc(buflen + 1); // add a NUL at the end if (!buf) { warn("rtsp_read_request: can't get a buffer."); - reply = rtsp_read_request_response_error; - goto shutdown; - } - + return(rtsp_read_request_response_error); + } + pthread_cleanup_push(malloc_cleanup, buf); ssize_t nread; ssize_t inbuf = 0; int msg_size = -1; @@ -717,11 +717,12 @@ do { char *jp = inbuf + buf; *jp = '\0'; *the_packet = msg; - return reply; - shutdown: - msg_free(the_packet); - free(buf); + if (reply != rtsp_read_request_response_ok) { + msg_free(the_packet); + release_buffer = 1; // allow the buffer to be released + } + pthread_cleanup_pop(release_buffer); return reply; } From bfc7b9b774c9f50f4408a283ae0b0bc7483c7c72 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 1 Feb 2019 16:19:27 +0000 Subject: [PATCH 408/569] lengthen some mutex timeouts to allow valgrind to work okay on a Pi 3B. Make operations that open the mixer uncancellable until it's closed again to stop a few small memory leaks when a player is cancelled. --- audio_alsa.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index c85712f8..e54230da 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1051,16 +1051,18 @@ static void deinit(void) { int set_mute_state() { int response = 1; // some problem expected, e.g. no mixer or not allowed to use it or disconnected + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 10000, 0); if ((alsa_backend_state != abm_disconnected) && (config.alsa_use_hardware_mute == 1) && (open_mixer() == 1)) { response = 0; // okay if actually using the mute facility - debug(1, "set_mute_state"); + debug(2, "alsa: actually set_mute_state"); int mute = 0; if ((mute_requested_externally != 0) || (mute_requested_internally != 0)) mute = 1; if (mute == 1) { - debug(1, "Hardware mute switched on"); + debug(2, "alsa: hardware mute switched on"); if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 0); else { @@ -1068,7 +1070,7 @@ int set_mute_state() { do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, alsa_mix_mute); } } else { - debug(1, "Hardware mute switched off"); + debug(2, "alsa: hardware mute switched off"); if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 1); else { @@ -1080,6 +1082,7 @@ int set_mute_state() { } debug_mutex_unlock(&alsa_mixer_mutex, 3); // release the mutex pthread_cleanup_pop(0); // release the mutex + pthread_setcancelstate(oldState, NULL); return response; } @@ -1374,7 +1377,7 @@ int play(void *buf, int samples) { // debug(3,"audio_alsa play called."); int ret = 0; - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 50000, 1); if (alsa_backend_state == abm_disconnected) { ret = do_open(); @@ -1520,7 +1523,7 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { do_alsa_device_init_if_needed(); } int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000); - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 20000, 1); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 200000, 1); // check possible state transitions here if ((alsa_backend_state == abm_disconnected) && (config.keep_dac_busy != 0)) { // open the dac and move to abm_connected mode From 08e84ece27b4c9f4b00af3d3b5a4cc82f4d54964 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 1 Feb 2019 21:07:13 +0000 Subject: [PATCH 409/569] add a volume control mutex to serialise multiple attempts to change the volume --- rtsp.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rtsp.c b/rtsp.c index c60f859e..67e2a88a 100644 --- a/rtsp.c +++ b/rtsp.c @@ -2164,7 +2164,10 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { } // remove flow control and mutexes - int rc = pthread_cond_destroy(&conn->flowcontrol); + int rc = pthread_mutex_destroy(&conn->volume_control_mutex); + if (rc) + debug(1, "Connection %d: error %d destroying volume_control_mutex.", conn->connection_number, rc); + rc = pthread_cond_destroy(&conn->flowcontrol); if (rc) debug(1, "Connection %d: error %d destroying flow control condition variable.", conn->connection_number, rc); @@ -2227,6 +2230,9 @@ static void *rtsp_conversation_thread_func(void *pconn) { if (rc) die("Connection %d: error %d initialising flow control condition variable.", conn->connection_number, rc); + rc = pthread_mutex_init(&conn->volume_control_mutex, NULL); + if (rc) + die("Connection %d: error %d initialising volume_control_mutex.", conn->connection_number, rc); // nothing before this is cancellable pthread_cleanup_push(rtsp_conversation_thread_cleanup_function, (void *)conn); From 46d117b69a94a4792cebdee61b38dfeda0a1d0cd Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 1 Feb 2019 21:07:35 +0000 Subject: [PATCH 410/569] add a volume control mutex to serialise multiple attempts to change the volume --- player.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player.h b/player.h index 00793a92..28b0341b 100644 --- a/player.h +++ b/player.h @@ -133,7 +133,7 @@ typedef struct { int32_t last_seqno_read; // mutexes and condition variables pthread_cond_t flowcontrol; - pthread_mutex_t ab_mutex, flush_mutex; + pthread_mutex_t ab_mutex, flush_mutex, volume_control_mutex; int fix_volume; uint32_t timestamp_epoch, last_timestamp, maximum_timestamp_interval; // timestamp_epoch of zero means not initialised, could start at 2 From 601ac4d760b2fefb2892f651b988aa59f3ad3322 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 1 Feb 2019 21:08:41 +0000 Subject: [PATCH 411/569] add a volume control mutex to serialise multiple attempts to change the volume, stop setting the fixed volume to max if using hybrid volume setup --- player.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/player.c b/player.c index fa2e2c3c..c0b4b54f 100644 --- a/player.c +++ b/player.c @@ -2542,6 +2542,7 @@ void *player_thread_func(void *arg) { } void player_volume_without_notification(double airplay_volume, rtsp_conn_info *conn) { + debug_mutex_lock(&conn->volume_control_mutex, 5000, 1); debug(2, "player_volume_without_notification %f", airplay_volume); // first, see if we are hw only, sw only, both with hw attenuation on the top or both with sw // attenuation on top @@ -2632,8 +2633,8 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c min_db = sw_min_db; break; case vol_both: - debug(1, "dB range passed is hw: %d, sw: %d, total: %d", hw_max_db - hw_min_db, - sw_max_db - sw_min_db, (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db)); + //debug(1, "dB range passed is hw: %d, sw: %d, total: %d", hw_max_db - hw_min_db, + // sw_max_db - sw_min_db, (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db)); max_db = (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db); // this should be the range requested min_db = 0; @@ -2671,7 +2672,8 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c // volume to its lowest // before using the hw attenuation. // one imagines that hw priority is likely to be much better - if (config.volume_range_hw_priority) { + // if (config.volume_range_hw_priority) { + if (1) { // hw priority if ((sw_max_db - sw_min_db) > scaled_attenuation) { software_attenuation = sw_min_db + scaled_attenuation; @@ -2700,7 +2702,8 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value // debug(1,"Hardware attenuation set to %f for airplay volume of // %f.",hardware_attenuation,airplay_volume); - conn->fix_volume = 0x10000; + if (volume_mode == vol_hw_only) + conn->fix_volume = 0x10000; } if ((volume_mode == vol_sw_only) || (volume_mode == vol_both)) { @@ -2709,7 +2712,6 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c // %f",software_attenuation,temp_fix_volume,airplay_volume); conn->fix_volume = temp_fix_volume; - memory_barrier(); // no cancellation points if (config.loudness) loudness_set_volume(software_attenuation / 100); @@ -2743,6 +2745,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c } } config.airplay_volume = airplay_volume; + debug_mutex_unlock(&conn->volume_control_mutex, 3); } void player_volume(double airplay_volume, rtsp_conn_info *conn) { From cb9a3076b76f9c7a9fa989e31b3db3bdc27bc3aa Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 1 Feb 2019 21:14:24 +0000 Subject: [PATCH 412/569] New volume code, new mute code, fix cancellation memnory leaks, defer alsa device initialisation until needed. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index af9268e8..0252f709 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.3d49], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d50], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From cf03983694e87c764a6346dc5119fe6e080e6996 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 2 Feb 2019 13:05:49 +0000 Subject: [PATCH 413/569] Fix warnings about deprecated mbedtls API calls --- metadata_hub.c | 6 +++--- player.c | 2 +- rtsp.c | 38 +++++++++++++++++++------------------- shairport.c | 6 +++--- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/metadata_hub.c b/metadata_hub.c index f6c92ae8..015327f0 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -228,9 +228,9 @@ char *metadata_write_image_file(const char *buf, int len) { #ifdef CONFIG_MBEDTLS mbedtls_md5_context tctx; - mbedtls_md5_starts(&tctx); - mbedtls_md5_update(&tctx, (const unsigned char *)buf, len); - mbedtls_md5_finish(&tctx, img_md5); + mbedtls_md5_starts_ret(&tctx); + mbedtls_md5_update_ret(&tctx, (const unsigned char *)buf, len); + mbedtls_md5_finish_ret(&tctx, img_md5); #endif #ifdef CONFIG_POLARSSL diff --git a/player.c b/player.c index c0b4b54f..e94e249b 100644 --- a/player.c +++ b/player.c @@ -2622,7 +2622,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c } } else { - int32_t max_db, min_db; + int32_t max_db =0, min_db = 0; switch (volume_mode) { case vol_hw_only: max_db = hw_max_db; diff --git a/rtsp.c b/rtsp.c index 67e2a88a..3143dba5 100644 --- a/rtsp.c +++ b/rtsp.c @@ -2039,18 +2039,18 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { #ifdef CONFIG_MBEDTLS mbedtls_md5_context tctx; - mbedtls_md5_starts(&tctx); - mbedtls_md5_update(&tctx, (const unsigned char *)username, strlen(username)); - mbedtls_md5_update(&tctx, (unsigned char *)":", 1); - mbedtls_md5_update(&tctx, (const unsigned char *)realm, strlen(realm)); - mbedtls_md5_update(&tctx, (unsigned char *)":", 1); - mbedtls_md5_update(&tctx, (const unsigned char *)config.password, strlen(config.password)); - mbedtls_md5_finish(&tctx, digest_urp); - mbedtls_md5_starts(&tctx); - mbedtls_md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method)); - mbedtls_md5_update(&tctx, (unsigned char *)":", 1); - mbedtls_md5_update(&tctx, (const unsigned char *)uri, strlen(uri)); - mbedtls_md5_finish(&tctx, digest_mu); + mbedtls_md5_starts_ret(&tctx); + mbedtls_md5_update_ret(&tctx, (const unsigned char *)username, strlen(username)); + mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update_ret(&tctx, (const unsigned char *)realm, strlen(realm)); + mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update_ret(&tctx, (const unsigned char *)config.password, strlen(config.password)); + mbedtls_md5_finish_ret(&tctx, digest_urp); + mbedtls_md5_starts_ret(&tctx); + mbedtls_md5_update_ret(&tctx, (const unsigned char *)req->method, strlen(req->method)); + mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update_ret(&tctx, (const unsigned char *)uri, strlen(uri)); + mbedtls_md5_finish_ret(&tctx, digest_mu); #endif #ifdef CONFIG_POLARSSL @@ -2089,15 +2089,15 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { #endif #ifdef CONFIG_MBEDTLS - mbedtls_md5_starts(&tctx); - mbedtls_md5_update(&tctx, buf, 32); - mbedtls_md5_update(&tctx, (unsigned char *)":", 1); - mbedtls_md5_update(&tctx, (const unsigned char *)*nonce, strlen(*nonce)); - mbedtls_md5_update(&tctx, (unsigned char *)":", 1); + mbedtls_md5_starts_ret(&tctx); + mbedtls_md5_update_ret(&tctx, buf, 32); + mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update_ret(&tctx, (const unsigned char *)*nonce, strlen(*nonce)); + mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); for (i = 0; i < 16; i++) snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]); - mbedtls_md5_update(&tctx, buf, 32); - mbedtls_md5_finish(&tctx, digest_total); + mbedtls_md5_update_ret(&tctx, buf, 32); + mbedtls_md5_finish_ret(&tctx, digest_total); #endif #ifdef CONFIG_POLARSSL diff --git a/shairport.c b/shairport.c index 15f8b9c8..9a26a368 100644 --- a/shairport.c +++ b/shairport.c @@ -1610,9 +1610,9 @@ int main(int argc, char **argv) { #ifdef CONFIG_MBEDTLS mbedtls_md5_context tctx; - mbedtls_md5_starts(&tctx); - mbedtls_md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); - mbedtls_md5_finish(&tctx, ap_md5); + mbedtls_md5_starts_ret(&tctx); + mbedtls_md5_update_ret(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); + mbedtls_md5_finish_ret(&tctx, ap_md5); #endif #ifdef CONFIG_POLARSSL From 4fb88a3aa56e02f8b0b3a98e83d3473788e4ff97 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 2 Feb 2019 15:03:27 +0000 Subject: [PATCH 414/569] allow for updated or old mbedtls library --- metadata_hub.c | 15 +++++++++++---- rtsp.c | 28 ++++++++++++++++++++++++++++ shairport.c | 15 +++++++++++---- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/metadata_hub.c b/metadata_hub.c index 015327f0..3ec7a414 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -227,10 +227,17 @@ char *metadata_write_image_file(const char *buf, int len) { #endif #ifdef CONFIG_MBEDTLS - mbedtls_md5_context tctx; - mbedtls_md5_starts_ret(&tctx); - mbedtls_md5_update_ret(&tctx, (const unsigned char *)buf, len); - mbedtls_md5_finish_ret(&tctx, img_md5); + #if MBEDTLS_VERSION_MINOR >= 7 + mbedtls_md5_context tctx; + mbedtls_md5_starts_ret(&tctx); + mbedtls_md5_update_ret(&tctx, (const unsigned char *)buf, len); + mbedtls_md5_finish_ret(&tctx, img_md5); + #else + mbedtls_md5_context tctx; + mbedtls_md5_starts(&tctx); + mbedtls_md5_update(&tctx, (const unsigned char *)buf, len); + mbedtls_md5_finish(&tctx, img_md5); + #endif #endif #ifdef CONFIG_POLARSSL diff --git a/rtsp.c b/rtsp.c index 3143dba5..fb66d4c3 100644 --- a/rtsp.c +++ b/rtsp.c @@ -2038,6 +2038,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { #endif #ifdef CONFIG_MBEDTLS + #if MBEDTLS_VERSION_MINOR >= 7 mbedtls_md5_context tctx; mbedtls_md5_starts_ret(&tctx); mbedtls_md5_update_ret(&tctx, (const unsigned char *)username, strlen(username)); @@ -2051,6 +2052,21 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); mbedtls_md5_update_ret(&tctx, (const unsigned char *)uri, strlen(uri)); mbedtls_md5_finish_ret(&tctx, digest_mu); + #else + mbedtls_md5_context tctx; + mbedtls_md5_starts(&tctx); + mbedtls_md5_update(&tctx, (const unsigned char *)username, strlen(username)); + mbedtls_md5_update(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update(&tctx, (const unsigned char *)realm, strlen(realm)); + mbedtls_md5_update(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update(&tctx, (const unsigned char *)config.password, strlen(config.password)); + mbedtls_md5_finish(&tctx, digest_urp); + mbedtls_md5_starts(&tctx); + mbedtls_md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method)); + mbedtls_md5_update(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update(&tctx, (const unsigned char *)uri, strlen(uri)); + mbedtls_md5_finish(&tctx, digest_mu); + #endif #endif #ifdef CONFIG_POLARSSL @@ -2089,6 +2105,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { #endif #ifdef CONFIG_MBEDTLS + #if MBEDTLS_VERSION_MINOR >= 7 mbedtls_md5_starts_ret(&tctx); mbedtls_md5_update_ret(&tctx, buf, 32); mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); @@ -2098,6 +2115,17 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]); mbedtls_md5_update_ret(&tctx, buf, 32); mbedtls_md5_finish_ret(&tctx, digest_total); + #else + mbedtls_md5_starts(&tctx); + mbedtls_md5_update(&tctx, buf, 32); + mbedtls_md5_update(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update(&tctx, (const unsigned char *)*nonce, strlen(*nonce)); + mbedtls_md5_update(&tctx, (unsigned char *)":", 1); + for (i = 0; i < 16; i++) + snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]); + mbedtls_md5_update(&tctx, buf, 32); + mbedtls_md5_finish(&tctx, digest_total); + #endif #endif #ifdef CONFIG_POLARSSL diff --git a/shairport.c b/shairport.c index 9a26a368..617e9009 100644 --- a/shairport.c +++ b/shairport.c @@ -1609,10 +1609,17 @@ int main(int argc, char **argv) { #endif #ifdef CONFIG_MBEDTLS - mbedtls_md5_context tctx; - mbedtls_md5_starts_ret(&tctx); - mbedtls_md5_update_ret(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); - mbedtls_md5_finish_ret(&tctx, ap_md5); + #if MBEDTLS_VERSION_MINOR >= 7 + mbedtls_md5_context tctx; + mbedtls_md5_starts_ret(&tctx); + mbedtls_md5_update_ret(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); + mbedtls_md5_finish_ret(&tctx, ap_md5); + #else + mbedtls_md5_context tctx; + mbedtls_md5_starts(&tctx); + mbedtls_md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); + mbedtls_md5_finish(&tctx, ap_md5); + #endif #endif #ifdef CONFIG_POLARSSL From 9239a7c56e43a2f4b20ed36dcfb1146cf8a237cf Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 2 Feb 2019 15:27:26 +0000 Subject: [PATCH 415/569] Include the mbedtls version.h file so that conditional compilation can be done right --- metadata_hub.c | 1 + rtsp.c | 1 + shairport.c | 1 + 3 files changed, 3 insertions(+) diff --git a/metadata_hub.c b/metadata_hub.c index 3ec7a414..56869647 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -46,6 +46,7 @@ #include "metadata_hub.h" #ifdef CONFIG_MBEDTLS +#include #include #endif diff --git a/rtsp.c b/rtsp.c index fb66d4c3..4b4b28e9 100644 --- a/rtsp.c +++ b/rtsp.c @@ -51,6 +51,7 @@ #endif #ifdef CONFIG_MBEDTLS +#include #include #endif diff --git a/shairport.c b/shairport.c index 617e9009..3c939c64 100644 --- a/shairport.c +++ b/shairport.c @@ -44,6 +44,7 @@ #include "config.h" #ifdef CONFIG_MBEDTLS +#include #include #endif From 7b8ce7974a714a92c53e1fc45411ed13d9491413 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 3 Feb 2019 16:52:36 +0000 Subject: [PATCH 416/569] allow configuration numbers to convert between floats and integers automatically. --- shairport.c | 1 + 1 file changed, 1 insertion(+) diff --git a/shairport.c b/shairport.c index 3c939c64..23409b76 100644 --- a/shairport.c +++ b/shairport.c @@ -366,6 +366,7 @@ int parse_options(int argc, char **argv) { /* Read the file. If there is an error, report it and exit. */ if (config_read_file(&config_file_stuff, config_file_real_path)) { free(config_file_real_path); + config_set_auto_convert (&config_file_stuff, 1); // allow autoconversion from int/float to int/float // make config.cfg point to it config.cfg = &config_file_stuff; /* Get the Service Name. */ From 550f22a344f88bcffd72a2648e8acfe6f90d2c52 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 3 Feb 2019 17:38:33 +0000 Subject: [PATCH 417/569] Include some more settings in the debug listing. --- shairport.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/shairport.c b/shairport.c index 23409b76..9ef40521 100644 --- a/shairport.c +++ b/shairport.c @@ -1535,10 +1535,14 @@ int main(int argc, char **argv) { debug(1, "udp port range is %d.", config.udp_port_range); debug(1, "player name is \"%s\".", config.service_name); debug(1, "backend is \"%s\".", config.output_name); - debug(1, "on-start action is \"%s\".", config.cmd_start); - debug(1, "on-stop action is \"%s\".", config.cmd_stop); + debug(1, "run_this_before_play_begins action is \"%s\".", config.cmd_start); + debug(1, "run_this_after_play_ends action is \"%s\".", config.cmd_stop); debug(1, "wait-cmd status is %d.", config.cmd_blocking); - debug(1, "on-start returns output is %d.", config.cmd_start_returns_output); + debug(1, "run_this_before_play_begins may return output is %d.", config.cmd_start_returns_output); + debug(1, "run_this_if_an_unfixable_error_is_detected action is \"%s\".", config.cmd_unfixable); + debug(1, "run_this_before_entering_active_mode action is \"%s\".", config.cmd_active_start); + debug(1, "run_this_after_exiting_active_mode action is \"%s\".", config.cmd_active_stop); + debug(1, "active_mode_timeout is %f seconds.", config.active_mode_timeout); debug(1, "mdns backend \"%s\".", config.mdns_name); debug(2, "userSuppliedLatency is %d.", config.userSuppliedLatency); debug(1, "stuffing option is \"%d\" (0-basic, 1-soxr).", config.packet_stuffing); @@ -1548,6 +1552,7 @@ int main(int argc, char **argv) { debug(1, "drift tolerance is %f seconds.", config.tolerance); debug(1, "password is \"%s\".", config.password); debug(1, "ignore_volume_control is %d.", config.ignore_volume_control); + debug(1, "combined attenuators (0 -- software is / 1 -- hardware is top of attenuation range) is %d.", config.volume_range_hw_priority); if (config.volume_max_db_set) debug(1, "volume_max_db is %d.", config.volume_max_db); else From d54025486a4a4e2c66079341d0fbf954c0a6bf4d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 4 Feb 2019 16:58:55 +0000 Subject: [PATCH 418/569] Include the mdns_external interface even if avahi is not included. --- mdns.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mdns.c b/mdns.c index de703975..56b93448 100644 --- a/mdns.c +++ b/mdns.c @@ -34,7 +34,6 @@ #ifdef CONFIG_AVAHI extern mdns_backend mdns_avahi; -extern mdns_backend mdns_external_avahi; #endif #ifdef CONFIG_DNS_SD extern mdns_backend mdns_dns_sd; @@ -43,11 +42,11 @@ extern mdns_backend mdns_external_dns_sd; #ifdef CONFIG_TINYSVCMDNS extern mdns_backend mdns_tinysvcmdns; #endif +extern mdns_backend mdns_external_avahi; static mdns_backend *mdns_backends[] = { #ifdef CONFIG_AVAHI &mdns_avahi, - &mdns_external_avahi, #endif #ifdef CONFIG_DNS_SD &mdns_dns_sd, @@ -56,6 +55,7 @@ static mdns_backend *mdns_backends[] = { #ifdef CONFIG_TINYSVCMDNS &mdns_tinysvcmdns, #endif + &mdns_external_avahi, NULL}; void mdns_register(void) { From 3162977c712496adcce6a19f9aa40c20a32dce42 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 5 Feb 2019 09:45:16 +0000 Subject: [PATCH 419/569] make mdns-external-avahi a conditional compilation --- Makefile.am | 6 +++++- configure.ac | 4 +++- mdns.c | 5 +++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index c384807c..f6d2fdf7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -8,7 +8,7 @@ bin_PROGRAMS = shairport-sync # See below for the flags for the test client program -shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c loudness.c activity_monitor.c +shairport_sync_SOURCES = shairport.c rtsp.c mdns.c common.c rtp.c player.c alac.c audio.c loudness.c activity_monitor.c if BUILD_FOR_FREEBSD AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\" @@ -40,6 +40,10 @@ if USE_TINYSVCMDNS shairport_sync_SOURCES += mdns_tinysvcmdns.c tinysvcmdns.c endif +if USE_EXTERNAL_MDNS +shairport_sync_SOURCES += mdns_external.c +endif + if USE_ALSA shairport_sync_SOURCES += audio_alsa.c endif diff --git a/configure.ac b/configure.ac index 0252f709..02d49f0c 100644 --- a/configure.ac +++ b/configure.ac @@ -73,7 +73,6 @@ AC_ARG_WITH([dummy],[ --with-dummy = include the dummy audio back end ],[AC_MSG AM_CONDITIONAL([USE_DUMMY], [test "x$with_dummy" = "xyes" ]) AC_ARG_WITH([stdout],[ --with-stdout = include the stdout audio back end ],[ AC_MSG_RESULT(>>Including the stdout audio back end) AC_DEFINE([CONFIG_STDOUT], 1, [Needed by the compiler.]) ], ) - AM_CONDITIONAL([USE_STDOUT], [test "x$with_stdout" = "xyes" ]) AC_ARG_WITH([pipe],[ --with-pipe = include the pipe audio back end ],[ AC_MSG_RESULT(>>Including the pipe audio back end) AC_DEFINE([CONFIG_PIPE], 1, [Needed by the compiler.]) ], ) @@ -95,6 +94,9 @@ AC_ARG_WITH([freebsd-service], [ --with-freebsd-service = install a FreeBSD startup script during a make install], , ) AM_CONDITIONAL([INSTALL_FREEBSD_SERVICE], [test "x$with_freebsd_service" = "xyes"]) +AC_ARG_WITH([external-mdns],[ --with-external-mdns = support the use of 'avahi-publish-service' or 'mDNSPublish' to advertise the service on Bonjour/ZeroConf ],[ AC_MSG_RESULT(>>Including external mdns support) AC_DEFINE([CONFIG_EXTERNAL_MDNS], 1, [Needed by the compiler.]) ], ) +AM_CONDITIONAL([USE_EXTERNAL_MDNS], [test "x$with_external_mdns" = "xyes" ]) + # Add the libconfig package if test "x${with_pkg_config}" = xyes ; then PKG_CHECK_MODULES( diff --git a/mdns.c b/mdns.c index 56b93448..8550f520 100644 --- a/mdns.c +++ b/mdns.c @@ -42,7 +42,10 @@ extern mdns_backend mdns_external_dns_sd; #ifdef CONFIG_TINYSVCMDNS extern mdns_backend mdns_tinysvcmdns; #endif + +#ifdef CONFIG_EXTERNAL_MDNS extern mdns_backend mdns_external_avahi; +#endif static mdns_backend *mdns_backends[] = { #ifdef CONFIG_AVAHI @@ -55,7 +58,9 @@ static mdns_backend *mdns_backends[] = { #ifdef CONFIG_TINYSVCMDNS &mdns_tinysvcmdns, #endif +#ifdef CONFIG_EXTERNAL_MDNS &mdns_external_avahi, +#endif NULL}; void mdns_register(void) { From 1ae7856dd27d3cab5aa6942e253e96f27a0b3de1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 5 Feb 2019 10:23:24 +0000 Subject: [PATCH 420/569] Stop a segmentation error if a bsackend has no help routine --- audio.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/audio.c b/audio.c index 7d0f489f..a2c21287 100644 --- a/audio.c +++ b/audio.c @@ -112,8 +112,12 @@ void audio_ls_outputs(void) { for (out = outputs; *out; out++) { printf("\n"); - printf("Options for output %s:\n", (*out)->name); - (*out)->help(); + if ((*out)->help) { + printf("Options for output %s:\n", (*out)->name); + (*out)->help(); + } else { + printf("No options for output %s:\n", (*out)->name); + } } } From 552218ab6ea149bcd2d26a3bf9e393094708481a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 5 Feb 2019 11:26:54 +0000 Subject: [PATCH 421/569] Clean up audio help texts, add in the volume_range_hw_priority setting. --- audio.c | 6 +++--- audio_alsa.c | 11 +++++------ audio_dummy.c | 3 +-- audio_pa.c | 4 +--- audio_pipe.c | 2 +- audio_stdout.c | 4 +--- common.h | 2 +- player.c | 2 +- scripts/shairport-sync.conf | 1 + shairport.c | 12 +++++++----- 10 files changed, 22 insertions(+), 25 deletions(-) diff --git a/audio.c b/audio.c index a2c21287..78686376 100644 --- a/audio.c +++ b/audio.c @@ -106,17 +106,17 @@ audio_output *audio_get_output(char *name) { void audio_ls_outputs(void) { audio_output **out; - printf("Available audio outputs:\n"); + printf("Available audio backends:\n"); for (out = outputs; *out; out++) printf(" %s%s\n", (*out)->name, out == outputs ? " (default)" : ""); for (out = outputs; *out; out++) { printf("\n"); if ((*out)->help) { - printf("Options for output %s:\n", (*out)->name); + printf("Settings and options for the audio backend \"%s\":\n", (*out)->name); (*out)->help(); } else { - printf("No options for output %s:\n", (*out)->name); + printf("There are no settings or options for the audio backend \"%s\".\n", (*out)->name); } } } diff --git a/audio_alsa.c b/audio_alsa.c index e54230da..2fd4497d 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -162,11 +162,10 @@ static uint64_t frame_index; static int measurement_data_is_valid; static void help(void) { - printf(" -d output-device set the output device [default*|...]\n" - " -m mixer-device set the mixer device ['output-device'*|...]\n" - " -c mixer-control set the mixer control [Master*|...]\n" - " -i mixer-index set the mixer index [0*|...]\n" - " *) default option\n"); + printf(" -d output-device set the output device, default is \"default\".\n" + " -c mixer-control set the mixer control name, default is to use no mixer.\n" + " -m mixer-device set the mixer device, default is the output device.\n" + " -i mixer-index set the mixer index, default is 0.\n"); } void set_alsa_out_dev(char *dev) { alsa_out_dev = dev; } @@ -202,7 +201,7 @@ int open_mixer() { debug(3, "Mixer Control name is \"%s\".", alsa_mix_ctrl); alsa_mix_elem = snd_mixer_find_selem(alsa_mix_handle, alsa_mix_sid); if (!alsa_mix_elem) { - debug(1, "Failed to find mixer element"); + warn("failed to find mixer control \"%s\".", alsa_mix_ctrl); response = -5; } else { response = 1; // we found a hardware mixer and successfully opened it diff --git a/audio_dummy.c b/audio_dummy.c index d8dea87f..88c63c94 100644 --- a/audio_dummy.c +++ b/audio_dummy.c @@ -54,10 +54,9 @@ static int play(__attribute__((unused)) void *buf, __attribute__((unused)) int s static void stop(void) { debug(1, "dummy audio stopped\n"); } -static void help(void) { printf(" There are no options for dummy audio.\n"); } audio_output audio_dummy = {.name = "dummy", - .help = &help, + .help = NULL, .init = &init, .deinit = &deinit, .start = &start, diff --git a/audio_pa.c b/audio_pa.c index cd321bd9..8dd38b8b 100644 --- a/audio_pa.c +++ b/audio_pa.c @@ -300,10 +300,8 @@ static void stop(void) { pa_stream_disconnect(stream); } -static void help(void) { printf(" no settings.\n"); } - audio_output audio_pa = {.name = "pa", - .help = &help, + .help = NULL, .init = &init, .deinit = &deinit, .start = &start, diff --git a/audio_pipe.c b/audio_pipe.c index 89a09a0c..e97f6034 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -127,7 +127,7 @@ static void deinit(void) { close(fd); } -static void help(void) { printf(" pipe takes 1 argument: the name of the FIFO to write to.\n"); } +static void help(void) { printf(" specify the pathname of the pipe to write to.\n"); } audio_output audio_pipe = {.name = "pipe", .help = &help, diff --git a/audio_stdout.c b/audio_stdout.c index 0438b1af..ea48e73d 100644 --- a/audio_stdout.c +++ b/audio_stdout.c @@ -74,10 +74,8 @@ static void deinit(void) { // don't close stdout } -static void help(void) { printf(" stdout takes no arguments\n"); } - audio_output audio_stdout = {.name = "stdout", - .help = &help, + .help = NULL, .init = &init, .deinit = &deinit, .start = &start, diff --git a/common.h b/common.h index 463e49b4..59fa72ce 100644 --- a/common.h +++ b/common.h @@ -182,7 +182,7 @@ typedef struct { // into the "active" mode. uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's // native range. - int volume_range_hw_priority; // when lowering the volume, use all the hw attenuation before using + int volume_range_hw_priority; // when extending the volume range by combining sw and hw attenuators, lowering the volume, use all the hw attenuation before using // sw attenuation enum sps_format_t output_format; enum volume_control_profile_type volume_control_profile; diff --git a/player.c b/player.c index e94e249b..8cf3df90 100644 --- a/player.c +++ b/player.c @@ -2673,7 +2673,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c // before using the hw attenuation. // one imagines that hw priority is likely to be much better // if (config.volume_range_hw_priority) { - if (1) { + if (config.volume_range_hw_priority != 0) { // hw priority if ((sw_max_db - sw_min_db) > scaled_attenuation) { software_attenuation = sw_min_db + scaled_attenuation; diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 031ce572..4a6ebe29 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -29,6 +29,7 @@ general = // volume_control_profile = "standard" ; // use this advanced setting to specify how the airplay volume is transferred to the mixer volume. // "standard" makes the volume change more quickly at lower volumes and slower at higher volumes. // "flat" makes the volume change at the same rate at all volumes. +// volume_range_hw_priority = "yes"; // when extending the volume range by combining the built-in software attenuator with the hardware mixer attenuator, set this to "yes" to reduce volume by using the hardware attenuator first, then the software attenuator. // run_this_when_volume_is_set = "/full/path/to/application/and/args"; // Run the specified application whenever the volume control is set or changed. // The desired AirPlay volume is appended to the end of the command line – leave a space if you want it treated as an extra argument. // AirPlay volume goes from 0 to -30 and -144 means "mute". diff --git a/shairport.c b/shairport.c index 9ef40521..e2e7b3c4 100644 --- a/shairport.c +++ b/shairport.c @@ -62,7 +62,6 @@ #include "activity_monitor.h" #include "common.h" -#include "mdns.h" #include "rtp.h" #include "rtsp.h" @@ -96,7 +95,7 @@ #include #endif -static int shutting_down = 0; +// static int shutting_down = 0; char configuration_file_path[4096 + 1]; char actual_configuration_file_path[4096 + 1]; @@ -113,11 +112,9 @@ static void sig_shutdown(__attribute__((unused)) int foo, __attribute__((unused) static void sig_child(__attribute__((unused)) int foo, __attribute__((unused)) siginfo_t *bar, __attribute__((unused)) void *baz) { + // wait for child processes to exit pid_t pid; while ((pid = waitpid((pid_t)-1, 0, WNOHANG)) > 0) { - if (pid == mdns_pid && !shutting_down) { - die("MDNS child process died unexpectedly!"); - } } } @@ -333,6 +330,8 @@ int parse_options(int argc, char **argv) { config.fixedLatencyOffset = 11025; // this sounds like it works properly. config.diagnostic_drop_packet_fraction = 0.0; config.active_mode_timeout = 10.0; + config.volume_range_hw_priority = 1; // if combining software and hardware volume control, give the hardware priority + // i.e. when reducing volume, reduce the hw first before reducing the software. #ifdef CONFIG_METADATA_HUB config.cover_art_cache_dir = "/tmp/shairport-sync/.cache/coverart"; @@ -608,6 +607,9 @@ int parse_options(int argc, char **argv) { die("Invalid volume_control_profile choice \"%s\". It should be \"standard\" (default) " "or \"flat\""); } + + config_set_lookup_bool(config.cfg, "general.volume_control_combined_hardware_priority", &config.volume_range_hw_priority); + /* Get the interface to listen on, if specified Default is all interfaces */ /* we keep the interface name and the index */ From 3d1ee2e03fd4a22b842b60ba8b383c03932890de Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 5 Feb 2019 11:38:11 +0000 Subject: [PATCH 422/569] Tidy code and slightly reorder settings listing. --- player.c | 15 ++++++++++----- shairport.c | 50 +++++++++++++++++++++++++++----------------------- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/player.c b/player.c index 8cf3df90..ccae0795 100644 --- a/player.c +++ b/player.c @@ -2615,14 +2615,18 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c if (airplay_volume == -144.0) { if ((config.output->mute) && (config.output->mute(1) == 0)) - debug(2, "player_volume_without_notification: volume mode is %d, airplay_volume is %f, hardware mute is enabled.", volume_mode, airplay_volume); + debug(2, "player_volume_without_notification: volume mode is %d, airplay_volume is %f, " + "hardware mute is enabled.", + volume_mode, airplay_volume); else { conn->software_mute_enabled = 1; - debug(2, "player_volume_without_notification: volume mode is %d, airplay_volume is %f, software mute is enabled.", volume_mode, airplay_volume); + debug(2, "player_volume_without_notification: volume mode is %d, airplay_volume is %f, " + "software mute is enabled.", + volume_mode, airplay_volume); } } else { - int32_t max_db =0, min_db = 0; + int32_t max_db = 0, min_db = 0; switch (volume_mode) { case vol_hw_only: max_db = hw_max_db; @@ -2633,7 +2637,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c min_db = sw_min_db; break; case vol_both: - //debug(1, "dB range passed is hw: %d, sw: %d, total: %d", hw_max_db - hw_min_db, + // debug(1, "dB range passed is hw: %d, sw: %d, total: %d", hw_max_db - hw_min_db, // sw_max_db - sw_min_db, (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db)); max_db = (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db); // this should be the range requested @@ -2739,7 +2743,8 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c config.output->mute(0); conn->software_mute_enabled = 0; - debug(2, "player_volume_without_notification: volume mode is %d, airplay volume is %f, software_attenuation: %f, hardware_attenuation: %f, muting " + debug(2, "player_volume_without_notification: volume mode is %d, airplay volume is %f, " + "software_attenuation: %f, hardware_attenuation: %f, muting " "is disabled.", volume_mode, airplay_volume, software_attenuation, hardware_attenuation); } diff --git a/shairport.c b/shairport.c index e2e7b3c4..fd95ad9c 100644 --- a/shairport.c +++ b/shairport.c @@ -44,8 +44,8 @@ #include "config.h" #ifdef CONFIG_MBEDTLS -#include #include +#include #endif #ifdef CONFIG_POLARSSL @@ -330,8 +330,9 @@ int parse_options(int argc, char **argv) { config.fixedLatencyOffset = 11025; // this sounds like it works properly. config.diagnostic_drop_packet_fraction = 0.0; config.active_mode_timeout = 10.0; - config.volume_range_hw_priority = 1; // if combining software and hardware volume control, give the hardware priority - // i.e. when reducing volume, reduce the hw first before reducing the software. + config.volume_range_hw_priority = + 1; // if combining software and hardware volume control, give the hardware priority +// i.e. when reducing volume, reduce the hw first before reducing the software. #ifdef CONFIG_METADATA_HUB config.cover_art_cache_dir = "/tmp/shairport-sync/.cache/coverart"; @@ -365,7 +366,8 @@ int parse_options(int argc, char **argv) { /* Read the file. If there is an error, report it and exit. */ if (config_read_file(&config_file_stuff, config_file_real_path)) { free(config_file_real_path); - config_set_auto_convert (&config_file_stuff, 1); // allow autoconversion from int/float to int/float + config_set_auto_convert(&config_file_stuff, + 1); // allow autoconversion from int/float to int/float // make config.cfg point to it config.cfg = &config_file_stuff; /* Get the Service Name. */ @@ -607,9 +609,9 @@ int parse_options(int argc, char **argv) { die("Invalid volume_control_profile choice \"%s\". It should be \"standard\" (default) " "or \"flat\""); } - - config_set_lookup_bool(config.cfg, "general.volume_control_combined_hardware_priority", &config.volume_range_hw_priority); + config_set_lookup_bool(config.cfg, "general.volume_control_combined_hardware_priority", + &config.volume_range_hw_priority); /* Get the interface to listen on, if specified Default is all interfaces */ /* we keep the interface name and the index */ @@ -1525,9 +1527,8 @@ int main(int argc, char **argv) { /* Print out options */ debug(1, "log verbosity is %d.", debuglev); debug(1, "disable resend requests is %s.", config.disable_resend_requests ? "on" : "off"); - debug(1, - "diagnostic_drop_packet_fraction is %f. A value of 0.0 means no packets will be dropped " - "deliberately.", + debug(1, "diagnostic_drop_packet_fraction is %f. A value of 0.0 means no packets will be dropped " + "deliberately.", config.diagnostic_drop_packet_fraction); debug(1, "statistics_requester status is %d.", config.statistics_requested); debug(1, "daemon status is %d.", config.daemonise); @@ -1554,11 +1555,16 @@ int main(int argc, char **argv) { debug(1, "drift tolerance is %f seconds.", config.tolerance); debug(1, "password is \"%s\".", config.password); debug(1, "ignore_volume_control is %d.", config.ignore_volume_control); - debug(1, "combined attenuators (0 -- software is / 1 -- hardware is top of attenuation range) is %d.", config.volume_range_hw_priority); if (config.volume_max_db_set) debug(1, "volume_max_db is %d.", config.volume_max_db); else debug(1, "volume_max_db is not set"); + debug(1, "volume range in dB (zero means use the range specified by the mixer): %u.", + config.volume_range_db); + debug( + 1, + "combined attenuators (0 -- software is / 1 -- hardware is top of attenuation range) is %d.", + config.volume_range_hw_priority); debug(1, "playback_mode is %d (0-stereo, 1-mono, 1-reverse_stereo, 2-both_left, 3-both_right).", config.playback_mode); debug(1, "disable_synchronization is %d.", config.no_sync); @@ -1572,8 +1578,6 @@ int main(int argc, char **argv) { debug(1, "audio backend latency offset is %f seconds.", config.audio_backend_latency_offset); debug(1, "audio backend silence lead-in time is %f seconds. A value -1.0 means use the default.", config.audio_backend_silent_lead_in_time); - debug(1, "volume range in dB (zero means use the range specified by the mixer): %u.", - config.volume_range_db); debug(1, "zeroconf regtype is \"%s\".", config.regtype); debug(1, "decoders_supported field is %d.", config.decoders_supported); debug(1, "use_apple_decoder is %d.", config.use_apple_decoder); @@ -1618,17 +1622,17 @@ int main(int argc, char **argv) { #endif #ifdef CONFIG_MBEDTLS - #if MBEDTLS_VERSION_MINOR >= 7 - mbedtls_md5_context tctx; - mbedtls_md5_starts_ret(&tctx); - mbedtls_md5_update_ret(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); - mbedtls_md5_finish_ret(&tctx, ap_md5); - #else - mbedtls_md5_context tctx; - mbedtls_md5_starts(&tctx); - mbedtls_md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); - mbedtls_md5_finish(&tctx, ap_md5); - #endif +#if MBEDTLS_VERSION_MINOR >= 7 + mbedtls_md5_context tctx; + mbedtls_md5_starts_ret(&tctx); + mbedtls_md5_update_ret(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); + mbedtls_md5_finish_ret(&tctx, ap_md5); +#else + mbedtls_md5_context tctx; + mbedtls_md5_starts(&tctx); + mbedtls_md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); + mbedtls_md5_finish(&tctx, ap_md5); +#endif #endif #ifdef CONFIG_POLARSSL From 1fc1247bb0eaaad7e43e5228a33b22e371c6c090 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 5 Feb 2019 12:18:16 +0000 Subject: [PATCH 423/569] Quieten an incorrect compiler warning --- player.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player.c b/player.c index ccae0795..72a79ef8 100644 --- a/player.c +++ b/player.c @@ -2553,7 +2553,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c // range has been specified // the range might imply that both hw and software mixers are needed, so calculate this - int32_t hw_max_db, hw_min_db; + int32_t hw_max_db = 0, hw_min_db = 0; // zeroed to quieten an incorrect uninitialised warning int32_t sw_max_db = 0, sw_min_db = -9630; if (config.output->parameters) { volume_mode = vol_hw_only; From e76cfa69fec6d17c06639f358fc76e323abb2c20 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 5 Feb 2019 15:12:51 +0000 Subject: [PATCH 424/569] remove dependency on libdaemon -- leave it in by default, but allow the new configuration option --with-libdaemon. --- common.c | 60 +++++++++++++++++--- common.h | 5 +- configure.ac | 37 +++++++++--- shairport.c | 155 +++++++++++++++++++++++++-------------------------- 4 files changed, 161 insertions(+), 96 deletions(-) diff --git a/common.c b/common.c index 87526f46..d8be92a4 100644 --- a/common.c +++ b/common.c @@ -75,10 +75,13 @@ #include #include #include - #endif +#ifdef CONFIG_LIBDAEMON #include +#else +#include +#endif #ifdef CONFIG_ALSA void set_alsa_out_dev(char *); @@ -143,13 +146,24 @@ void die(const char *format, ...) { va_end(args); if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) - daemon_log(LOG_EMERG, "|% 20.9f|% 20.9f|*fatal error: %s", tss, tsl, s); + + #ifdef CONFIG_LIBDAEMON + daemon_log(LOG_ERR, "|% 20.9f|% 20.9f|*fatal error: %s", tss, tsl, s); else if ((debuglev) && (config.debugger_show_relative_time)) - daemon_log(LOG_EMERG, "% 20.9f|*fatal error: %s", tsl, s); + daemon_log(LOG_ERR, "% 20.9f|*fatal error: %s", tsl, s); else if ((debuglev) && (config.debugger_show_elapsed_time)) - daemon_log(LOG_EMERG, "% 20.9f|*fatal error: %s", tss, s); + daemon_log(LOG_ERR, "% 20.9f|*fatal error: %s", tss, s); else - daemon_log(LOG_EMERG, "fatal error: %s", s); + daemon_log(LOG_ERR, "fatal error: %s", s); + #else + syslog(LOG_ERR, "|% 20.9f|% 20.9f|*fatal error: %s", tss, tsl, s); + else if ((debuglev) && (config.debugger_show_relative_time)) + syslog(LOG_ERR, "% 20.9f|*fatal error: %s", tsl, s); + else if ((debuglev) && (config.debugger_show_elapsed_time)) + syslog(LOG_ERR, "% 20.9f|*fatal error: %s", tss, s); + else + syslog(LOG_ERR, "fatal error: %s", s); + #endif pthread_setcancelstate(oldState, NULL); exit(1); } @@ -170,7 +184,7 @@ void warn(const char *format, ...) { va_start(args, format); vsnprintf(s, sizeof(s), format, args); va_end(args); - +#ifdef CONFIG_LIBDAEMON if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) daemon_log(LOG_WARNING, "|% 20.9f|% 20.9f|*warning: %s", tss, tsl, s); else if ((debuglev) && (config.debugger_show_relative_time)) @@ -179,6 +193,16 @@ void warn(const char *format, ...) { daemon_log(LOG_WARNING, "% 20.9f|*warning: %s", tss, s); else daemon_log(LOG_WARNING, "%s", s); +#else + if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) + syslog(LOG_WARNING, "|% 20.9f|% 20.9f|*warning: %s", tss, tsl, s); + else if ((debuglev) && (config.debugger_show_relative_time)) + syslog(LOG_WARNING, "% 20.9f|*warning: %s", tsl, s); + else if ((debuglev) && (config.debugger_show_elapsed_time)) + syslog(LOG_WARNING, "% 20.9f|*warning: %s", tss, s); + else + syslog(LOG_WARNING, "%s", s); +#endif pthread_setcancelstate(oldState, NULL); } @@ -200,6 +224,7 @@ void debug(int level, const char *format, ...) { va_start(args, format); vsnprintf(s, sizeof(s), format, args); va_end(args); +#ifdef CONFIG_LIBDAEMON if ((config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) daemon_log(LOG_DEBUG, "|% 20.9f|% 20.9f|%s", tss, tsl, s); else if (config.debugger_show_relative_time) @@ -208,6 +233,16 @@ void debug(int level, const char *format, ...) { daemon_log(LOG_DEBUG, "% 20.9f|%s", tss, s); else daemon_log(LOG_DEBUG, "%s", s); +#else + if ((config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) + syslog(LOG_DEBUG, "|% 20.9f|% 20.9f|%s", tss, tsl, s); + else if (config.debugger_show_relative_time) + syslog(LOG_DEBUG, "% 20.9f|%s", tsl, s); + else if (config.debugger_show_elapsed_time) + syslog(LOG_DEBUG, "% 20.9f|%s", tss, s); + else + syslog(LOG_DEBUG, "%s", s); +#endif pthread_setcancelstate(oldState, NULL); } @@ -227,7 +262,7 @@ void inform(const char *format, ...) { va_start(args, format); vsnprintf(s, sizeof(s), format, args); va_end(args); - +#ifdef CONFIG_LIBDAEMON if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) daemon_log(LOG_INFO, "|% 20.9f|% 20.9f|%s", tss, tsl, s); else if ((debuglev) && (config.debugger_show_relative_time)) @@ -236,6 +271,17 @@ void inform(const char *format, ...) { daemon_log(LOG_INFO, "% 20.9f|%s", tss, s); else daemon_log(LOG_INFO, "%s", s); +#else + if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) + syslog(LOG_INFO, "|% 20.9f|% 20.9f|%s", tss, tsl, s); + else if ((debuglev) && (config.debugger_show_relative_time)) + syslog(LOG_INFO, "% 20.9f|%s", tsl, s); + else if ((debuglev) && (config.debugger_show_elapsed_time)) + syslog(LOG_INFO, "% 20.9f|%s", tss, s); + else + syslog(LOG_INFO, "%s", s); + +#endif pthread_setcancelstate(oldState, NULL); } diff --git a/common.h b/common.h index 59fa72ce..870cb1a6 100644 --- a/common.h +++ b/common.h @@ -141,10 +141,13 @@ typedef struct { uint32_t fixedLatencyOffset; // add this to all automatic latencies supplied to get the actual // total latency // the total latency will be limited to the min and max-latency values, if supplied +#ifdef CONFIG_LIBDAEMON int daemonise; int daemonise_store_pid; // don't try to save a PID file char *piddir; char *computed_piddir; // the actual pid directory to create, if any + char *pidfile; +#endif int logOutputLevel; // log output level int debugger_show_elapsed_time; // in the debug message, display the time since startup @@ -159,7 +162,6 @@ typedef struct { int decoders_supported; int use_apple_decoder; // set to 1 if you want to use the apple decoder instead of the original by // David Hammerton - char *pidfile; // char *logfile; // char *errfile; char *configfile; @@ -320,7 +322,6 @@ void command_set_volume(double volume); int mkpath(const char *path, mode_t mode); void shairport_shutdown(); -// void shairport_startup_complete(void); extern sigset_t pselect_sigset; diff --git a/configure.ac b/configure.ac index 02d49f0c..fd934930 100644 --- a/configure.ac +++ b/configure.ac @@ -44,14 +44,35 @@ AM_CONDITIONAL([BUILD_FOR_OPENBSD], [test "x${with_os}" = xopenbsd ]) AC_ARG_WITH([pkg_config], [ --with-pkg-config = use pkg-config to find libraries], ,[with_pkg_config=yes]) -AC_MSG_RESULT(>>Including libdaemon) -if test "x${with_pkg_config}" = xyes ; then - PKG_CHECK_MODULES( - [DAEMON], [libdaemon], - [LIBS="${DAEMON_LIBS} ${LIBS}"]) -else - AC_CHECK_LIB([daemon],[daemon_fork], , AC_MSG_ERROR(libdaemon needed)) -fi +# Look for libdaemon +AC_ARG_WITH(libdaemon, [ --with-libdaemon = include support for daemonising in older non-systemd systems], + if test "x${with_libdaemon}" = xyes ; then + [AC_MSG_RESULT(>>Including libdaemon support for older systems) + AC_DEFINE([CONFIG_LIBDAEMON], 1, [Include libdaemon]) + REQUESTED_LIBDAEMON=1 + if test "x${with_pkg_config}" = xyes ; then + PKG_CHECK_MODULES( + [DAEMON], [libdaemon], + [LIBS="${DAEMON_LIBS} ${LIBS}"]) + else + AC_CHECK_LIB([daemon],[daemon_fork], , AC_MSG_ERROR(libdaemon needed)) + fi + ] + fi, + #default is to include libdaemon + [AC_MSG_RESULT(>>Including libdaemon support for older systems) + AC_DEFINE([CONFIG_LIBDAEMON], 1, [Include libdaemon]) + REQUESTED_LIBDAEMON=1 + if test "x${with_pkg_config}" = xyes ; then + PKG_CHECK_MODULES( + [DAEMON], [libdaemon], + [LIBS="${DAEMON_LIBS} ${LIBS}"]) + else + AC_CHECK_LIB([daemon],[daemon_fork], , AC_MSG_ERROR(libdaemon needed)) + fi + ] + ) +AM_CONDITIONAL([USE_LIBDAEMON], [test "x$REQUESTED_LIBDAEMON" = "x1"]) ##### The following check for the pthreads library doesn't put the compiler into the correct pthread mode ##### so we add the -pthread compilation flag in AMFLAGS in the Makefile.am as well. diff --git a/shairport.c b/shairport.c index fd95ad9c..e1689d3e 100644 --- a/shairport.c +++ b/shairport.c @@ -85,11 +85,15 @@ #include "mpris-service.h" #endif +#ifdef CONFIG_LIBDAEMON #include #include #include #include #include +#else +#include +#endif #ifdef CONFIG_CONVOLUTION #include @@ -104,9 +108,10 @@ static void sig_ignore(__attribute__((unused)) int foo, __attribute__((unused)) static void sig_shutdown(__attribute__((unused)) int foo, __attribute__((unused)) siginfo_t *bar, __attribute__((unused)) void *baz) { debug(1, "shutdown requested..."); - // daemon_log(LOG_NOTICE, "exit..."); +#ifdef CONFIG_LIBDAEMON daemon_retval_send(255); daemon_pid_file_remove(); +#endif exit(0); } @@ -148,12 +153,12 @@ void usage(char *progname) { printf("\n"); printf("Options:\n"); printf(" -h, --help show this help.\n"); +#ifdef CONFIG_LIBDAEMON printf(" -d, --daemon daemonise.\n"); printf(" -j, --justDaemoniseNoPIDFile daemonise without a PID file.\n"); +#endif printf(" -V, --version show version information.\n"); printf(" -k, --kill kill the existing shairport daemon.\n"); - printf(" -D, --disconnectFromOutput disconnect immediately from the output device.\n"); - printf(" -R, --reconnectToOutput reconnect to the output device.\n"); printf(" -c, --configfile=FILE read configuration settings from FILE. Default is " "/etc/shairport-sync.conf.\n"); @@ -313,6 +318,7 @@ int parse_options(int argc, char **argv) { poptFreeContext(optCon); +#ifdef CONFIG_LIBDAEMON if ((daemonisewith) && (daemonisewithout)) die("Select either daemonize_with_pid_file or daemonize_without_pid_file -- you have selected " "both!"); @@ -321,6 +327,7 @@ int parse_options(int argc, char **argv) { if (daemonisewith) config.daemonise_store_pid = 1; }; +#endif config.resyncthreshold = 1.0 * fResyncthreshold / 44100; config.tolerance = 1.0 * fTolerance / 44100; @@ -374,6 +381,7 @@ int parse_options(int argc, char **argv) { if (config_lookup_string(config.cfg, "general.name", &str)) { raw_service_name = (char *)str; } +#ifdef CONFIG_LIBDAEMON int daemonisewithout = 0; int daemonisewith = 0; /* Get the Daemonize setting. */ @@ -394,7 +402,7 @@ int parse_options(int argc, char **argv) { /* Get the directory path for the pid file created when the program is daemonised. */ if (config_lookup_string(config.cfg, "sessioncontrol.daemon_pid_dir", &str)) config.piddir = (char *)str; - +#endif /* Get the mdns_backend setting. */ if (config_lookup_string(config.cfg, "general.mdns_backend", &str)) config.mdns_name = (char *)str; @@ -1049,6 +1057,8 @@ int parse_options(int argc, char **argv) { } #endif +#ifdef CONFIG_LIBDAEMON + // now, check and calculate the pid directory #ifdef DEFINED_CUSTOM_PID_DIR char *use_this_pid_dir = PIDDIR; @@ -1060,7 +1070,7 @@ int parse_options(int argc, char **argv) { use_this_pid_dir = config.piddir; if (use_this_pid_dir) config.computed_piddir = strdup(use_this_pid_dir); - +#endif return optind + 1; } @@ -1116,14 +1126,7 @@ void signal_setup(void) { sigaction(SIGCHLD, &sa, NULL); } -// forked daemon lets the spawner know it's up and running OK -// should be called only once! -void shairport_startup_complete(void) { - if (config.daemonise) { - // daemon_ready(); - } -} - +#ifdef CONFIG_LIBDAEMON char pid_file_path_string[4096] = "\0"; const char *pid_file_proc(void) { @@ -1132,29 +1135,10 @@ const char *pid_file_proc(void) { // debug(1,"pid_file_path_string \"%s\".",pid_file_path_string); return pid_file_path_string; } - -void exit_function() { - debug(1, "exit function called..."); - // cancel_all_RTSP_threads(); - if (conns) - free(conns); // make sure the connections have been deleted first - if (config.service_name) - free(config.service_name); - if (config.regtype) - free(config.regtype); - if (config.computed_piddir) - free(config.computed_piddir); - if (ranarray) - free((void *)ranarray); - 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... -} +#endif void main_cleanup_handler(__attribute__((unused)) void *arg) { - + // it doesn't look like this is called when the main function terminates. debug(1, "main cleanup handler called."); #ifdef CONFIG_MQTT if (config.mqtt_enabled) { @@ -1194,24 +1178,44 @@ void main_cleanup_handler(__attribute__((unused)) void *arg) { debug(1, "Deinitialise the audio backend."); config.output->deinit(); } - daemon_log(LOG_NOTICE, "Unexpected exit..."); +#ifdef CONFIG_LIBDAEMON daemon_retval_send(0); daemon_pid_file_remove(); daemon_signal_done(); +#endif + debug(1, "Exit..."); exit(0); } +void exit_function() { + debug(1, "exit function called..."); + main_cleanup_handler(NULL); + if (conns) + free(conns); // make sure the connections have been deleted first + if (config.service_name) + free(config.service_name); + if (config.regtype) + free(config.regtype); +#ifdef CONFIG_LIBDAEMON + if (config.computed_piddir) + free(config.computed_piddir); +#endif + if (ranarray) + free((void *)ranarray); + 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... +} + int main(int argc, char **argv) { 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 fp_time_at_startup = get_absolute_time_in_fp(); fp_time_at_last_debug_message = fp_time_at_startup; - // debug(1,"startup"); - daemon_set_verbosity(LOG_DEBUG); - memset(&config, 0, sizeof(config)); // also clears all strings, BTW - atexit(exit_function); - - // this is a bit weird, but apparently necessary + // this is a bit weird, but necessary -- basename() may modify the argument passed in char *basec = strdup(argv[0]); char *bname = basename(basec); config.appName = strdup(bname); @@ -1219,9 +1223,18 @@ int main(int argc, char **argv) { die("can not allocate memory for the app name!"); free(basec); + // debug(1,"startup"); +#ifdef CONFIG_LIBDAEMON + daemon_set_verbosity(LOG_DEBUG); +#else + setlogmask (LOG_UPTO (LOG_DEBUG)); + openlog(NULL,0,LOG_DAEMON); +#endif + atexit(exit_function); + // set defaults - // get thje endianness + // get the endianness union { uint32_t u32; uint8_t arr[4]; @@ -1298,6 +1311,8 @@ int main(int argc, char **argv) { exit(1); } +#ifdef CONFIG_LIBDAEMON + pid_t pid; /* Reset signal handlers */ @@ -1317,44 +1332,22 @@ int main(int argc, char **argv) { daemon_pid_file_proc = pid_file_proc; - /* Check if we are called with -D or --disconnectFromOutput parameter */ - if (argc >= 2 && - ((strcmp(argv[1], "-D") == 0) || (strcmp(argv[1], "--disconnectFromOutput") == 0))) { - if ((pid = daemon_pid_file_is_running()) >= 0) { - if (kill(pid, SIGUSR2) != 0) { // try to send the signal - daemon_log(LOG_WARNING, - "Failed trying to send disconnectFromOutput command to daemon pid: %d: %s", pid, - strerror(errno)); - } - } else { - daemon_log(LOG_WARNING, - "Can't send a disconnectFromOutput request -- Failed to find daemon: %s", - strerror(errno)); - } - exit(1); - } +#endif - /* Check if we are called with -R or --reconnectToOutput parameter */ - if (argc >= 2 && - ((strcmp(argv[1], "-R") == 0) || (strcmp(argv[1], "--reconnectToOutput") == 0))) { - if ((pid = daemon_pid_file_is_running()) >= 0) { - if (kill(pid, SIGHUP) != 0) { // try to send the signal - daemon_log(LOG_WARNING, - "Failed trying to send reconnectToOutput command to daemon pid: %d: %s", pid, - strerror(errno)); - } - } else { - daemon_log(LOG_WARNING, "Can't send a reconnectToOutput request -- Failed to find daemon: %s", - strerror(errno)); - } - exit(1); - } // parse arguments into config -- needed to locate pid_dir int audio_arg = parse_options(argc, argv); + // mDNS supports maximum of 63-character names (we append 13). + if (strlen(config.service_name) > 50) { + warn("Supplied name too long (max 50 characters)"); + config.service_name[50] = '\0'; // truncate it and carry on... + } + /* Check if we are called with -k or --kill parameter */ if (argc >= 2 && ((strcmp(argv[1], "-k") == 0) || (strcmp(argv[1], "--kill") == 0))) { + +#ifdef CONFIG_LIBDAEMON int ret; /* Kill daemon with SIGTERM */ @@ -1364,20 +1357,20 @@ int main(int argc, char **argv) { else daemon_pid_file_remove(); return ret < 0 ? 1 : 0; +#else + syslog(LOG_ERR, "shairport-sync was built without support for the -k or --kill option"); + return 1; +#endif + } +#ifdef CONFIG_LIBDAEMON /* If we are going to daemonise, check that the daemon is not running already.*/ if ((config.daemonise) && ((pid = daemon_pid_file_is_running()) >= 0)) { daemon_log(LOG_ERR, "Daemon already running on PID file %u", pid); return 1; } - // mDNS supports maximum of 63-character names (we append 13). - if (strlen(config.service_name) > 50) { - warn("Supplied name too long (max 50 characters)"); - config.service_name[50] = '\0'; // truncate it and carry on... - } - /* here, daemonise with libdaemon */ if (config.daemonise) { @@ -1460,6 +1453,8 @@ int main(int argc, char **argv) { /* end libdaemon stuff */ } +#endif + main_thread_id = pthread_self(); if (!main_thread_id) debug(1, "Main thread is set up to be NULL!"); @@ -1531,8 +1526,10 @@ int main(int argc, char **argv) { "deliberately.", config.diagnostic_drop_packet_fraction); debug(1, "statistics_requester status is %d.", config.statistics_requested); +#if CONFIG_LIBDAEMON debug(1, "daemon status is %d.", config.daemonise); debug(1, "deamon pid file path is \"%s\".", pid_file_proc()); +#endif debug(1, "rtsp listening port is %d.", config.port); debug(1, "udp base port is %d.", config.udp_port_base); debug(1, "udp port range is %d.", config.udp_port_range); From 916edea5a196424a855e3d74514f74ec1350f4cc Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 5 Feb 2019 15:13:16 +0000 Subject: [PATCH 425/569] remove dependency on libdaemon -- leave it in by default, but allow the new configuration option --with-libdaemon. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index fd934930..a92b3365 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.3d50], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d51], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 79e22063efc660118063df548853761a97c63754 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 5 Feb 2019 15:51:16 +0000 Subject: [PATCH 426/569] check that the systems are started before trying to stop them, duh. --- activity_monitor.c | 13 +++++++++---- dacp.c | 16 ++++++++++------ metadata_hub.c | 23 ++++++++++++++--------- rtsp.c | 11 ++++++++--- shairport.c | 14 ++++++++------ 5 files changed, 49 insertions(+), 28 deletions(-) diff --git a/activity_monitor.c b/activity_monitor.c index 6452a08e..86af012a 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -45,6 +45,8 @@ enum am_state { am_inactive, am_active, am_timing_out } state; enum ps_state { ps_inactive, ps_active } player_state; +int activity_monitor_running = 0; + pthread_t activity_monitor_thread; pthread_mutex_t activity_monitor_mutex; pthread_cond_t activity_monitor_cv; @@ -218,11 +220,14 @@ void *activity_monitor_thread_code(void *arg) { void activity_monitor_start() { // debug(1,"activity_monitor_start"); pthread_create(&activity_monitor_thread, NULL, activity_monitor_thread_code, NULL); + activity_monitor_running = 1; } void activity_monitor_stop() { - debug(1, "activity_monitor_stop start..."); - pthread_cancel(activity_monitor_thread); - pthread_join(activity_monitor_thread, NULL); - debug(1, "activity_monitor_stop complete"); + if (activity_monitor_running) { + debug(1, "activity_monitor_stop start..."); + pthread_cancel(activity_monitor_thread); + pthread_join(activity_monitor_thread, NULL); + debug(1, "activity_monitor_stop complete"); + } } diff --git a/dacp.c b/dacp.c index 7efaea3a..4f40e2d0 100644 --- a/dacp.c +++ b/dacp.c @@ -58,6 +58,7 @@ typedef struct { void *port_monitor_private_storage; } dacp_server_record; +int dacp_monitor_initialised = 0; pthread_t dacp_monitor_thread; dacp_server_record dacp_server; void *mdns_dacp_monitor_private_storage_pointer; @@ -888,15 +889,18 @@ void dacp_monitor_start() { memset(&dacp_server, 0, sizeof(dacp_server_record)); pthread_create(&dacp_monitor_thread, NULL, dacp_monitor_thread_code, NULL); + dacp_monitor_initialised = 1; } void dacp_monitor_stop() { - debug(1, "dacp_monitor_stop"); - pthread_cancel(dacp_monitor_thread); - pthread_join(dacp_monitor_thread, NULL); - pthread_mutex_destroy(&dacp_server_information_lock); - debug(1, "DACP Conversation Lock Mutex Destroyed"); - pthread_mutex_destroy(&dacp_conversation_lock); + if (dacp_monitor_initialised) { // only if it's been started and initialised + debug(1, "dacp_monitor_stop"); + pthread_cancel(dacp_monitor_thread); + pthread_join(dacp_monitor_thread, NULL); + pthread_mutex_destroy(&dacp_server_information_lock); + debug(1, "DACP Conversation Lock Mutex Destroyed"); + pthread_mutex_destroy(&dacp_conversation_lock); + } } uint32_t dacp_tlv_crawl(char **p, int32_t *length) { diff --git a/metadata_hub.c b/metadata_hub.c index 56869647..f116f272 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -58,6 +58,8 @@ #include #endif +int metadata_hub_initialised = 0; + pthread_rwlock_t metadata_hub_re_lock = PTHREAD_RWLOCK_INITIALIZER; struct track_metadata_bundle *track_metadata; // used for a temporary track metadata store @@ -100,18 +102,21 @@ void metadata_hub_init(void) { // debug(1, "Metadata bundle initialisation."); memset(&metadata_store, 0, sizeof(metadata_store)); track_metadata = NULL; + metadata_hub_initialised = 1; } void metadata_hub_stop(void) { - debug(1, "metadata_hub_stop."); - metadata_hub_release_track_artwork(); - if (metadata_store.track_metadata) { - metadata_hub_release_track_metadata(metadata_store.track_metadata); - metadata_store.track_metadata = NULL; - } - if (track_metadata) { - metadata_hub_release_track_metadata(track_metadata); - track_metadata = NULL; + if (metadata_hub_initialised) { + debug(1, "metadata_hub_stop."); + metadata_hub_release_track_artwork(); + if (metadata_store.track_metadata) { + metadata_hub_release_track_metadata(metadata_store.track_metadata); + metadata_store.track_metadata = NULL; + } + if (track_metadata) { + metadata_hub_release_track_metadata(track_metadata); + track_metadata = NULL; + } } } diff --git a/rtsp.c b/rtsp.c index 4b4b28e9..9aff0e65 100644 --- a/rtsp.c +++ b/rtsp.c @@ -91,6 +91,8 @@ enum rtsp_read_request_response { // Mike Brady's part... +int metadata_running = 0; + // always lock use this when accessing the playing conn value static pthread_mutex_t playing_conn_lock = PTHREAD_MUTEX_INITIALIZER; @@ -1454,12 +1456,15 @@ void metadata_init(void) { int ret = pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL); if (ret) debug(1, "Failed to create metadata thread!"); + metadata_running = 1; } void metadata_stop(void) { - debug(1, "metadata_stop called."); - pthread_cancel(metadata_thread); - pthread_join(metadata_thread, NULL); + if (metadata_running) { + debug(1, "metadata_stop called."); + pthread_cancel(metadata_thread); + pthread_join(metadata_thread, NULL); + } } int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier, diff --git a/shairport.c b/shairport.c index e1689d3e..2bea884f 100644 --- a/shairport.c +++ b/shairport.c @@ -1075,7 +1075,7 @@ int parse_options(int argc, char **argv) { } #if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE) -GMainLoop *g_main_loop; +static GMainLoop *g_main_loop = NULL; pthread_t dbus_thread; void *dbus_thread_func(__attribute__((unused)) void *arg) { @@ -1138,7 +1138,7 @@ const char *pid_file_proc(void) { #endif void main_cleanup_handler(__attribute__((unused)) void *arg) { - // it doesn't look like this is called when the main function terminates. + // it doesn't look like this is called when the main function is cancelled eith a pthread cancel. debug(1, "main cleanup handler called."); #ifdef CONFIG_MQTT if (config.mqtt_enabled) { @@ -1153,9 +1153,11 @@ void main_cleanup_handler(__attribute__((unused)) void *arg) { #ifdef CONFIG_DBUS_INTERFACE stop_dbus_service(); #endif - debug(1, "Stopping DBUS Loop Thread"); - g_main_loop_quit(g_main_loop); - pthread_join(dbus_thread, NULL); + if (g_main_loop) { + debug(1, "Stopping DBUS Loop Thread"); + g_main_loop_quit(g_main_loop); + pthread_join(dbus_thread, NULL); + } #endif #ifdef CONFIG_DACP_CLIENT @@ -1174,7 +1176,7 @@ void main_cleanup_handler(__attribute__((unused)) void *arg) { activity_monitor_stop(0); - if (config.output->deinit) { + if ((config.output) && (config.output->deinit)) { debug(1, "Deinitialise the audio backend."); config.output->deinit(); } From c86823c343a70f6106fd71e314255a8fce978e4d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 8 Feb 2019 09:04:33 +0000 Subject: [PATCH 427/569] Starting to connect active mode with disable standby. --- activity_monitor.c | 6 +++++- activity_monitor.h | 4 ++++ audio_alsa.c | 24 +++++++++++++++++++----- common.h | 7 +++++++ 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/activity_monitor.c b/activity_monitor.c index 86af012a..94321f8b 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -42,7 +42,7 @@ #include "common.h" #include "rtsp.h" -enum am_state { am_inactive, am_active, am_timing_out } state; +enum am_state state; enum ps_state { ps_inactive, ps_active } player_state; int activity_monitor_running = 0; @@ -217,6 +217,10 @@ void *activity_monitor_thread_code(void *arg) { pthread_exit(NULL); } +enum am_state activity_status() { + return (state); +} + void activity_monitor_start() { // debug(1,"activity_monitor_start"); pthread_create(&activity_monitor_thread, NULL, activity_monitor_thread_code, NULL); diff --git a/activity_monitor.h b/activity_monitor.h index 9de33454..d9e1ffd6 100644 --- a/activity_monitor.h +++ b/activity_monitor.h @@ -1,4 +1,8 @@ #pragma once + +enum am_state { am_inactive, am_active, am_timing_out }; + void activity_monitor_start(); void activity_monitor_stop(); void activity_monitor_signify_activity(int active); // 0 means inactive, non-zero means active +enum am_state activity_status(); // true if non inactive; false if inactive diff --git a/audio_alsa.c b/audio_alsa.c index 2fd4497d..4cb187b2 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -975,12 +975,26 @@ static int init(int argc, char **argv) { } } - // Get the optional "Keep DAC Busy setting" - int kdb; - if (config_set_lookup_bool(config.cfg, "alsa.disable_standby_mode", &kdb)) { - config.keep_dac_busy = kdb; + + /* Get the optional disable_standby_mode setting. */ + config.disable_standby_mode = disable_standby_while_active; + config.keep_dac_busy = 0; + if (config_lookup_string(config.cfg, "alsa.disable_standby_mode", &str)) { + if ((strcasecmp(str, "no") == 0) || (strcasecmp(str, "off") == 0) || (strcasecmp(str, "never") == 0)) + config.disable_standby_mode = disable_standby_off; + else if ((strcasecmp(str, "yes") == 0) || (strcasecmp(str, "on") == 0) || (strcasecmp(str, "always") == 0)) { + config.disable_standby_mode = disable_standby_on; + config.keep_dac_busy = 1; + } else if (strcasecmp(str, "while active") == 0) + config.disable_standby_mode = disable_standby_while_active; + else { + warn("Invalid disable_standby_mode option choice \"%s\". It should be " + "\"always\", \"while active\" or \"never\". " + "It is set to \"while active\"."); + } } - debug(1, "alsa: disable_standby_mode is %s.", config.keep_dac_busy ? "on" : "off"); + + debug(1, "alsa: disable_standby_mode is %d.", config.disable_standby_mode); } optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour diff --git a/common.h b/common.h index 870cb1a6..e13ebf9e 100644 --- a/common.h +++ b/common.h @@ -62,6 +62,12 @@ enum decoders_supported_type { decoder_apple_alac, } decoders_supported_type; +enum disable_standby_mode_type { + disable_standby_off = 0, + disable_standby_while_active, + disable_standby_always +} + // the following enum is for the formats recognised -- currently only S16LE is recognised for input, // so these are output only for the present @@ -201,6 +207,7 @@ typedef struct { float loudness_reference_volume_db; int alsa_use_hardware_mute; double alsa_maximum_stall_time; + enum disable_standby_mode_type disable_standby_mode; volatile int keep_dac_busy; #if defined(CONFIG_DBUS_INTERFACE) From 8b589879be8a3587ea9a8c80b4896b473f3e8f75 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 8 Feb 2019 09:40:51 +0000 Subject: [PATCH 428/569] fix a few minor compilation errors --- audio_alsa.c | 10 +++++++--- common.h | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 4cb187b2..ef65a345 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -27,8 +27,6 @@ #define ALSA_PCM_NEW_HW_PARAMS_API -#include "audio.h" -#include "common.h" #include #include #include @@ -37,6 +35,12 @@ #include #include +#include "config.h" + +#include "common.h" +#include "activity_monitor.h" +#include "audio.h" + enum alsa_backend_mode { abm_disconnected, abm_connected, @@ -983,7 +987,7 @@ static int init(int argc, char **argv) { if ((strcasecmp(str, "no") == 0) || (strcasecmp(str, "off") == 0) || (strcasecmp(str, "never") == 0)) config.disable_standby_mode = disable_standby_off; else if ((strcasecmp(str, "yes") == 0) || (strcasecmp(str, "on") == 0) || (strcasecmp(str, "always") == 0)) { - config.disable_standby_mode = disable_standby_on; + config.disable_standby_mode = disable_standby_always; config.keep_dac_busy = 1; } else if (strcasecmp(str, "while active") == 0) config.disable_standby_mode = disable_standby_while_active; diff --git a/common.h b/common.h index e13ebf9e..6fdf38c1 100644 --- a/common.h +++ b/common.h @@ -66,7 +66,7 @@ enum disable_standby_mode_type { disable_standby_off = 0, disable_standby_while_active, disable_standby_always -} +}; // the following enum is for the formats recognised -- currently only S16LE is recognised for input, // so these are output only for the present From 34351a99cb17014c9e8c0172ddbec734da038109 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 8 Feb 2019 16:57:39 +0000 Subject: [PATCH 429/569] Continue hooking up active mode and disable standby stuff --- activity_monitor.c | 23 ++++++++++++++++ dbus-service.c | 55 ++++++++++++++++++++++++++++++++++++- org.gnome.ShairportSync.xml | 2 ++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/activity_monitor.c b/activity_monitor.c index 94321f8b..796e3ed0 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -40,6 +40,7 @@ #include "activity_monitor.h" #include "common.h" +#include "dbus-service.h" #include "rtsp.h" enum am_state state; @@ -60,6 +61,17 @@ void going_active(int block) { debug(2, "abeg"); // active mode begin send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points #endif + +#ifdef CONFIG_DBUS_INTERFACE + shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE); +#endif + + if (config.disable_standby_mode == disable_standby_while_active) { + config.keep_dac_busy = 1; +#ifdef CONFIG_DBUS_INTERFACE + shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE); +#endif + } } void going_inactive(int block) { @@ -71,6 +83,17 @@ void going_inactive(int block) { debug(2, "aend"); // active mode end send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points #endif + +#ifdef CONFIG_DBUS_INTERFACE + shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE); +#endif + + if (config.disable_standby_mode == disable_standby_while_active) { + config.keep_dac_busy = 0; +#ifdef CONFIG_DBUS_INTERFACE + shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE); +#endif + } } void activity_monitor_signify_activity(int active) { diff --git a/dbus-service.c b/dbus-service.c index 59fdd2c0..c6156cb5 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -430,6 +430,36 @@ gboolean notify_drift_tolerance_callback(ShairportSync *skeleton, return TRUE; } +gboolean notify_disable_standby_mode_callback(ShairportSync *skeleton, + __attribute__((unused)) gpointer user_data) { + char *th = (char *)shairport_sync_get_disable_standby_mode(skeleton); + if ((strcasecmp(th, "no") == 0) || (strcasecmp(th, "off") == 0) || (strcasecmp(th, "never") == 0)) { + config.disable_standby_mode = disable_standby_off; + config.keep_dac_busy = 0; + } else if ((strcasecmp(th, "yes") == 0) || (strcasecmp(th, "on") == 0) || (strcasecmp(th, "always") == 0)) { + config.disable_standby_mode = disable_standby_always; + config.keep_dac_busy = 1; + } else if (strcasecmp(th, "while active") == 0) + config.disable_standby_mode = disable_standby_while_active; + else { + warn("An unrecognised disable_standby_mode: \"%s\" was requested via D-Bus interface.", th); + switch (config.disable_standby_mode) { + case disable_standby_off: + shairport_sync_set_disable_standby_mode(skeleton, "off"); + break; + case disable_standby_always: + shairport_sync_set_disable_standby_mode(skeleton, "always"); + break; + case disable_standby_while_active: + shairport_sync_set_disable_standby_mode(skeleton, "while active"); + break; + default: + break; + } + } + return TRUE; +} + gboolean notify_alacdecoder_callback(ShairportSync *skeleton, __attribute__((unused)) gpointer user_data) { char *th = (char *)shairport_sync_get_alacdecoder(skeleton); @@ -622,7 +652,9 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name G_CALLBACK(notify_interpolation_callback), NULL); g_signal_connect(shairportSyncSkeleton, "notify::alacdecoder", G_CALLBACK(notify_alacdecoder_callback), NULL); - g_signal_connect(shairportSyncSkeleton, "notify::volume-control-profile", + g_signal_connect(shairportSyncSkeleton, "notify::disable-standby-mode", + G_CALLBACK(notify_disable_standby_mode_callback), NULL); + g_signal_connect(shairportSyncSkeleton, "notify::volume-control-profile", G_CALLBACK(notify_volume_control_profile_callback), NULL); g_signal_connect(shairportSyncSkeleton, "notify::disable-standby", G_CALLBACK(notify_disable_standby_callback), NULL); @@ -706,6 +738,27 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name #endif + shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE); + debug(1, ">> Active set to \"false\""); + + switch (config.disable_standby_mode) { + case disable_standby_off: + shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "off"); + debug(1, ">> disable standby mode set to \"off\""); + break; + case disable_standby_always: + shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "always"); + debug(1, ">> disable standby mode set to \"always\""); + break; + case disable_standby_while_active: + shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "while active"); + debug(1, ">> disable standby mode set to \"while active\""); + break; + default: + debug(1,"invalid disable_standby mode!"); + break; + } + #ifdef CONFIG_SOXR if (config.packet_stuffing == ST_basic) { shairport_sync_set_interpolation(SHAIRPORT_SYNC(shairportSyncSkeleton), "basic"); diff --git a/org.gnome.ShairportSync.xml b/org.gnome.ShairportSync.xml index 279e4f0a..cea574bc 100644 --- a/org.gnome.ShairportSync.xml +++ b/org.gnome.ShairportSync.xml @@ -2,7 +2,9 @@ + + From 58acd161cd7da5d112c45ff0cfda09b8b4c3f752 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 8 Feb 2019 20:48:29 +0000 Subject: [PATCH 430/569] make including of reference to dbus-service conditional on CONFIG_DBUS_INTERFACE --- activity_monitor.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/activity_monitor.c b/activity_monitor.c index 796e3ed0..2d418d6d 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -40,9 +40,13 @@ #include "activity_monitor.h" #include "common.h" -#include "dbus-service.h" #include "rtsp.h" +#ifdef CONFIG_DBUS_INTERFACE +#include "dbus-service.h" +#endif + + enum am_state state; enum ps_state { ps_inactive, ps_active } player_state; From 97a4766e93c73e741b6ea4682f4e80ad14076d8a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 8 Feb 2019 22:31:50 +0000 Subject: [PATCH 431/569] Add call to check if the dbus service is started. --- activity_monitor.c | 14 ++++++++++++-- audio_alsa.c | 5 +++++ dbus-service.c | 8 ++++++++ dbus-service.h | 1 + 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/activity_monitor.c b/activity_monitor.c index 2d418d6d..697bec95 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -67,13 +67,18 @@ void going_active(int block) { #endif #ifdef CONFIG_DBUS_INTERFACE + if (dbus_service_is_running()) shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE); #endif if (config.disable_standby_mode == disable_standby_while_active) { - config.keep_dac_busy = 1; #ifdef CONFIG_DBUS_INTERFACE + if (dbus_service_is_running()) shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE); + else + config.keep_dac_busy = 1; +#else + config.keep_dac_busy = 1; #endif } } @@ -89,13 +94,18 @@ void going_inactive(int block) { #endif #ifdef CONFIG_DBUS_INTERFACE + if (dbus_service_is_running()) shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE); #endif if (config.disable_standby_mode == disable_standby_while_active) { - config.keep_dac_busy = 0; #ifdef CONFIG_DBUS_INTERFACE + if (dbus_service_is_running()) shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE); + else + config.keep_dac_busy = 0; +#else + config.keep_dac_busy = 0; #endif } } diff --git a/audio_alsa.c b/audio_alsa.c index ef65a345..9658832a 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1533,7 +1533,12 @@ void alsa_buffer_monitor_thread_cleanup_function(__attribute__((unused)) void */ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { + int okb = -1; while (1) { + if (okb != config.keep_dac_busy) { + debug(1,"keep_dac_busy is now %d",config.keep_dac_busy); + okb = config.keep_dac_busy; + } if ((config.keep_dac_busy != 0) && (alsa_device_initialised == 0)) { debug(2, "alsa: alsa_buffer_monitor_thread_code() calling " "do_alsa_device_init_if_needed."); diff --git a/dbus-service.c b/dbus-service.c index c6156cb5..8656d919 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -15,6 +15,8 @@ #include "dbus-service.h" +int service_is_running = 0; + ShairportSyncDiagnostics *shairportSyncDiagnosticsSkeleton = NULL; ShairportSyncRemoteControl *shairportSyncRemoteControlSkeleton = NULL; ShairportSyncAdvancedRemoteControl *shairportSyncAdvancedRemoteControlSkeleton = NULL; @@ -841,6 +843,7 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name debug(1, "Shairport Sync native D-Bus service started at \"%s\" on the %s bus.", name, (config.dbus_service_bus_type == DBT_session) ? "session" : "system"); + service_is_running = 1; } static void on_dbus_name_lost_again(__attribute__((unused)) GDBusConnection *connection, @@ -887,4 +890,9 @@ void stop_dbus_service() { g_bus_unown_name(ownerID); else debug(1, "Zero OwnerID for \"org.gnome.ShairportSync\"."); + service_is_running = 0; +} + +int dbus_service_is_running() { + return service_is_running; } diff --git a/dbus-service.h b/dbus-service.h index a00d06e0..5cfc02e2 100644 --- a/dbus-service.h +++ b/dbus-service.h @@ -7,5 +7,6 @@ ShairportSync *shairportSyncSkeleton; int start_dbus_service(); void stop_dbus_service(); +int dbus_service_is_running(); #endif /* #ifndef DBUS_SERVICE_H */ From f3865d77b0dcbb35e2c679d05f1aa2a376d09e92 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 9 Feb 2019 17:35:41 +0000 Subject: [PATCH 432/569] quieten a few debug messages --- audio_alsa.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 9658832a..711a1767 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1545,7 +1545,7 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { do_alsa_device_init_if_needed(); } int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000); - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 200000, 1); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 200000, 0); // check possible state transitions here if ((alsa_backend_state == abm_disconnected) && (config.keep_dac_busy != 0)) { // open the dac and move to abm_connected mode @@ -1620,7 +1620,7 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { } } } - debug_mutex_unlock(&alsa_mutex, 3); + debug_mutex_unlock(&alsa_mutex, 0); pthread_cleanup_pop(0); // release the mutex usleep(sleep_time_ms * 1000); // has a cancellation point in it } From 804e62d49545b0fb2820b1f25ad74045b5e5da68 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 9 Feb 2019 18:23:52 +0000 Subject: [PATCH 433/569] Hook up the active mode to the disable standby facility, enabling a 'while active' setting for it. Expose them to D-Bus. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index a92b3365..f8c72f71 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.3d51], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d52], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 54bb627effb347f4a6237a03509d8396a1d27f76 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 12 Feb 2019 10:55:04 +0000 Subject: [PATCH 434/569] Fix some errors and warnings from OpenBSD --- activity_monitor.c | 1 - common.c | 4 ++-- shairport.c | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/activity_monitor.c b/activity_monitor.c index 697bec95..ad0c528f 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -170,7 +170,6 @@ void *activity_monitor_thread_code(void *arg) { uint64_t sec; uint64_t nsec; - int rc; struct timespec time_for_wait; state = am_inactive; diff --git a/common.c b/common.c index d8be92a4..6bd31524 100644 --- a/common.c +++ b/common.c @@ -450,7 +450,7 @@ char *base64_enc(uint8_t *input, int length) { b64 = BIO_push(b64, bmem); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); BIO_write(b64, input, length); - BIO_flush(b64); + (void) BIO_flush(b64); BIO_get_mem_ptr(b64, &bptr); char *buf = (char *)malloc(bptr->length); @@ -482,7 +482,7 @@ uint8_t *base64_dec(char *input, int *outlen) { BIO_write(bmem, input, inlen); while (inlen++ & 3) BIO_write(bmem, "=", 1); - BIO_flush(bmem); + (void) BIO_flush(bmem); int bufsize = strlen(input) * 3 / 4 + 1; uint8_t *buf = malloc(bufsize); diff --git a/shairport.c b/shairport.c index 2bea884f..7cb2c39a 100644 --- a/shairport.c +++ b/shairport.c @@ -31,7 +31,6 @@ #include #include #include -#include #include #include #include @@ -39,6 +38,7 @@ #include #include #include +#include #include #include "config.h" From 41796cbaf9a6d0b9a3a7f87f9123e1dac2bc6b0f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 12 Feb 2019 12:08:16 +0000 Subject: [PATCH 435/569] Quieten two compilation warnings from tinysvcmdns --- tinysvcmdns.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tinysvcmdns.c b/tinysvcmdns.c index bdce032c..d0cbebb8 100644 --- a/tinysvcmdns.c +++ b/tinysvcmdns.c @@ -416,7 +416,7 @@ int rr_list_append(struct rr_list **rr_head, struct rr_entry *rr) { if (*rr_head == NULL) { *rr_head = node; } else { - struct rr_list *e = *rr_head, *taile; + struct rr_list *e = *rr_head, *taile = NULL; for (; e; e = e->next) { // already in list - don't add if (e->e == rr) { @@ -426,7 +426,10 @@ int rr_list_append(struct rr_list **rr_head, struct rr_entry *rr) { if (e->next == NULL) taile = e; } - taile->next = node; + if (taile) + taile->next = node; + else + DEBUG_PRINTF("taile not given a value.\n"); } } else { die("can not allocate memory for \"node\" in tinysvcmdns."); @@ -500,7 +503,7 @@ struct rr_entry *rr_create(uint8_t *name, enum rr_type type) { } void rr_set_nsec(struct rr_entry *rr_nsec, enum rr_type type) { - assert(rr_nsec->type = RR_NSEC); + assert((rr_nsec->type = RR_NSEC)); assert((type / 8) < sizeof(rr_nsec->data.NSEC.bitmap)); rr_nsec->data.NSEC.bitmap[type / 8] = 1 << (7 - (type % 8)); From 5af6179875a60076426cd9459b1a4e87e5fad413 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 12 Feb 2019 15:47:10 +0000 Subject: [PATCH 436/569] use the term'state' rather than 'mode' when taling about the active state. Use 'while_active' rather than 'while active' --- activity_monitor.c | 6 +++--- audio_alsa.c | 6 +++--- common.h | 2 +- dbus-service.c | 8 ++++---- metadata_hub.h | 6 +++--- scripts/shairport-sync.conf | 10 +++++----- shairport.c | 20 ++++++++++---------- 7 files changed, 29 insertions(+), 29 deletions(-) diff --git a/activity_monitor.c b/activity_monitor.c index ad0c528f..4377c89e 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -132,7 +132,7 @@ void activity_monitor_signify_activity(int active) { going_active( config.cmd_blocking); // note -- will be executed with the mutex locked, but that's okay } else if ((state == am_active) && (player_state == ps_inactive) && - (config.active_mode_timeout == 0.0)) { + (config.active_state_timeout == 0.0)) { going_inactive( config.cmd_blocking); // note -- will be executed with the mutex locked, but that's okay } @@ -189,14 +189,14 @@ void *activity_monitor_thread_code(void *arg) { // debug(1,"am_state: am_active"); while (player_state != ps_inactive) pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); - if (config.active_mode_timeout == 0.0) { + if (config.active_state_timeout == 0.0) { state = am_inactive; // going_inactive(); // this is done in activity_monitor_signify_activity } else { state = am_timing_out; uint64_t time_to_wait_for_wakeup_fp = - (uint64_t)(config.active_mode_timeout * 1000000); // resolution of microseconds + (uint64_t)(config.active_state_timeout * 1000000); // resolution of microseconds time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp << 32; time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp / 1000000; diff --git a/audio_alsa.c b/audio_alsa.c index 711a1767..dd114e28 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -989,12 +989,12 @@ static int init(int argc, char **argv) { else if ((strcasecmp(str, "yes") == 0) || (strcasecmp(str, "on") == 0) || (strcasecmp(str, "always") == 0)) { config.disable_standby_mode = disable_standby_always; config.keep_dac_busy = 1; - } else if (strcasecmp(str, "while active") == 0) + } else if (strcasecmp(str, "while_active") == 0) config.disable_standby_mode = disable_standby_while_active; else { warn("Invalid disable_standby_mode option choice \"%s\". It should be " - "\"always\", \"while active\" or \"never\". " - "It is set to \"while active\"."); + "\"always\", \"while_active\" or \"never\". " + "It is set to \"while_active\"."); } } diff --git a/common.h b/common.h index 6fdf38c1..78c9ddda 100644 --- a/common.h +++ b/common.h @@ -186,7 +186,7 @@ typedef struct { double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any // fixed latency there might be in the audio path double audio_backend_silent_lead_in_time; // the length of the silence that should precede a play. - double active_mode_timeout; // the amount of time from when play ends to when the system leaves + double active_state_timeout; // the amount of time from when play ends to when the system leaves // into the "active" mode. uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's // native range. diff --git a/dbus-service.c b/dbus-service.c index 8656d919..2ed17d0a 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -441,7 +441,7 @@ gboolean notify_disable_standby_mode_callback(ShairportSync *skeleton, } else if ((strcasecmp(th, "yes") == 0) || (strcasecmp(th, "on") == 0) || (strcasecmp(th, "always") == 0)) { config.disable_standby_mode = disable_standby_always; config.keep_dac_busy = 1; - } else if (strcasecmp(th, "while active") == 0) + } else if (strcasecmp(th, "while_active") == 0) config.disable_standby_mode = disable_standby_while_active; else { warn("An unrecognised disable_standby_mode: \"%s\" was requested via D-Bus interface.", th); @@ -453,7 +453,7 @@ gboolean notify_disable_standby_mode_callback(ShairportSync *skeleton, shairport_sync_set_disable_standby_mode(skeleton, "always"); break; case disable_standby_while_active: - shairport_sync_set_disable_standby_mode(skeleton, "while active"); + shairport_sync_set_disable_standby_mode(skeleton, "while_active"); break; default: break; @@ -753,8 +753,8 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name debug(1, ">> disable standby mode set to \"always\""); break; case disable_standby_while_active: - shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "while active"); - debug(1, ">> disable standby mode set to \"while active\""); + shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "while_active"); + debug(1, ">> disable standby mode set to \"while_active\""); break; default: debug(1,"invalid disable_standby mode!"); diff --git a/metadata_hub.h b/metadata_hub.h index 9406851f..784f6621 100644 --- a/metadata_hub.h +++ b/metadata_hub.h @@ -12,10 +12,10 @@ enum play_status_type { PS_PLAYING, } play_status_type; -enum active_mode_type { +enum active_state_type { AM_INACTIVE = 0, AM_ACTIVE, -} active_mode_type; +} active_state_type; enum shuffle_status_type { SS_NOT_AVAILABLE = 0, @@ -82,7 +82,7 @@ typedef struct metadata_bundle { enum play_status_type player_state; // this is the state of the actual player itself, which can be a bit noisy. - enum active_mode_type active_state; + enum active_state_type active_state; int speaker_volume; // this is the actual speaker volume, allowing for the main volume and the // speaker volume control diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 4a6ebe29..bf6ce4ba 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -56,10 +56,10 @@ sessioncontrol = // run_this_before_play_begins = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line // run_this_after_play_ends = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line -// "active" mode starts when play begins and ends when the active_mode_timeout has elapsed after play ends, unless another play session starts before the timeout has fully elapsed. -// run_this_before_entering_active_mode = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line -// run_this_after_exiting_active_mode = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line -// active_mode_timeout = 10; // wait for this number of seconds after play ends before leaving the active mode, unless another play session begins. +// "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. +// run_this_before_entering_active_state = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line +// run_this_after_exiting_active_state = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line +// active_state_timeout = 10; // wait for this number of seconds after play ends before leaving the active state, unless another play session begins. // run_this_if_an_unfixable_error_is_detected = "/full/path/to/application and args"; // if a problem occurs that can't be cleared by Shairport Sync itself, hook a program on here to deal with it. An error code-string is passed as the last argument. // Many of these "unfixable" problems are caused by malfunctioning output devices, and sometimes it is necessary to restart the whole device to clear the problem. @@ -86,7 +86,7 @@ alsa = // use_mmap_if_available = "yes"; // Use this optional advanced setting to control whether MMAP-based output is used to communicate with the DAC. Default is "yes" // use_hardware_mute_if_available = "no"; // Use this optional advanced setting to control whether the hardware in the DAC is used for muting. Default is "no", for compatibility with other audio players. // maximum_stall_time = 0.200; // Use this optional advanced setting to control how long to wait for data to be consumed by the output device before considering it an error. It should never approach 200 ms. -// disable_standby_mode = "no"; // Some DACs make small "popping" noises when they go in and out of standby mode. Set this to "yes" to keep the DAC active all the time. +// disable_standby_mode = "while_active"; // Some DACs make small "popping" noises when they go in and out of standby mode. Settings can be: "always", "while_active" or "never". Default is "while_active". You can use "yes" instead of "always" and "no instead of "never". }; // Parameters for the "sndio" audio back end. All are optional. diff --git a/shairport.c b/shairport.c index 7cb2c39a..0959e5f2 100644 --- a/shairport.c +++ b/shairport.c @@ -336,7 +336,7 @@ int parse_options(int argc, char **argv) { // nothing else comes in first. config.fixedLatencyOffset = 11025; // this sounds like it works properly. config.diagnostic_drop_packet_fraction = 0.0; - config.active_mode_timeout = 10.0; + config.active_state_timeout = 10.0; config.volume_range_hw_priority = 1; // if combining software and hardware volume control, give the hardware priority // i.e. when reducing volume, reduce the hw first before reducing the software. @@ -737,23 +737,23 @@ int parse_options(int argc, char **argv) { config.cmd_stop = (char *)str; } - if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_entering_active_mode", + if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_entering_active_state", &str)) { config.cmd_active_start = (char *)str; } - if (config_lookup_string(config.cfg, "sessioncontrol.run_this_after_exiting_active_mode", + if (config_lookup_string(config.cfg, "sessioncontrol.run_this_after_exiting_active_state", &str)) { config.cmd_active_stop = (char *)str; } - if (config_lookup_float(config.cfg, "sessioncontrol.active_mode_timeout", &dvalue)) { + if (config_lookup_float(config.cfg, "sessioncontrol.active_state_timeout", &dvalue)) { if (dvalue < 0.0) - warn("Invalid value \"%f\" for sessioncontrol.active_mode_timeout. It must be positive. " + warn("Invalid value \"%f\" for sessioncontrol.active_state_timeout. It must be positive. " "The default of %f will be used instead.", - dvalue, config.active_mode_timeout); + dvalue, config.active_state_timeout); else - config.active_mode_timeout = dvalue; + config.active_state_timeout = dvalue; } if (config_lookup_string(config.cfg, @@ -1542,9 +1542,9 @@ int main(int argc, char **argv) { debug(1, "wait-cmd status is %d.", config.cmd_blocking); debug(1, "run_this_before_play_begins may return output is %d.", config.cmd_start_returns_output); debug(1, "run_this_if_an_unfixable_error_is_detected action is \"%s\".", config.cmd_unfixable); - debug(1, "run_this_before_entering_active_mode action is \"%s\".", config.cmd_active_start); - debug(1, "run_this_after_exiting_active_mode action is \"%s\".", config.cmd_active_stop); - debug(1, "active_mode_timeout is %f seconds.", config.active_mode_timeout); + debug(1, "run_this_before_entering_active_state action is \"%s\".", config.cmd_active_start); + debug(1, "run_this_after_exiting_active_state action is \"%s\".", config.cmd_active_stop); + debug(1, "active_state_timeout is %f seconds.", config.active_state_timeout); debug(1, "mdns backend \"%s\".", config.mdns_name); debug(2, "userSuppliedLatency is %d.", config.userSuppliedLatency); debug(1, "stuffing option is \"%d\" (0-basic, 1-soxr).", config.packet_stuffing); From 9bc925be904c7dc12e3fe0f193f7762f47839612 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 12 Feb 2019 16:22:02 +0000 Subject: [PATCH 437/569] Update RELEASENOTES.md --- RELEASENOTES.md | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9a424de7..ef07d12e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,15 +1,31 @@ +Version 3.3d52 to Version 3.3d40 +==== +**New Features** +* Two new external program/script hooks – `run_this_before_entering_active_state` and `run_this_after_exiting_active_state` are provided for when the system becomes active and when it becomes inactive. Background: Many users use the `run_this_before_play_begins` program hook to turn on an amplifier and the `run_this_after_play_ends` hook to turn it off. A big problem is when another play session starts immediately afterwards. This happens, for example, when a YouTube clip ends and the next one begins, and the time interval can be very short. All this switching can stress electronics and speakers. To get around this, the concept of an *active state* covering a sequence of play sessions is introduced. An active state begins when a play session starts. When the play session ends, the system stays in the active state for a period called the `active_state_timeout`, 10 seconds by default. If another play session starts before that timeout elapses, the system remains in the active state; otherwise the system leaves the active state. +* The new `alsa`-only Disable Standby feature, intended to reduce audible pops by preventing the DAC entering standby mode, can be set with the `alsa` group setting `disable_standby_mode` to `always`, `while_active` or `never`. The `always` setting is recommended for systems where the output device is dedicated to Shairport Sync. The "Disable Standby" mode can be set or cleared via the D-Bus interface `DisableStandby` property. + +**Enhancements** +* The volume-control software has been completely rewritten. From a user's point of view, the result should be a much smoother response to volume contol changes, free from artefacts. It is now also possible to combine the hardware mixer and the software attenuator in two ways -- giving priority to the software mixer of giving priority to the hardware mixer. +* The muting/unmuting code has been rewritten to be simpler and more consistent. +* In the `alsa` backend, new `play()` and `delay()` functions minimise the use of `snd_pcm_recover()` to prevent unnecessary resets of the output DACs. +* In the `alsa` backend driver, hardware isn't accessed until the first time it is needed. That is, when Shairport Sync starts up, it no longer needs to access the device momentarily. instead, it waits for the first use. +* The `libdaemon` library is now an optional build. It is not necessary for `systemd` systems and van be omitted. Use the `--without-libdaemon` configuration option to leave it out. +**Bug Fixes** +* The `mdns-external` method used to advertise the Shairport Sync AirPlay service on ZeroConf is now an optional build and is omitted by default. Hitherto, it was included with `--with-avahi` and could not be included on its own. +* A number of memory leaks associated with the use of OpenSSL have been fixed. + Version 3.3d39 to Version 3.3d38 ==== **New Feature** -This important new feature helps with removing some annoying clicking / popping noises. It is really pretty impressive, especially combined with the fixes to the dithering code described below. If you can enable 24- or 32-bit audio output to your output device, you can get even better results. +A new feature called *Disable Standby* keeps the output DAC in the play state all the time and helps to remove some annoying clicking / popping noises. It is really pretty impressive, especially combined with the fixes to the dithering code described below. If you can enable 24- or 32-bit audio output to your output device, you can get even better results. This is an ALSA-specific attempt to remove the annoying low-level clicking sounds that some output devices make just when they start processing audio and sometimes when they stop. Typically a faint click might be heard just before a play session starts or just before audio resumes after a pause. Similarly, a faint click can sometimes be heard just after a play session ends. It is extremely difficult to remove these clicks completely from the hardware, so this new feature ensures that — once Shairport Sync has started — the output device avoids situations where these clicks might be generated by *always* playing audio. To accomplish this, if Shairport Sync isn't actually playing anything, audio frames consisting of silence are sent to the output device. Apart from the initial startup transition, the output device never stops playing, avoiding both starting and stopping transitions. -To enable this feature, a new `alsa` group setting with the name `disable_standby_mode` is available. (If you do a full `$ sudo make install`, a new sample configuration file with this setting in it is installed at `/etc/shairport-sync.conf.sample` or `/usr/local/etc/shairport-sync.conf.sample`.) The default for the setting is `"no"`. +To enable this feature, a new `alsa` group setting with the name `disable_standby_mode` is available. (If you do a full `$ sudo make install`, a new sample configuration file with this setting in it is installed at `/etc/shairport-sync.conf.sample` or `/usr/local/etc/shairport-sync.conf.sample`.) The default for the setting is `"no"`. *(Note -- this was changed subsequently; please see above.)* A downside to this feature is that the output device totally dedicated to Shairport Sync. For this reason, this new feature is disabled by default. @@ -17,9 +33,7 @@ Note -- this setting is likely to change. It will probably be necessary to modif **Enhancements and Bug Fixes** -At present, Shairport Sync adds dither to the audio if (a) the built-in software-based volume control is used, (b) if the audio is mixed to mono or (3) if there is a change in sample size, say from 16- to 24-bit. The code for generating dither has been much improved. Due to a poor pseudo-random number arrangement, the dither noise didn't sound like white noise -- now it does. - -In addition, if dither is to be added, it is also added to the silence inserted just prior to the start of play, and is also added to the silent frames used to prevent the output device from going into standby mode, if selected. +* At present, Shairport Sync adds dither to the audio if (a) the built-in software-based volume control is used, (b) if the audio is mixed to mono or (3) if there is a change in sample size, say from 16- to 24-bit. The code for generating dither has been much improved. Due to a poor pseudo-random number arrangement, the dither noise didn't sound like white noise -- now it does. In addition, if dither is to be added, it is also added to the silence inserted just prior to the start of play, and is also added to the silent frames used to prevent the output device from going into standby mode, if selected. Version 3.3d37 to Version 3.3d22 ==== From 18732b9c5616be1ee4b73b5db2307141a13d1003 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 12 Feb 2019 16:31:59 +0000 Subject: [PATCH 438/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ef07d12e..a9a0f104 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -5,7 +5,7 @@ Version 3.3d52 to Version 3.3d40 * The new `alsa`-only Disable Standby feature, intended to reduce audible pops by preventing the DAC entering standby mode, can be set with the `alsa` group setting `disable_standby_mode` to `always`, `while_active` or `never`. The `always` setting is recommended for systems where the output device is dedicated to Shairport Sync. The "Disable Standby" mode can be set or cleared via the D-Bus interface `DisableStandby` property. **Enhancements** -* The volume-control software has been completely rewritten. From a user's point of view, the result should be a much smoother response to volume contol changes, free from artefacts. It is now also possible to combine the hardware mixer and the software attenuator in two ways -- giving priority to the software mixer of giving priority to the hardware mixer. +* The volume-control software has been completely rewritten. From a user's point of view, the result should be a much smoother response to volume contol changes, free from artefacts. It is now also possible to combine the hardware mixer and the software attenuator in two ways -- giving priority to the software mixer or giving priority to the hardware mixer. * The muting/unmuting code has been rewritten to be simpler and more consistent. * In the `alsa` backend, new `play()` and `delay()` functions minimise the use of `snd_pcm_recover()` to prevent unnecessary resets of the output DACs. * In the `alsa` backend driver, hardware isn't accessed until the first time it is needed. That is, when Shairport Sync starts up, it no longer needs to access the device momentarily. instead, it waits for the first use. From 29fcd28e78f7d0797c38247ce794af9a5476f981 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 12 Feb 2019 16:34:05 +0000 Subject: [PATCH 439/569] Update RELEASENOTES.md --- RELEASENOTES.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a9a0f104..42dcc6d5 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -8,10 +8,11 @@ Version 3.3d52 to Version 3.3d40 * The volume-control software has been completely rewritten. From a user's point of view, the result should be a much smoother response to volume contol changes, free from artefacts. It is now also possible to combine the hardware mixer and the software attenuator in two ways -- giving priority to the software mixer or giving priority to the hardware mixer. * The muting/unmuting code has been rewritten to be simpler and more consistent. * In the `alsa` backend, new `play()` and `delay()` functions minimise the use of `snd_pcm_recover()` to prevent unnecessary resets of the output DACs. -* In the `alsa` backend driver, hardware isn't accessed until the first time it is needed. That is, when Shairport Sync starts up, it no longer needs to access the device momentarily. instead, it waits for the first use. -* The `libdaemon` library is now an optional build. It is not necessary for `systemd` systems and van be omitted. Use the `--without-libdaemon` configuration option to leave it out. +* In the `alsa` backend driver, hardware isn't accessed until the first time it is needed. That is, when Shairport Sync starts up, it no longer needs to access the device momentarily. Instead, it waits for the first use. +* The `libdaemon` library is now an optional build. It is not necessary for `systemd` systems and can be omitted. Use the `--without-libdaemon` configuration option to leave it out. + **Bug Fixes** -* The `mdns-external` method used to advertise the Shairport Sync AirPlay service on ZeroConf is now an optional build and is omitted by default. Hitherto, it was included with `--with-avahi` and could not be included on its own. +* The `mdns-external` method used to advertise the Shairport Sync AirPlay service on ZeroConf is now an optional build and is omitted by default. Previously it was included with `--with-avahi` and could not be included on its own. * A number of memory leaks associated with the use of OpenSSL have been fixed. Version 3.3d39 to Version 3.3d38 From 7921ab2320a28be9e89229dd2bf9bae9c745c5b5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 12 Feb 2019 16:36:52 +0000 Subject: [PATCH 440/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 42dcc6d5..2b67c5ad 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,7 +2,7 @@ Version 3.3d52 to Version 3.3d40 ==== **New Features** * Two new external program/script hooks – `run_this_before_entering_active_state` and `run_this_after_exiting_active_state` are provided for when the system becomes active and when it becomes inactive. Background: Many users use the `run_this_before_play_begins` program hook to turn on an amplifier and the `run_this_after_play_ends` hook to turn it off. A big problem is when another play session starts immediately afterwards. This happens, for example, when a YouTube clip ends and the next one begins, and the time interval can be very short. All this switching can stress electronics and speakers. To get around this, the concept of an *active state* covering a sequence of play sessions is introduced. An active state begins when a play session starts. When the play session ends, the system stays in the active state for a period called the `active_state_timeout`, 10 seconds by default. If another play session starts before that timeout elapses, the system remains in the active state; otherwise the system leaves the active state. -* The new `alsa`-only Disable Standby feature, intended to reduce audible pops by preventing the DAC entering standby mode, can be set with the `alsa` group setting `disable_standby_mode` to `always`, `while_active` or `never`. The `always` setting is recommended for systems where the output device is dedicated to Shairport Sync. The "Disable Standby" mode can be set or cleared via the D-Bus interface `DisableStandby` property. +* A new `alsa`-only `disable_standby_mode` setting, for controlling the Disable Standby feature, can be set to: `always`, `while_active` or `never`. The `always` setting is recommended for systems where the output device is dedicated to Shairport Sync. The "Disable Standby" state iself can be set or cleared via the D-Bus interface `DisableStandby` property. **Enhancements** * The volume-control software has been completely rewritten. From a user's point of view, the result should be a much smoother response to volume contol changes, free from artefacts. It is now also possible to combine the hardware mixer and the software attenuator in two ways -- giving priority to the software mixer or giving priority to the hardware mixer. From f07c16a6b96c8ed01812f623aa11721015199e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Thu, 14 Feb 2019 15:37:54 +0000 Subject: [PATCH 441/569] Add lock-free ringbuffer. Preparation to make jack_stream_write_cb realtime safe. --- audio_jack.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/audio_jack.c b/audio_jack.c index c8a8b9ee..91abbe4b 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -31,6 +31,7 @@ #include #include +#include enum ift_type { IFT_frame_left_sample = 0, @@ -81,6 +82,8 @@ jack_client_t *client; jack_nframes_t sample_rate; jack_nframes_t jack_latency; +static jack_ringbuffer_t *jackbuf; + jack_latency_range_t latest_left_latency_range, latest_right_latency_range; int64_t time_of_latest_transfer; @@ -282,6 +285,9 @@ void jack_deinit() { if (open_client_if_necessary_thread) { pthread_cancel(*open_client_if_necessary_thread); free((char *)open_client_if_necessary_thread); + + jack_ringbuffer_free(jackbuf); + } } @@ -354,6 +360,9 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a if (config.jack_right_channel_name == NULL) config.jack_right_channel_name = strdup("right"); + jackbuf = jack_ringbuffer_create(buffer_size); // make the jack ringbuffer the same size as audio_lmb + jack_ringbuffer_mlock(jackbuf); // lock buffer into memory so that it never gets paged out + jack_set_error_function(default_jack_error_callback); jack_set_info_function(default_jack_info_callback); From 06504516f12573bd714d12ed143955d549502607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Thu, 14 Feb 2019 15:45:57 +0000 Subject: [PATCH 442/569] die() on failure to create ringbuffer. --- audio_jack.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/audio_jack.c b/audio_jack.c index 91abbe4b..88bec832 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -361,6 +361,8 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a config.jack_right_channel_name = strdup("right"); jackbuf = jack_ringbuffer_create(buffer_size); // make the jack ringbuffer the same size as audio_lmb + if (jackbuf == NULL) + die("Can't allocate %d bytes for the JACK ringbuffer.", buffer_size); jack_ringbuffer_mlock(jackbuf); // lock buffer into memory so that it never gets paged out jack_set_error_function(default_jack_error_callback); From e99837f878360b82363ede4f2358de47e01a1cf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Thu, 14 Feb 2019 20:18:05 +0000 Subject: [PATCH 443/569] First step of replacing existing buffer with jack_ringbuffer. Compiles, but does not work, of course. --- audio_jack.c | 108 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 89 insertions(+), 19 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 88bec832..1dc471ac 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -38,15 +38,20 @@ enum ift_type { IFT_frame_right_sample, } ift_type; +// Two-channel, 16bit audio: +static const int bytes_per_frame = 4; // Four seconds buffer -- should be plenty -#define buffer_size 44100 * 4 * 2 * 2 +#define buffer_size 44100 * 4 * bytes_per_frame static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER; +/* char *audio_lmb, *audio_umb, *audio_toq, *audio_eoq; + size_t audio_occupancy; // this is in frames, not bytes. A frame is a left and // right sample, each 16 bits, hence 4 bytes +*/ pthread_t *open_client_if_necessary_thread = NULL; int jack_init(int, char **); @@ -83,6 +88,7 @@ jack_nframes_t sample_rate; jack_nframes_t jack_latency; static jack_ringbuffer_t *jackbuf; +static int flush_please = 0; jack_latency_range_t latest_left_latency_range, latest_right_latency_range; int64_t time_of_latest_transfer; @@ -90,7 +96,15 @@ int64_t time_of_latest_transfer; int play(void *buf, int samples) { // debug(1,"jack_play of %d samples.",samples); // copy the samples into the queue - size_t bytes_to_transfer = samples * 2 * 2; + size_t bytes_to_transfer, bytes_transferred; + bytes_to_transfer = samples * bytes_per_frame; + pthread_mutex_lock(&buffer_mutex); // it's ok to lock here since we're not in the realtime callback + bytes_transferred = jack_ringbuffer_write(jackbuf, buf, bytes_to_transfer); + pthread_mutex_unlock(&buffer_mutex); + if (bytes_transferred < bytes_to_transfer) { + debug(1, "JACK ringbuffer overrun. Only wrote %d of %d bytes.", bytes_transferred, bytes_to_transfer); + } +/* size_t space_to_end_of_buffer = audio_umb - audio_eoq; if (space_to_end_of_buffer >= bytes_to_transfer) { memcpy(audio_eoq, buf, bytes_to_transfer); @@ -107,6 +121,7 @@ int play(void *buf, int samples) { audio_eoq = audio_lmb + bytes_to_transfer - space_to_end_of_buffer; pthread_mutex_unlock(&buffer_mutex); } +*/ return 0; } @@ -132,6 +147,24 @@ void deinterleave_and_convert_stream(const char *interleaved_frames, } } +static inline jack_default_audio_sample_t sample_conv(short sample) { + return ((sample < 0) ? (-1.0 * sample / SHRT_MIN) : (1.0 * sample / SHRT_MAX)); +} + +void deinterleave_and_convert(const char *interleaved_frames, + jack_default_audio_sample_t * const jack_buffer_left, + jack_default_audio_sample_t * const jack_buffer_right, + jack_nframes_t nframes) { + jack_nframes_t i; + short *ifp = (short *)interleaved_frames; // we're dealing with 16bit audio here + jack_default_audio_sample_t *fpl = jack_buffer_left; + jack_default_audio_sample_t *fpr = jack_buffer_right; + for (i=0; i nframes) { + frames_required = nframes; + } else { + frames_required = thisbuf; + } + deinterleave_and_convert(v[i].buf, &left_buffer[frames_written], &right_buffer[frames_written], frames_required); + frames_written = frames_required; + nframes -= frames_written; + } + jack_ringbuffer_read_advance(jackbuf, frames_written * bytes_per_frame); + + // debug(1,"transferring %u frames",written); + // Why are we doing this? The port latency should never change unless the JACK graph is reordered. Should happen on a graph reorder callback only. + // jack_port_get_latency_range(left_port, JackPlaybackLatency, &latest_left_latency_range); + // jack_port_get_latency_range(right_port, JackPlaybackLatency, &latest_right_latency_range); + + // now, if there are any more frames to put into the buffer, fill them with + // silence + while (nframes > 0) { + left_buffer[frames_written] = 0.0; + right_buffer[frames_written] = 0.0; + frames_written++; + nframes--; + } + time_of_latest_transfer = get_absolute_time_in_fp(); // this is the only writer, no locking necessary. + +/* size_t frames_we_can_transfer = nframes; // lock pthread_mutex_lock(&buffer_mutex); @@ -173,7 +241,6 @@ int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *a IFT_frame_right_sample); audio_toq = audio_lmb + (frames_we_can_transfer - first_portion_to_write) * 2 * 2; } - // debug(1,"transferring %u frames",frames_we_can_transfer); audio_occupancy -= frames_we_can_transfer; jack_port_get_latency_range(left_port, JackPlaybackLatency, &latest_left_latency_range); jack_port_get_latency_range(right_port, JackPlaybackLatency, &latest_right_latency_range); @@ -181,16 +248,12 @@ int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *a pthread_mutex_unlock(&buffer_mutex); // unlock - // now, if there are any more frames to put into the buffer, fill them with - // silence - jack_nframes_t i; - for (i = frames_we_can_transfer; i < nframes; i++) { - left_buffer[i] = 0.0; - right_buffer[i] = 0.0; - } +*/ return 0; } +// FIXME: set_graph_order_callback(), recompute latencies here! + void default_jack_error_callback(const char *desc) { debug(2, "jackd error: \"%s\"", desc); } void default_jack_info_callback(const char *desc) { inform("jackd information: \"%s\"", desc); } @@ -207,6 +270,7 @@ void default_jack_set_latency_callback(jack_latency_callback_mode_t mode, b_latency = (right_latency_range.min + right_latency_range.max) / 2; // jack_latency = b_latency; // actually, we are not interested in the latency of the jack // devices connected... + // FIXME: yes, we are. jack_latency = 0; debug(1, "playback latency callback: %" PRIu32 ".", jack_latency); } @@ -368,6 +432,7 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a jack_set_error_function(default_jack_error_callback); jack_set_info_function(default_jack_info_callback); +/* // allocate space for the audio buffer audio_lmb = malloc(buffer_size); if (audio_lmb == NULL) @@ -375,6 +440,7 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a audio_toq = audio_eoq = audio_lmb; audio_umb = audio_lmb + buffer_size; audio_occupancy = 0; // frames +*/ client_is_open = 0; @@ -411,13 +477,16 @@ int jack_delay(long *the_delay) { // but then a transfer could occur and we would get the buffer occupancy after another transfer // had occurred // so we could "lose" a full transfer (e.g. 1024 frames @ 44,100 fps ~ 23.2 milliseconds) + pthread_mutex_lock(&buffer_mutex); - int64_t time_now = get_absolute_time_in_fp(); - int64_t delta = time_now - time_of_latest_transfer; // this is the time back to the last time data - // was transferred into a jack buffer - size_t audio_occupancy_now = audio_occupancy; // this is the buffer occupancy before any - // subsequent transfer because transfer is blocked - // by the mutex + int64_t time_now = get_absolute_time_in_fp(); + // this is the time back to the last time data + // was transferred into a jack buffer + int64_t delta = time_now - time_of_latest_transfer; + // this is the buffer occupancy before any + // subsequent transfer because transfer is blocked + // by the mutex + size_t audio_occupancy_now = jack_ringbuffer_read_space(jackbuf); pthread_mutex_unlock(&buffer_mutex); int64_t frames_processed_since_latest_latency_check = (delta * 44100) >> 32; @@ -427,17 +496,18 @@ int jack_delay(long *the_delay) { base_latency = (latest_right_latency_range.min + latest_right_latency_range.max) / 2; *the_delay = base_latency + audio_occupancy_now - frames_processed_since_latest_latency_check; // debug(1,"reporting a delay of %d frames",*the_delay); - return 0; } void jack_flush() { + debug(1, "It is not possible to safely flush a lock-free ringbuffer. Asking the process callback to do it..."); + flush_please = 1; +/* // debug(1,"jack flush"); - pthread_mutex_lock(&buffer_mutex); audio_toq = audio_eoq = audio_lmb; audio_umb = audio_lmb + buffer_size; audio_occupancy = 0; // frames - pthread_mutex_unlock(&buffer_mutex); +*/ } void jack_stop(void) { From 83c8857cf8a4396ab3dfc4ec58103e0e429236a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Thu, 14 Feb 2019 20:27:49 +0000 Subject: [PATCH 444/569] try a solution for flush(). Compiled but not tested. --- audio_jack.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 1dc471ac..7e950457 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -177,20 +177,23 @@ int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *a int frames_written = 0; int frames_required = 0; - jack_ringbuffer_get_read_vector(jackbuf, v); // an array of two elements because of possible ringbuffer wrap-around - for (i=0; i<2; i++) { - thisbuf = v[i].len / bytes_per_frame; - if (thisbuf > nframes) { - frames_required = nframes; - } else { - frames_required = thisbuf; + if (flush_please) { + jack_ringbuffer_read_advance(jackbuf, jack_ringbuffer_read_space(jackbuf)); + } else { + jack_ringbuffer_get_read_vector(jackbuf, v); // an array of two elements because of possible ringbuffer wrap-around + for (i=0; i<2; i++) { + thisbuf = v[i].len / bytes_per_frame; + if (thisbuf > nframes) { + frames_required = nframes; + } else { + frames_required = thisbuf; + } + deinterleave_and_convert(v[i].buf, &left_buffer[frames_written], &right_buffer[frames_written], frames_required); + frames_written = frames_required; + nframes -= frames_written; } - deinterleave_and_convert(v[i].buf, &left_buffer[frames_written], &right_buffer[frames_written], frames_required); - frames_written = frames_required; - nframes -= frames_written; + jack_ringbuffer_read_advance(jackbuf, frames_written * bytes_per_frame); } - jack_ringbuffer_read_advance(jackbuf, frames_written * bytes_per_frame); - // debug(1,"transferring %u frames",written); // Why are we doing this? The port latency should never change unless the JACK graph is reordered. Should happen on a graph reorder callback only. // jack_port_get_latency_range(left_port, JackPlaybackLatency, &latest_left_latency_range); From 3e6ef0194f1eb20c38fd669ec7dada1ec7a760b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Thu, 14 Feb 2019 20:39:03 +0000 Subject: [PATCH 445/569] Unset flush_please after flushing. Tentatively re-enable latency queries. Same problems... --- audio_jack.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 7e950457..eb00b666 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -179,6 +179,7 @@ int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *a if (flush_please) { jack_ringbuffer_read_advance(jackbuf, jack_ringbuffer_read_space(jackbuf)); + flush_please = 0; } else { jack_ringbuffer_get_read_vector(jackbuf, v); // an array of two elements because of possible ringbuffer wrap-around for (i=0; i<2; i++) { @@ -196,8 +197,8 @@ int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *a } // debug(1,"transferring %u frames",written); // Why are we doing this? The port latency should never change unless the JACK graph is reordered. Should happen on a graph reorder callback only. - // jack_port_get_latency_range(left_port, JackPlaybackLatency, &latest_left_latency_range); - // jack_port_get_latency_range(right_port, JackPlaybackLatency, &latest_right_latency_range); + jack_port_get_latency_range(left_port, JackPlaybackLatency, &latest_left_latency_range); + jack_port_get_latency_range(right_port, JackPlaybackLatency, &latest_right_latency_range); // now, if there are any more frames to put into the buffer, fill them with // silence From a238ac54bb0842eb532a69a06a1f14577a591077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Thu, 14 Feb 2019 20:50:02 +0000 Subject: [PATCH 446/569] audio_occupancy_now in delay() needs to be in frames, not bytes. Still no luck... --- audio_jack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_jack.c b/audio_jack.c index eb00b666..525eb64b 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -490,7 +490,7 @@ int jack_delay(long *the_delay) { // this is the buffer occupancy before any // subsequent transfer because transfer is blocked // by the mutex - size_t audio_occupancy_now = jack_ringbuffer_read_space(jackbuf); + size_t audio_occupancy_now = jack_ringbuffer_read_space(jackbuf) / bytes_per_frame; pthread_mutex_unlock(&buffer_mutex); int64_t frames_processed_since_latest_latency_check = (delta * 44100) >> 32; From 7ab566960c54d94f5af4f588ebdfc352c54e3f06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 15:53:43 +0000 Subject: [PATCH 447/569] move setting of time_of_latest_transfer to play(), we're now looking at ringbuffer writes, not jack output buffers, as those can't be locked to be tested atomically. --- audio_jack.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 525eb64b..4f1edad4 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -100,6 +100,8 @@ int play(void *buf, int samples) { bytes_to_transfer = samples * bytes_per_frame; pthread_mutex_lock(&buffer_mutex); // it's ok to lock here since we're not in the realtime callback bytes_transferred = jack_ringbuffer_write(jackbuf, buf, bytes_to_transfer); + // semantics change: we now measure the last time audio was moved into the ringbuffer, not the jack output buffers. + time_of_latest_transfer = get_absolute_time_in_fp(); pthread_mutex_unlock(&buffer_mutex); if (bytes_transferred < bytes_to_transfer) { debug(1, "JACK ringbuffer overrun. Only wrote %d of %d bytes.", bytes_transferred, bytes_to_transfer); @@ -208,7 +210,6 @@ int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *a frames_written++; nframes--; } - time_of_latest_transfer = get_absolute_time_in_fp(); // this is the only writer, no locking necessary. /* size_t frames_we_can_transfer = nframes; @@ -477,6 +478,10 @@ void jack_start(__attribute__((unused)) int i_sample_rate, int jack_delay(long *the_delay) { + // semantics change: we now look at the last transfer into the lock-free ringbuffer, not + // into the jack buffers directly (because locking those would violate real-time constraints). + // on average, that should lead to just a constant additional latency. the old comment still applies: + // without the mutex, we could get the time of what is the last transfer of data to a jack buffer, // but then a transfer could occur and we would get the buffer occupancy after another transfer // had occurred @@ -504,7 +509,7 @@ int jack_delay(long *the_delay) { } void jack_flush() { - debug(1, "It is not possible to safely flush a lock-free ringbuffer. Asking the process callback to do it..."); + debug(1, "Only the consumer can safely flush a lock-free ringbuffer. Asking the process callback to do it..."); flush_please = 1; /* // debug(1,"jack flush"); From aca15a889b456751bfa24381a3e72dc2e507ac5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 15:56:08 +0000 Subject: [PATCH 448/569] remove comment - the "rolling" status of jack transport is irrelevant to shairport-sync. --- audio_jack.c | 1 - 1 file changed, 1 deletion(-) diff --git a/audio_jack.c b/audio_jack.c index 4f1edad4..7344d86f 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -283,7 +283,6 @@ void default_jack_set_latency_callback(jack_latency_callback_mode_t mode, int jack_is_running() { int reply = -1; // meaning jack is not running - // if the client is open and initialised, see if the status is "rolling" if (client_is_open) { // check if the ports have a zero latency -- if they both have, then it's disconnected. From 8347223986efd8994a8c335ca44045c31ec8459a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 16:08:10 +0000 Subject: [PATCH 449/569] Remove dubious latency check from jack_is_running(). For now, we assume JACK is always running, which makes this function pointless, as it always returns possitive. FIXME: understand why it is needed. --- audio_jack.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 7344d86f..1731ac17 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -287,14 +287,24 @@ int jack_is_running() { // check if the ports have a zero latency -- if they both have, then it's disconnected. - jack_latency_range_t left_latency_range, right_latency_range; - jack_port_get_latency_range(left_port, JackPlaybackLatency, &left_latency_range); - jack_port_get_latency_range(right_port, JackPlaybackLatency, &right_latency_range); + // FIXME: this causes a segfault when shairport-sync is exited with CTRL-C, because + // the client_is_open flag is stale by then. Also, this test is not necessary. + // shairport-sync should not worry what's reading its ports. As long as jack is alive, + // deliver audio, even if nothing is connected. This behaviour probably stems from + // the wish to not hog an audio device if not needed, which is no longer an issue with + // jack. Moreover, don't "conserve" CPU this way, because in a realtime system you want + // deterministic CPU load more than anything else. + // jack_latency_range_t left_latency_range, right_latency_range; + // jack_port_get_latency_range(left_port, JackPlaybackLatency, &left_latency_range); + // jack_port_get_latency_range(right_port, JackPlaybackLatency, &right_latency_range); // if ((left_latency_range.min == 0) && (left_latency_range.max == 0) && // (right_latency_range.min == 0) && (right_latency_range.max == 0)) { // reply = -2; // meaning Shairport Sync is not connected // } else { + + // FIXME: For now, we assume JACK is always running, as it should. + // Still need to understand why shairport-sync needs this function. reply = 0; // meaning jack is open and Shairport Sync is connected to it // } } From 9c57fe43ccdafb7aa3cf2edf14cb16a9c90897c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 16:41:20 +0000 Subject: [PATCH 450/569] Do not re-check JACK graph latency every time delay() is called. Do not register a latency callback with jack. --- audio_jack.c | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 1731ac17..23331e53 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -263,6 +263,7 @@ void default_jack_error_callback(const char *desc) { debug(2, "jackd error: \"%s void default_jack_info_callback(const char *desc) { inform("jackd information: \"%s\"", desc); } +/* void default_jack_set_latency_callback(jack_latency_callback_mode_t mode, __attribute__((unused)) void *arg) { if (mode == JackPlaybackLatency) { @@ -280,6 +281,7 @@ void default_jack_set_latency_callback(jack_latency_callback_mode_t mode, debug(1, "playback latency callback: %" PRIu32 ".", jack_latency); } } +*/ int jack_is_running() { int reply = -1; // meaning jack is not running @@ -325,16 +327,19 @@ int jack_client_open_if_needed(void) { sample_rate = jack_get_sample_rate(client); // debug(1, "jackaudio sample rate = %" PRId32 ".", sample_rate); if (sample_rate == 44100) { - if (jack_set_latency_callback(client, default_jack_set_latency_callback, NULL) == 0) { + // FIXME: shairport-sync has no need for a latency callback, it's a leaf node with only outputs. + // If in the future some jack video player wants to resync to shairplay-sync audio, then yes, but + // the current usage seems to suggest a misunderstanding. +// if (jack_set_latency_callback(client, default_jack_set_latency_callback, NULL) == 0) { if (jack_activate(client)) { debug(1, "jackaudio cannot activate client"); } else { debug(2, "jackaudio client opened."); client_is_open = 1; } - } else { - debug(1, "jackaudio cannot set latency callback"); - } +// } else { +// debug(1, "jackaudio cannot set latency callback"); +// } } else { inform( "jackaudio is running at the wrong speed (%d) for Shairport Sync, which must be 44100", @@ -505,15 +510,22 @@ int jack_delay(long *the_delay) { // subsequent transfer because transfer is blocked // by the mutex size_t audio_occupancy_now = jack_ringbuffer_read_space(jackbuf) / bytes_per_frame; + debug(1, "JN: audio_occupancy_now is %d.", audio_occupancy_now); pthread_mutex_unlock(&buffer_mutex); int64_t frames_processed_since_latest_latency_check = (delta * 44100) >> 32; + + // FIXME: this should only be done if there was an actual change, i.e. on the jack graph reorder + // callback, to update a static variable which can be checked here. For now, use a fixed arbitrary value: + jack_nframes_t base_latency = 0; + // debug(1,"delta: %" PRId64 " frames.",frames_processed_since_latest_latency_check); - jack_nframes_t base_latency = (latest_left_latency_range.min + latest_left_latency_range.max) / 2; - if (base_latency == 0) - base_latency = (latest_right_latency_range.min + latest_right_latency_range.max) / 2; + + // jack_nframes_t base_latency = (latest_left_latency_range.min + latest_left_latency_range.max) / 2; + // if (base_latency == 0) + // base_latency = (latest_right_latency_range.min + latest_right_latency_range.max) / 2; *the_delay = base_latency + audio_occupancy_now - frames_processed_since_latest_latency_check; - // debug(1,"reporting a delay of %d frames",*the_delay); + debug(1,"reporting a delay of %d frames",*the_delay); return 0; } From 6104c580f8ba3db54b025fa27887e7076413765d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 16:51:17 +0000 Subject: [PATCH 451/569] NOP: comment flush implementation --- audio_jack.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/audio_jack.c b/audio_jack.c index 23331e53..919b0d6a 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -180,8 +180,10 @@ int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *a int frames_required = 0; if (flush_please) { + // we just move the read pointer ahead without doing anything with the data. jack_ringbuffer_read_advance(jackbuf, jack_ringbuffer_read_space(jackbuf)); flush_please = 0; + // since we don't change nframes, the whole buffer will be zeroed later. } else { jack_ringbuffer_get_read_vector(jackbuf, v); // an array of two elements because of possible ringbuffer wrap-around for (i=0; i<2; i++) { From 23d4ec2beecfbe218779312f0b8d6d0cff7613b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 17:19:07 +0000 Subject: [PATCH 452/569] Fix logic error in counting the frames written and pending. RESTORES AUDIO! --- audio_jack.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 919b0d6a..544255a8 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -194,8 +194,8 @@ int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *a frames_required = thisbuf; } deinterleave_and_convert(v[i].buf, &left_buffer[frames_written], &right_buffer[frames_written], frames_required); - frames_written = frames_required; - nframes -= frames_written; + frames_written += frames_required; + nframes -= frames_required; } jack_ringbuffer_read_advance(jackbuf, frames_written * bytes_per_frame); } From 444c114231c20758efd289544fc6d58be20bf0ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 17:58:50 +0000 Subject: [PATCH 453/569] NOP: remove commented-out code segments that were re-implemented --- audio_jack.c | 127 --------------------------------------------------- 1 file changed, 127 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 544255a8..3d3fadda 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -106,49 +106,9 @@ int play(void *buf, int samples) { if (bytes_transferred < bytes_to_transfer) { debug(1, "JACK ringbuffer overrun. Only wrote %d of %d bytes.", bytes_transferred, bytes_to_transfer); } -/* - size_t space_to_end_of_buffer = audio_umb - audio_eoq; - if (space_to_end_of_buffer >= bytes_to_transfer) { - memcpy(audio_eoq, buf, bytes_to_transfer); - pthread_mutex_lock(&buffer_mutex); - audio_occupancy += samples; - audio_eoq += bytes_to_transfer; - pthread_mutex_unlock(&buffer_mutex); - } else { - memcpy(audio_eoq, buf, space_to_end_of_buffer); - buf += space_to_end_of_buffer; - memcpy(audio_lmb, buf, bytes_to_transfer - space_to_end_of_buffer); - pthread_mutex_lock(&buffer_mutex); - audio_occupancy += samples; - audio_eoq = audio_lmb + bytes_to_transfer - space_to_end_of_buffer; - pthread_mutex_unlock(&buffer_mutex); - } -*/ return 0; } -void deinterleave_and_convert_stream(const char *interleaved_frames, - const jack_default_audio_sample_t *jack_frame_buffer, - jack_nframes_t number_of_frames, enum ift_type side) { - jack_nframes_t i; - short *ifp = (short *)interleaved_frames; - jack_default_audio_sample_t *fp = (jack_default_audio_sample_t *)jack_frame_buffer; - if (side == IFT_frame_right_sample) - ifp++; - for (i = 0; i < number_of_frames; i++) { - short sample = *ifp; - jack_default_audio_sample_t converted_value; - if (sample >= 0) - converted_value = (1.0 * sample) / SHRT_MAX; - else - converted_value = -(1.0 * sample) / SHRT_MIN; - *fp = converted_value; - ifp++; - ifp++; - fp++; - } -} - static inline jack_default_audio_sample_t sample_conv(short sample) { return ((sample < 0) ? (-1.0 * sample / SHRT_MIN) : (1.0 * sample / SHRT_MAX)); } @@ -212,50 +172,6 @@ int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *a frames_written++; nframes--; } - -/* - size_t frames_we_can_transfer = nframes; - // lock - pthread_mutex_lock(&buffer_mutex); - if (audio_occupancy < frames_we_can_transfer) { - frames_we_can_transfer = audio_occupancy; - // This means we effectively have underflow from the Shairport Sync source. - // In fact, it may be that there is nothing at all coming from the source, - // but the Shairport Sync client is open and active, so it must continue to output something. - } - - if (frames_we_can_transfer * 2 * 2 <= (size_t)(audio_umb - audio_toq)) { - // the bytes are all in a row in the audio buffer - deinterleave_and_convert_stream(audio_toq, &left_buffer[0], frames_we_can_transfer, - IFT_frame_left_sample); - deinterleave_and_convert_stream(audio_toq, &right_buffer[0], frames_we_can_transfer, - IFT_frame_right_sample); - audio_toq += frames_we_can_transfer * 2 * 2; - } else { - // the bytes are in two places in the audio buffer - size_t first_portion_to_write = (audio_umb - audio_toq) / (2 * 2); - if (first_portion_to_write != 0) { - deinterleave_and_convert_stream(audio_toq, &left_buffer[0], first_portion_to_write, - IFT_frame_left_sample); - deinterleave_and_convert_stream(audio_toq, &right_buffer[0], first_portion_to_write, - IFT_frame_right_sample); - } - deinterleave_and_convert_stream(audio_lmb, &left_buffer[first_portion_to_write], - frames_we_can_transfer - first_portion_to_write, - IFT_frame_left_sample); - deinterleave_and_convert_stream(audio_lmb, &right_buffer[first_portion_to_write], - frames_we_can_transfer - first_portion_to_write, - IFT_frame_right_sample); - audio_toq = audio_lmb + (frames_we_can_transfer - first_portion_to_write) * 2 * 2; - } - audio_occupancy -= frames_we_can_transfer; - jack_port_get_latency_range(left_port, JackPlaybackLatency, &latest_left_latency_range); - jack_port_get_latency_range(right_port, JackPlaybackLatency, &latest_right_latency_range); - time_of_latest_transfer = get_absolute_time_in_fp(); - pthread_mutex_unlock(&buffer_mutex); - // unlock - -*/ return 0; } @@ -265,26 +181,6 @@ void default_jack_error_callback(const char *desc) { debug(2, "jackd error: \"%s void default_jack_info_callback(const char *desc) { inform("jackd information: \"%s\"", desc); } -/* -void default_jack_set_latency_callback(jack_latency_callback_mode_t mode, - __attribute__((unused)) void *arg) { - if (mode == JackPlaybackLatency) { - jack_latency_range_t left_latency_range, right_latency_range; - jack_port_get_latency_range(left_port, JackPlaybackLatency, &left_latency_range); - jack_port_get_latency_range(right_port, JackPlaybackLatency, &right_latency_range); - - jack_nframes_t b_latency = (left_latency_range.min + left_latency_range.max) / 2; - if (b_latency == 0) - b_latency = (right_latency_range.min + right_latency_range.max) / 2; - // jack_latency = b_latency; // actually, we are not interested in the latency of the jack - // devices connected... - // FIXME: yes, we are. - jack_latency = 0; - debug(1, "playback latency callback: %" PRIu32 ".", jack_latency); - } -} -*/ - int jack_is_running() { int reply = -1; // meaning jack is not running if (client_is_open) { @@ -329,19 +225,12 @@ int jack_client_open_if_needed(void) { sample_rate = jack_get_sample_rate(client); // debug(1, "jackaudio sample rate = %" PRId32 ".", sample_rate); if (sample_rate == 44100) { - // FIXME: shairport-sync has no need for a latency callback, it's a leaf node with only outputs. - // If in the future some jack video player wants to resync to shairplay-sync audio, then yes, but - // the current usage seems to suggest a misunderstanding. -// if (jack_set_latency_callback(client, default_jack_set_latency_callback, NULL) == 0) { if (jack_activate(client)) { debug(1, "jackaudio cannot activate client"); } else { debug(2, "jackaudio client opened."); client_is_open = 1; } -// } else { -// debug(1, "jackaudio cannot set latency callback"); -// } } else { inform( "jackaudio is running at the wrong speed (%d) for Shairport Sync, which must be 44100", @@ -453,16 +342,6 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a jack_set_error_function(default_jack_error_callback); jack_set_info_function(default_jack_info_callback); -/* - // allocate space for the audio buffer - audio_lmb = malloc(buffer_size); - if (audio_lmb == NULL) - die("Can't allocate %d bytes for jackaudio buffer.", buffer_size); - audio_toq = audio_eoq = audio_lmb; - audio_umb = audio_lmb + buffer_size; - audio_occupancy = 0; // frames -*/ - client_is_open = 0; // now, if selected, start a thread to automatically open a client when there is a server. @@ -534,12 +413,6 @@ int jack_delay(long *the_delay) { void jack_flush() { debug(1, "Only the consumer can safely flush a lock-free ringbuffer. Asking the process callback to do it..."); flush_please = 1; -/* - // debug(1,"jack flush"); - audio_toq = audio_eoq = audio_lmb; - audio_umb = audio_lmb + buffer_size; - audio_occupancy = 0; // frames -*/ } void jack_stop(void) { From 120e95fc662cb316a8c5853c45f18e6ae2a4733e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 18:06:36 +0000 Subject: [PATCH 454/569] Add parentheses around macro to avoid breakage with stronger-binding operators. --- audio_jack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_jack.c b/audio_jack.c index 3d3fadda..667ace96 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -41,7 +41,7 @@ enum ift_type { // Two-channel, 16bit audio: static const int bytes_per_frame = 4; // Four seconds buffer -- should be plenty -#define buffer_size 44100 * 4 * bytes_per_frame +#define buffer_size (44100 * 4 * bytes_per_frame) static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER; From aefd7cdb3314fc07f3296163192ee2a17d6a244e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 18:09:36 +0000 Subject: [PATCH 455/569] remove more dead code and obsolete comments --- audio_jack.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 667ace96..5f1a4adc 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -46,12 +46,6 @@ static const int bytes_per_frame = 4; static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER; -/* -char *audio_lmb, *audio_umb, *audio_toq, *audio_eoq; - -size_t audio_occupancy; // this is in frames, not bytes. A frame is a left and - // right sample, each 16 bits, hence 4 bytes -*/ pthread_t *open_client_if_necessary_thread = NULL; int jack_init(int, char **); @@ -334,7 +328,7 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a if (config.jack_right_channel_name == NULL) config.jack_right_channel_name = strdup("right"); - jackbuf = jack_ringbuffer_create(buffer_size); // make the jack ringbuffer the same size as audio_lmb + jackbuf = jack_ringbuffer_create(buffer_size); if (jackbuf == NULL) die("Can't allocate %d bytes for the JACK ringbuffer.", buffer_size); jack_ringbuffer_mlock(jackbuf); // lock buffer into memory so that it never gets paged out From ec13a2bb3a7fbb0b272bcdd7ae4da79195605f8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 18:19:44 +0000 Subject: [PATCH 456/569] Do not query the JACK port latency on every process() call. It can only ever change when the graph is reordered. --- audio_jack.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 5f1a4adc..c0c4fab1 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -153,11 +153,6 @@ int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *a } jack_ringbuffer_read_advance(jackbuf, frames_written * bytes_per_frame); } - // debug(1,"transferring %u frames",written); - // Why are we doing this? The port latency should never change unless the JACK graph is reordered. Should happen on a graph reorder callback only. - jack_port_get_latency_range(left_port, JackPlaybackLatency, &latest_left_latency_range); - jack_port_get_latency_range(right_port, JackPlaybackLatency, &latest_right_latency_range); - // now, if there are any more frames to put into the buffer, fill them with // silence while (nframes > 0) { From a3201ff290373a1720fa1ef30122e05d9b140cf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 18:22:33 +0000 Subject: [PATCH 457/569] Declare deinterleaving function static, it's nobody else's business. --- audio_jack.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index c0c4fab1..5cde2213 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -107,10 +107,10 @@ static inline jack_default_audio_sample_t sample_conv(short sample) { return ((sample < 0) ? (-1.0 * sample / SHRT_MIN) : (1.0 * sample / SHRT_MAX)); } -void deinterleave_and_convert(const char *interleaved_frames, - jack_default_audio_sample_t * const jack_buffer_left, - jack_default_audio_sample_t * const jack_buffer_right, - jack_nframes_t nframes) { +static void deinterleave_and_convert(const char *interleaved_frames, + jack_default_audio_sample_t * const jack_buffer_left, + jack_default_audio_sample_t * const jack_buffer_right, + jack_nframes_t nframes) { jack_nframes_t i; short *ifp = (short *)interleaved_frames; // we're dealing with 16bit audio here jack_default_audio_sample_t *fpl = jack_buffer_left; From d7c7365bb11c4d8e47f4244986d46ae9b603c87a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 18:31:23 +0000 Subject: [PATCH 458/569] comment out some debug statements not relevant to users --- audio_jack.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 5cde2213..f5aa7cea 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -380,7 +380,7 @@ int jack_delay(long *the_delay) { // subsequent transfer because transfer is blocked // by the mutex size_t audio_occupancy_now = jack_ringbuffer_read_space(jackbuf) / bytes_per_frame; - debug(1, "JN: audio_occupancy_now is %d.", audio_occupancy_now); + // debug(1, "audio_occupancy_now is %d.", audio_occupancy_now); pthread_mutex_unlock(&buffer_mutex); int64_t frames_processed_since_latest_latency_check = (delta * 44100) >> 32; @@ -395,12 +395,12 @@ int jack_delay(long *the_delay) { // if (base_latency == 0) // base_latency = (latest_right_latency_range.min + latest_right_latency_range.max) / 2; *the_delay = base_latency + audio_occupancy_now - frames_processed_since_latest_latency_check; - debug(1,"reporting a delay of %d frames",*the_delay); + // debug(1,"reporting a delay of %d frames",*the_delay); return 0; } void jack_flush() { - debug(1, "Only the consumer can safely flush a lock-free ringbuffer. Asking the process callback to do it..."); + // debug(1, "Only the consumer can safely flush a lock-free ringbuffer. Asking the process callback to do it..."); flush_please = 1; } From 9520e891836eaeaed41127a1601980e4a72a0d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 18:39:20 +0000 Subject: [PATCH 459/569] Declare all functions static that are not callbacks and not in audio_jack API struct. If there are any calls from the outside I haven't thought of, that should make them fail. --- audio_jack.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index f5aa7cea..7bd513b0 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -200,7 +200,7 @@ int jack_is_running() { return reply; } -int jack_client_open_if_needed(void) { +static int jack_client_open_if_needed(void) { pthread_mutex_lock(&client_mutex); if (client_is_open == 0) { jack_status_t status; @@ -231,7 +231,7 @@ int jack_client_open_if_needed(void) { return client_is_open; } -void jack_close(void) { +static void jack_close(void) { pthread_mutex_lock(&client_mutex); if (client_is_open) { if (jack_deactivate(client)) @@ -254,7 +254,7 @@ void jack_deinit() { } } -void *open_client_if_necessary_thread_function(void *arg) { +static void *open_client_if_necessary_thread_function(void *arg) { int *interval = (int *)arg; while (*interval != 0) { if (client_is_open == 0) { From 91a8553a02cf0451ba1b9884660a4691b7bba2a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 18:43:51 +0000 Subject: [PATCH 460/569] Declare all JACK callbacks static (they are accessed via function pointers only). --- audio_jack.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 7bd513b0..680ce8cb 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -121,7 +121,7 @@ static void deinterleave_and_convert(const char *interleaved_frames, } } -int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *arg) { +static int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *arg) { jack_default_audio_sample_t *left_buffer = (jack_default_audio_sample_t *)jack_port_get_buffer(left_port, nframes); @@ -166,9 +166,9 @@ int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *a // FIXME: set_graph_order_callback(), recompute latencies here! -void default_jack_error_callback(const char *desc) { debug(2, "jackd error: \"%s\"", desc); } +static void default_jack_error_callback(const char *desc) { debug(2, "jackd error: \"%s\"", desc); } -void default_jack_info_callback(const char *desc) { inform("jackd information: \"%s\"", desc); } +static void default_jack_info_callback(const char *desc) { inform("jackd information: \"%s\"", desc); } int jack_is_running() { int reply = -1; // meaning jack is not running From 89a8add7af36d5525dbaf1eebbc38e715431952b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 18:57:15 +0000 Subject: [PATCH 461/569] NOP: move external API functions to the end, ordered by struct audio_output --- audio_jack.c | 130 ++++++++++++++++++++++++++------------------------- 1 file changed, 66 insertions(+), 64 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 680ce8cb..e4d97439 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -87,21 +87,6 @@ static int flush_please = 0; jack_latency_range_t latest_left_latency_range, latest_right_latency_range; int64_t time_of_latest_transfer; -int play(void *buf, int samples) { - // debug(1,"jack_play of %d samples.",samples); - // copy the samples into the queue - size_t bytes_to_transfer, bytes_transferred; - bytes_to_transfer = samples * bytes_per_frame; - pthread_mutex_lock(&buffer_mutex); // it's ok to lock here since we're not in the realtime callback - bytes_transferred = jack_ringbuffer_write(jackbuf, buf, bytes_to_transfer); - // semantics change: we now measure the last time audio was moved into the ringbuffer, not the jack output buffers. - time_of_latest_transfer = get_absolute_time_in_fp(); - pthread_mutex_unlock(&buffer_mutex); - if (bytes_transferred < bytes_to_transfer) { - debug(1, "JACK ringbuffer overrun. Only wrote %d of %d bytes.", bytes_transferred, bytes_to_transfer); - } - return 0; -} static inline jack_default_audio_sample_t sample_conv(short sample) { return ((sample < 0) ? (-1.0 * sample / SHRT_MIN) : (1.0 * sample / SHRT_MAX)); @@ -170,35 +155,6 @@ static void default_jack_error_callback(const char *desc) { debug(2, "jackd erro static void default_jack_info_callback(const char *desc) { inform("jackd information: \"%s\"", desc); } -int jack_is_running() { - int reply = -1; // meaning jack is not running - if (client_is_open) { - - // check if the ports have a zero latency -- if they both have, then it's disconnected. - - // FIXME: this causes a segfault when shairport-sync is exited with CTRL-C, because - // the client_is_open flag is stale by then. Also, this test is not necessary. - // shairport-sync should not worry what's reading its ports. As long as jack is alive, - // deliver audio, even if nothing is connected. This behaviour probably stems from - // the wish to not hog an audio device if not needed, which is no longer an issue with - // jack. Moreover, don't "conserve" CPU this way, because in a realtime system you want - // deterministic CPU load more than anything else. - // jack_latency_range_t left_latency_range, right_latency_range; - // jack_port_get_latency_range(left_port, JackPlaybackLatency, &left_latency_range); - // jack_port_get_latency_range(right_port, JackPlaybackLatency, &right_latency_range); - - // if ((left_latency_range.min == 0) && (left_latency_range.max == 0) && - // (right_latency_range.min == 0) && (right_latency_range.max == 0)) { - // reply = -2; // meaning Shairport Sync is not connected - // } else { - - // FIXME: For now, we assume JACK is always running, as it should. - // Still need to understand why shairport-sync needs this function. - reply = 0; // meaning jack is open and Shairport Sync is connected to it - // } - } - return reply; -} static int jack_client_open_if_needed(void) { pthread_mutex_lock(&client_mutex); @@ -243,17 +199,6 @@ static void jack_close(void) { pthread_mutex_unlock(&client_mutex); } -void jack_deinit() { - jack_close(); - if (open_client_if_necessary_thread) { - pthread_cancel(*open_client_if_necessary_thread); - free((char *)open_client_if_necessary_thread); - - jack_ringbuffer_free(jackbuf); - - } -} - static void *open_client_if_necessary_thread_function(void *arg) { int *interval = (int *)arg; while (*interval != 0) { @@ -351,6 +296,17 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a return 0; } +void jack_deinit() { + jack_close(); + if (open_client_if_necessary_thread) { + pthread_cancel(*open_client_if_necessary_thread); + free((char *)open_client_if_necessary_thread); + + jack_ringbuffer_free(jackbuf); + + } +} + void jack_start(__attribute__((unused)) int i_sample_rate, __attribute__((unused)) int i_sample_format) { // debug(1, "jack start"); @@ -360,6 +316,47 @@ void jack_start(__attribute__((unused)) int i_sample_rate, debug(1, "cannot open a jack client for a play session"); } +void jack_stop(void) { + // debug(1, "jack stop"); + if (config.jack_auto_client_disconnect) + jack_close(); +} + +int jack_is_running() { + int reply = -1; // meaning jack is not running + if (client_is_open) { + + // check if the ports have a zero latency -- if they both have, then it's disconnected. + + // FIXME: this causes a segfault when shairport-sync is exited with CTRL-C, because + // the client_is_open flag is stale by then. Also, this test is not necessary. + // shairport-sync should not worry what's reading its ports. As long as jack is alive, + // deliver audio, even if nothing is connected. This behaviour probably stems from + // the wish to not hog an audio device if not needed, which is no longer an issue with + // jack. Moreover, don't "conserve" CPU this way, because in a realtime system you want + // deterministic CPU load more than anything else. + // jack_latency_range_t left_latency_range, right_latency_range; + // jack_port_get_latency_range(left_port, JackPlaybackLatency, &left_latency_range); + // jack_port_get_latency_range(right_port, JackPlaybackLatency, &right_latency_range); + + // if ((left_latency_range.min == 0) && (left_latency_range.max == 0) && + // (right_latency_range.min == 0) && (right_latency_range.max == 0)) { + // reply = -2; // meaning Shairport Sync is not connected + // } else { + + // FIXME: For now, we assume JACK is always running, as it should. + // Still need to understand why shairport-sync needs this function. + reply = 0; // meaning jack is open and Shairport Sync is connected to it + // } + } + return reply; +} + +void jack_flush() { + // debug(1, "Only the consumer can safely flush a lock-free ringbuffer. Asking the process callback to do it..."); + flush_please = 1; +} + int jack_delay(long *the_delay) { // semantics change: we now look at the last transfer into the lock-free ringbuffer, not @@ -399,13 +396,18 @@ int jack_delay(long *the_delay) { return 0; } -void jack_flush() { - // debug(1, "Only the consumer can safely flush a lock-free ringbuffer. Asking the process callback to do it..."); - flush_please = 1; -} - -void jack_stop(void) { - // debug(1, "jack stop"); - if (config.jack_auto_client_disconnect) - jack_close(); +int play(void *buf, int samples) { + // debug(1,"jack_play of %d samples.",samples); + // copy the samples into the queue + size_t bytes_to_transfer, bytes_transferred; + bytes_to_transfer = samples * bytes_per_frame; + pthread_mutex_lock(&buffer_mutex); // it's ok to lock here since we're not in the realtime callback + bytes_transferred = jack_ringbuffer_write(jackbuf, buf, bytes_to_transfer); + // semantics change: we now measure the last time audio was moved into the ringbuffer, not the jack output buffers. + time_of_latest_transfer = get_absolute_time_in_fp(); + pthread_mutex_unlock(&buffer_mutex); + if (bytes_transferred < bytes_to_transfer) { + debug(1, "JACK ringbuffer overrun. Only wrote %d of %d bytes.", bytes_transferred, bytes_to_transfer); + } + return 0; } From 0b589cbe6d13d43faa252d70156c7a2e1323734c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 19:29:24 +0000 Subject: [PATCH 462/569] Remove option to configure port names. This is very unusual for a JACK client, nothing is gained by offering this, and it might confuse users. --- audio_jack.c | 16 ++-------------- common.h | 2 +- scripts/shairport-sync.conf | 2 -- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index e4d97439..15f1f49d 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -163,9 +163,9 @@ static int jack_client_open_if_needed(void) { client = jack_client_open(config.jack_client_name, JackNoStartServer, &status); if (client) { jack_set_process_callback(client, jack_stream_write_cb, 0); - left_port = jack_port_register(client, config.jack_left_channel_name, JACK_DEFAULT_AUDIO_TYPE, + left_port = jack_port_register(client, "out_L", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); - right_port = jack_port_register(client, config.jack_right_channel_name, + right_port = jack_port_register(client, "out_R", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); sample_rate = jack_get_sample_rate(client); // debug(1, "jackaudio sample rate = %" PRId32 ".", sample_rate); @@ -235,14 +235,6 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a if (config_lookup_string(config.cfg, "jack.client_name", &str)) { config.jack_client_name = (char *)str; } - /* Get the Left Channel Name. */ - if (config_lookup_string(config.cfg, "jack.left_channel_name", &str)) { - config.jack_left_channel_name = (char *)str; - } - /* Get the Right Channel Name. */ - if (config_lookup_string(config.cfg, "jack.right_channel_name", &str)) { - config.jack_right_channel_name = (char *)str; - } /* See if we should attempt to connect to the jack server automatically, and, if so, how often * we should try. */ @@ -263,10 +255,6 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a if (config.jack_client_name == NULL) config.jack_client_name = strdup("Shairport Sync"); - if (config.jack_left_channel_name == NULL) - config.jack_left_channel_name = strdup("left"); - if (config.jack_right_channel_name == NULL) - config.jack_right_channel_name = strdup("right"); jackbuf = jack_ringbuffer_create(buffer_size); if (jackbuf == NULL) diff --git a/common.h b/common.h index 78c9ddda..a43c2b85 100644 --- a/common.h +++ b/common.h @@ -232,7 +232,7 @@ typedef struct { double diagnostic_drop_packet_fraction; // pseudo randomly drop this fraction of packets, for // debugging. Currently audio packets only... #ifdef CONFIG_JACK - char *jack_client_name, *jack_left_channel_name, *jack_right_channel_name; + char *jack_client_name; int jack_auto_client_open_interval; // will try to open a client automatically every second int jack_auto_client_disconnect; // will disconnect from the server on end of session if set, // normally clear. diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index bf6ce4ba..cb927380 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -109,8 +109,6 @@ pa = jack = { // client_name = "Shairport Sync"; //Set this to the name of the client that should appear in "Connections" when Shairport Sync is active. -// left_channel_name = "left"; //Set this to the name of the left output port that should appear in "Connections" when Shairport Sync is active. -// right_channel_name = "right"; //Set this to the name of the right output port that should appear in "Connections" when Shairport Sync is active. // auto_client_open_interval = 1; // Set this to the interval in seconds between automatic attempts to open a client on the jack server. Set to zero to disable this behaviour. // auto_client_disconnect = "no"; // Set this to "yes" to close the jack client automatically at then end of a play session. Default is no -- the client stays connected. }; From adefcc698ccdd0164f834b233135b07ccb6f46c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 19:34:23 +0000 Subject: [PATCH 463/569] remove unused globals --- audio_jack.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 15f1f49d..d825cacc 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -33,11 +33,6 @@ #include #include -enum ift_type { - IFT_frame_left_sample = 0, - IFT_frame_right_sample, -} ift_type; - // Two-channel, 16bit audio: static const int bytes_per_frame = 4; // Four seconds buffer -- should be plenty @@ -74,8 +69,6 @@ audio_output audio_jack = {.name = "jack", jack_port_t *left_port; jack_port_t *right_port; -long offset = 0; - int client_is_open; jack_client_t *client; jack_nframes_t sample_rate; From 63b3381a27906859e7a2932e6bebe3979e7e075d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 22:59:31 +0000 Subject: [PATCH 464/569] Fail early and fail deadly (AFAIK there is no fallback to another audio backend, so any severe JACK issue should be fatal). --- audio_jack.c | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index d825cacc..20e3b37f 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -148,32 +148,29 @@ static void default_jack_error_callback(const char *desc) { debug(2, "jackd erro static void default_jack_info_callback(const char *desc) { inform("jackd information: \"%s\"", desc); } - static int jack_client_open_if_needed(void) { pthread_mutex_lock(&client_mutex); if (client_is_open == 0) { jack_status_t status; client = jack_client_open(config.jack_client_name, JackNoStartServer, &status); - if (client) { - jack_set_process_callback(client, jack_stream_write_cb, 0); - left_port = jack_port_register(client, "out_L", JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, 0); - right_port = jack_port_register(client, "out_R", - JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); - sample_rate = jack_get_sample_rate(client); - // debug(1, "jackaudio sample rate = %" PRId32 ".", sample_rate); - if (sample_rate == 44100) { - if (jack_activate(client)) { - debug(1, "jackaudio cannot activate client"); - } else { - debug(2, "jackaudio client opened."); - client_is_open = 1; - } - } else { - inform( - "jackaudio is running at the wrong speed (%d) for Shairport Sync, which must be 44100", - sample_rate); - } + if (!client) { + die("Could not start JACK server. JackStatus is %x", status); + } + sample_rate = jack_get_sample_rate(client); + if (sample_rate != 44100) { + die("The JACK server is running at the wrong sample rate (%d) for Shairport Sync. Must be 44100 Hz.", + sample_rate); + } + jack_set_process_callback(client, jack_stream_write_cb, 0); + left_port = jack_port_register(client, "out_L", JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + right_port = jack_port_register(client, "out_R", + JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + if (jack_activate(client)) { + die("Could not activate %s JACK client.", config.jack_client_name); + } else { + debug(2, "JACK client %s activated sucessfully.", config.jack_client_name); + client_is_open = 1; } } pthread_mutex_unlock(&client_mutex); From edd699dfbce4e82d84b2891a28f7e1af034acd2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 23:08:04 +0000 Subject: [PATCH 465/569] Remove auto_client_open_interval option, always open JACK connection immediately or fail. Remove client creation thread and wrapper function. --- audio_jack.c | 49 ++----------------------------------- common.h | 1 - scripts/shairport-sync.conf | 1 - 3 files changed, 2 insertions(+), 49 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 20e3b37f..4071c39e 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -189,25 +189,12 @@ static void jack_close(void) { pthread_mutex_unlock(&client_mutex); } -static void *open_client_if_necessary_thread_function(void *arg) { - int *interval = (int *)arg; - while (*interval != 0) { - if (client_is_open == 0) { - debug(1, "Try to open the jack client"); - jack_client_open_if_needed(); - } - sleep(*interval); - } - pthread_exit(NULL); -} - int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) { config.audio_backend_latency_offset = 0; config.audio_backend_buffer_desired_length = 0.500; config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.25; // below this, soxr interpolation will not occur -- it'll be basic interpolation // instead. - config.jack_auto_client_open_interval = 1; // check every second // get settings from settings file first, allow them to be overridden by // command line options @@ -220,24 +207,11 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a // now the specific options if (config.cfg != NULL) { const char *str; - int value; /* Get the Client Name. */ if (config_lookup_string(config.cfg, "jack.client_name", &str)) { config.jack_client_name = (char *)str; } - /* See if we should attempt to connect to the jack server automatically, and, if so, how often - * we should try. */ - if (config_lookup_int(config.cfg, "jack.auto_client_open_interval", &value)) { - if ((value < 0) || (value > 300)) - debug(1, - "Invalid jack auto_client_open_interval \"%sd\". It should be between 0 and 300, " - "default is %d.", - value, config.jack_auto_client_open_interval); - else - config.jack_auto_client_open_interval = value; - } - /* See if we should close the client at then end of a play session. */ config_set_lookup_bool(config.cfg, "jack.auto_client_disconnect", &config.jack_auto_client_disconnect); @@ -256,33 +230,14 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a client_is_open = 0; - // now, if selected, start a thread to automatically open a client when there is a server. - if (config.jack_auto_client_open_interval != 0) { - open_client_if_necessary_thread = malloc(sizeof(pthread_t)); - if (open_client_if_necessary_thread == NULL) { - debug(1, "Couldn't allocate space for jack server scanner thread"); - jack_client_open_if_needed(); - } else { - pthread_create(open_client_if_necessary_thread, NULL, - open_client_if_necessary_thread_function, - &config.jack_auto_client_open_interval); - } - } else { - jack_client_open_if_needed(); - } + jack_client_open_if_needed(); return 0; } void jack_deinit() { jack_close(); - if (open_client_if_necessary_thread) { - pthread_cancel(*open_client_if_necessary_thread); - free((char *)open_client_if_necessary_thread); - - jack_ringbuffer_free(jackbuf); - - } + jack_ringbuffer_free(jackbuf); } void jack_start(__attribute__((unused)) int i_sample_rate, diff --git a/common.h b/common.h index a43c2b85..b91b3a00 100644 --- a/common.h +++ b/common.h @@ -233,7 +233,6 @@ typedef struct { // debugging. Currently audio packets only... #ifdef CONFIG_JACK char *jack_client_name; - int jack_auto_client_open_interval; // will try to open a client automatically every second int jack_auto_client_disconnect; // will disconnect from the server on end of session if set, // normally clear. #endif diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index cb927380..cfddd414 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -109,7 +109,6 @@ pa = jack = { // client_name = "Shairport Sync"; //Set this to the name of the client that should appear in "Connections" when Shairport Sync is active. -// auto_client_open_interval = 1; // Set this to the interval in seconds between automatic attempts to open a client on the jack server. Set to zero to disable this behaviour. // auto_client_disconnect = "no"; // Set this to "yes" to close the jack client automatically at then end of a play session. Default is no -- the client stays connected. }; From 8c5e85c87c2c65e5c1305cd22388253d51d04508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 23:14:31 +0000 Subject: [PATCH 466/569] Remove audio_client_disconnect option. This is not good behaviour for a JACK client, and adds nothing. --- audio_jack.c | 6 ------ common.h | 2 -- scripts/shairport-sync.conf | 1 - 3 files changed, 9 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 4071c39e..e9730184 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -211,10 +211,6 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a if (config_lookup_string(config.cfg, "jack.client_name", &str)) { config.jack_client_name = (char *)str; } - - /* See if we should close the client at then end of a play session. */ - config_set_lookup_bool(config.cfg, "jack.auto_client_disconnect", - &config.jack_auto_client_disconnect); } if (config.jack_client_name == NULL) @@ -251,8 +247,6 @@ void jack_start(__attribute__((unused)) int i_sample_rate, void jack_stop(void) { // debug(1, "jack stop"); - if (config.jack_auto_client_disconnect) - jack_close(); } int jack_is_running() { diff --git a/common.h b/common.h index b91b3a00..9a824cbf 100644 --- a/common.h +++ b/common.h @@ -233,8 +233,6 @@ typedef struct { // debugging. Currently audio packets only... #ifdef CONFIG_JACK char *jack_client_name; - int jack_auto_client_disconnect; // will disconnect from the server on end of session if set, - // normally clear. #endif } shairport_cfg; diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index cfddd414..c06c32e7 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -109,7 +109,6 @@ pa = jack = { // client_name = "Shairport Sync"; //Set this to the name of the client that should appear in "Connections" when Shairport Sync is active. -// auto_client_disconnect = "no"; // Set this to "yes" to close the jack client automatically at then end of a play session. Default is no -- the client stays connected. }; // Parameters for the "pipe" audio back end, a back end that directs raw CD-style audio output to a pipe. No interpolation is done. From ec68c677e0bd80bd29058b4c01462611f8e10011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 23:17:50 +0000 Subject: [PATCH 467/569] Remove jack_stop() function pointer from API struct, since it does nothing. Checked, no segfault on program termination. --- audio_jack.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index e9730184..0221d043 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -57,7 +57,7 @@ audio_output audio_jack = {.name = "jack", .init = &jack_init, .deinit = &jack_deinit, .start = &jack_start, - .stop = &jack_stop, + .stop = NULL, .is_running = &jack_is_running, .flush = &jack_flush, .delay = &jack_delay, @@ -245,10 +245,6 @@ void jack_start(__attribute__((unused)) int i_sample_rate, debug(1, "cannot open a jack client for a play session"); } -void jack_stop(void) { - // debug(1, "jack stop"); -} - int jack_is_running() { int reply = -1; // meaning jack is not running if (client_is_open) { From 1906bfd727c2348aba71e1e8b7c22cd4264b71f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 23:38:01 +0000 Subject: [PATCH 468/569] Remove indirections open_if_needed() and close(), can now be integrated into API calls init and deinit. --- audio_jack.c | 83 ++++++++++++++++++++++------------------------------ 1 file changed, 35 insertions(+), 48 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 0221d043..b67e16ca 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -148,47 +148,6 @@ static void default_jack_error_callback(const char *desc) { debug(2, "jackd erro static void default_jack_info_callback(const char *desc) { inform("jackd information: \"%s\"", desc); } -static int jack_client_open_if_needed(void) { - pthread_mutex_lock(&client_mutex); - if (client_is_open == 0) { - jack_status_t status; - client = jack_client_open(config.jack_client_name, JackNoStartServer, &status); - if (!client) { - die("Could not start JACK server. JackStatus is %x", status); - } - sample_rate = jack_get_sample_rate(client); - if (sample_rate != 44100) { - die("The JACK server is running at the wrong sample rate (%d) for Shairport Sync. Must be 44100 Hz.", - sample_rate); - } - jack_set_process_callback(client, jack_stream_write_cb, 0); - left_port = jack_port_register(client, "out_L", JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, 0); - right_port = jack_port_register(client, "out_R", - JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); - if (jack_activate(client)) { - die("Could not activate %s JACK client.", config.jack_client_name); - } else { - debug(2, "JACK client %s activated sucessfully.", config.jack_client_name); - client_is_open = 1; - } - } - pthread_mutex_unlock(&client_mutex); - return client_is_open; -} - -static void jack_close(void) { - pthread_mutex_lock(&client_mutex); - if (client_is_open) { - if (jack_deactivate(client)) - debug(1, "Error deactivating jack client"); - if (jack_client_close(client)) - debug(1, "Error closing jack client"); - client_is_open = 0; - } - pthread_mutex_unlock(&client_mutex); -} - int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) { config.audio_backend_latency_offset = 0; config.audio_backend_buffer_desired_length = 0.500; @@ -226,23 +185,51 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a client_is_open = 0; - jack_client_open_if_needed(); + pthread_mutex_lock(&client_mutex); + if (client_is_open == 0) { + jack_status_t status; + client = jack_client_open(config.jack_client_name, JackNoStartServer, &status); + if (!client) { + die("Could not start JACK server. JackStatus is %x", status); + } + sample_rate = jack_get_sample_rate(client); + if (sample_rate != 44100) { + die("The JACK server is running at the wrong sample rate (%d) for Shairport Sync. Must be 44100 Hz.", + sample_rate); + } + jack_set_process_callback(client, jack_stream_write_cb, 0); + left_port = jack_port_register(client, "out_L", JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + right_port = jack_port_register(client, "out_R", + JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + if (jack_activate(client)) { + die("Could not activate %s JACK client.", config.jack_client_name); + } else { + debug(2, "JACK client %s activated sucessfully.", config.jack_client_name); + client_is_open = 1; + } + } + pthread_mutex_unlock(&client_mutex); return 0; } void jack_deinit() { - jack_close(); + pthread_mutex_lock(&client_mutex); + if (client_is_open) { + if (jack_deactivate(client)) + debug(1, "Error deactivating jack client"); + if (jack_client_close(client)) + debug(1, "Error closing jack client"); + client_is_open = 0; + } + pthread_mutex_unlock(&client_mutex); jack_ringbuffer_free(jackbuf); } void jack_start(__attribute__((unused)) int i_sample_rate, __attribute__((unused)) int i_sample_format) { - // debug(1, "jack start"); - // see if the client is running. If not, try to open and initialise it - - if (jack_client_open_if_needed() == 0) - debug(1, "cannot open a jack client for a play session"); + // nothing to do, JACK client has already been set up at jack_init() } int jack_is_running() { From 753cc07777abed3f7953720b55458b7e80bf3fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 23:39:53 +0000 Subject: [PATCH 469/569] Remove optional is_running() API call, since it doesn't do anything useful. We are either already running along just fine, or we are throughly dead. --- audio_jack.c | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index b67e16ca..c04853f0 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -58,7 +58,7 @@ audio_output audio_jack = {.name = "jack", .deinit = &jack_deinit, .start = &jack_start, .stop = NULL, - .is_running = &jack_is_running, + .is_running = NULL, .flush = &jack_flush, .delay = &jack_delay, .play = &play, @@ -232,36 +232,6 @@ void jack_start(__attribute__((unused)) int i_sample_rate, // nothing to do, JACK client has already been set up at jack_init() } -int jack_is_running() { - int reply = -1; // meaning jack is not running - if (client_is_open) { - - // check if the ports have a zero latency -- if they both have, then it's disconnected. - - // FIXME: this causes a segfault when shairport-sync is exited with CTRL-C, because - // the client_is_open flag is stale by then. Also, this test is not necessary. - // shairport-sync should not worry what's reading its ports. As long as jack is alive, - // deliver audio, even if nothing is connected. This behaviour probably stems from - // the wish to not hog an audio device if not needed, which is no longer an issue with - // jack. Moreover, don't "conserve" CPU this way, because in a realtime system you want - // deterministic CPU load more than anything else. - // jack_latency_range_t left_latency_range, right_latency_range; - // jack_port_get_latency_range(left_port, JackPlaybackLatency, &left_latency_range); - // jack_port_get_latency_range(right_port, JackPlaybackLatency, &right_latency_range); - - // if ((left_latency_range.min == 0) && (left_latency_range.max == 0) && - // (right_latency_range.min == 0) && (right_latency_range.max == 0)) { - // reply = -2; // meaning Shairport Sync is not connected - // } else { - - // FIXME: For now, we assume JACK is always running, as it should. - // Still need to understand why shairport-sync needs this function. - reply = 0; // meaning jack is open and Shairport Sync is connected to it - // } - } - return reply; -} - void jack_flush() { // debug(1, "Only the consumer can safely flush a lock-free ringbuffer. Asking the process callback to do it..."); flush_please = 1; From 2826756938aeccbdff8cb602c8383d022604eaa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 23:43:00 +0000 Subject: [PATCH 470/569] Remove global client_is_open flag, always execute former conditional statements. We know we're open. --- audio_jack.c | 56 +++++++++++++++++++++------------------------------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index c04853f0..f6f8b799 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -48,7 +48,6 @@ void jack_deinit(void); void jack_start(int, int); int play(void *, int); void jack_stop(void); -int jack_is_running(void); int jack_delay(long *); void jack_flush(void); @@ -69,7 +68,6 @@ audio_output audio_jack = {.name = "jack", jack_port_t *left_port; jack_port_t *right_port; -int client_is_open; jack_client_t *client; jack_nframes_t sample_rate; jack_nframes_t jack_latency; @@ -183,31 +181,26 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a jack_set_error_function(default_jack_error_callback); jack_set_info_function(default_jack_info_callback); - client_is_open = 0; - pthread_mutex_lock(&client_mutex); - if (client_is_open == 0) { - jack_status_t status; - client = jack_client_open(config.jack_client_name, JackNoStartServer, &status); - if (!client) { - die("Could not start JACK server. JackStatus is %x", status); - } - sample_rate = jack_get_sample_rate(client); - if (sample_rate != 44100) { - die("The JACK server is running at the wrong sample rate (%d) for Shairport Sync. Must be 44100 Hz.", - sample_rate); - } - jack_set_process_callback(client, jack_stream_write_cb, 0); - left_port = jack_port_register(client, "out_L", JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, 0); - right_port = jack_port_register(client, "out_R", - JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); - if (jack_activate(client)) { - die("Could not activate %s JACK client.", config.jack_client_name); - } else { - debug(2, "JACK client %s activated sucessfully.", config.jack_client_name); - client_is_open = 1; - } + jack_status_t status; + client = jack_client_open(config.jack_client_name, JackNoStartServer, &status); + if (!client) { + die("Could not start JACK server. JackStatus is %x", status); + } + sample_rate = jack_get_sample_rate(client); + if (sample_rate != 44100) { + die("The JACK server is running at the wrong sample rate (%d) for Shairport Sync. Must be 44100 Hz.", + sample_rate); + } + jack_set_process_callback(client, jack_stream_write_cb, 0); + left_port = jack_port_register(client, "out_L", JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + right_port = jack_port_register(client, "out_R", + JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + if (jack_activate(client)) { + die("Could not activate %s JACK client.", config.jack_client_name); + } else { + debug(2, "JACK client %s activated sucessfully.", config.jack_client_name); } pthread_mutex_unlock(&client_mutex); @@ -216,13 +209,10 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a void jack_deinit() { pthread_mutex_lock(&client_mutex); - if (client_is_open) { - if (jack_deactivate(client)) - debug(1, "Error deactivating jack client"); - if (jack_client_close(client)) - debug(1, "Error closing jack client"); - client_is_open = 0; - } + if (jack_deactivate(client)) + debug(1, "Error deactivating jack client"); + if (jack_client_close(client)) + debug(1, "Error closing jack client"); pthread_mutex_unlock(&client_mutex); jack_ringbuffer_free(jackbuf); } From 8e46c727d59334b87ea0f639160e92349f5c81d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 23:45:12 +0000 Subject: [PATCH 471/569] Remove client_mutex, since JACK opening and closing is now single-threaded. --- audio_jack.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index f6f8b799..95759536 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -39,7 +39,6 @@ static const int bytes_per_frame = 4; #define buffer_size (44100 * 4 * bytes_per_frame) static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_t *open_client_if_necessary_thread = NULL; @@ -181,7 +180,6 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a jack_set_error_function(default_jack_error_callback); jack_set_info_function(default_jack_info_callback); - pthread_mutex_lock(&client_mutex); jack_status_t status; client = jack_client_open(config.jack_client_name, JackNoStartServer, &status); if (!client) { @@ -202,18 +200,15 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a } else { debug(2, "JACK client %s activated sucessfully.", config.jack_client_name); } - pthread_mutex_unlock(&client_mutex); return 0; } void jack_deinit() { - pthread_mutex_lock(&client_mutex); if (jack_deactivate(client)) debug(1, "Error deactivating jack client"); if (jack_client_close(client)) debug(1, "Error closing jack client"); - pthread_mutex_unlock(&client_mutex); jack_ringbuffer_free(jackbuf); } From 55797c83877c0209f8a0ea666d6f755805d7cd64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Fri, 15 Feb 2019 23:51:11 +0000 Subject: [PATCH 472/569] Remove unused headers. will come back once we implement command line parsing, the others may well be gone for good. --- audio_jack.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 95759536..8d176759 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -20,17 +20,11 @@ #include "audio.h" #include "common.h" #include -#include #include -#include #include -#include -#include #include -#include #include -#include #include // Two-channel, 16bit audio: @@ -215,6 +209,9 @@ void jack_deinit() { void jack_start(__attribute__((unused)) int i_sample_rate, __attribute__((unused)) int i_sample_format) { // nothing to do, JACK client has already been set up at jack_init() + // also, we have no say over the sample rate or sample format of JACK + // we convert the 16bit samples to float, and die if the sample rate is != 44k1. + // FIXME: later, resampling would be nice. Fold into soxr if possible } void jack_flush() { From 0009fcddf6c3f9c64c629e4bf086a1677ff2db1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Sat, 16 Feb 2019 00:53:04 +0000 Subject: [PATCH 473/569] Revert "Remove client_mutex, since JACK opening and closing is now single-threaded." That was a bit too optimistic, I guess. Must understand more about the rest of shairport-sync. Fixes spurious segfault on program termination with CTRL-C that does not happen in gdb :) This reverts commit 8e46c727d59334b87ea0f639160e92349f5c81d1. --- audio_jack.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/audio_jack.c b/audio_jack.c index 8d176759..ec004861 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -33,6 +33,7 @@ static const int bytes_per_frame = 4; #define buffer_size (44100 * 4 * bytes_per_frame) static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_t *open_client_if_necessary_thread = NULL; @@ -174,6 +175,7 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a jack_set_error_function(default_jack_error_callback); jack_set_info_function(default_jack_info_callback); + pthread_mutex_lock(&client_mutex); jack_status_t status; client = jack_client_open(config.jack_client_name, JackNoStartServer, &status); if (!client) { @@ -194,15 +196,18 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a } else { debug(2, "JACK client %s activated sucessfully.", config.jack_client_name); } + pthread_mutex_unlock(&client_mutex); return 0; } void jack_deinit() { + pthread_mutex_lock(&client_mutex); if (jack_deactivate(client)) debug(1, "Error deactivating jack client"); if (jack_client_close(client)) debug(1, "Error closing jack client"); + pthread_mutex_unlock(&client_mutex); jack_ringbuffer_free(jackbuf); } From f1ba07ff2bebb56f1258ee16994e69bfe6d72f4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Sat, 16 Feb 2019 01:02:30 +0000 Subject: [PATCH 474/569] Make callback names simpler and more consistent. Always use &callback in setter functions to make the function pointer explicit, even though it's not syntactically necessary. --- audio_jack.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index ec004861..9ccc4be7 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -91,7 +91,7 @@ static void deinterleave_and_convert(const char *interleaved_frames, } } -static int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *arg) { +static int process(jack_nframes_t nframes, __attribute__((unused)) void *arg) { jack_default_audio_sample_t *left_buffer = (jack_default_audio_sample_t *)jack_port_get_buffer(left_port, nframes); @@ -136,9 +136,9 @@ static int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) // FIXME: set_graph_order_callback(), recompute latencies here! -static void default_jack_error_callback(const char *desc) { debug(2, "jackd error: \"%s\"", desc); } +static void error(const char *desc) { debug(2, "jackd error: \"%s\"", desc); } -static void default_jack_info_callback(const char *desc) { inform("jackd information: \"%s\"", desc); } +static void info(const char *desc) { inform("jackd information: \"%s\"", desc); } int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) { config.audio_backend_latency_offset = 0; @@ -172,8 +172,8 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a die("Can't allocate %d bytes for the JACK ringbuffer.", buffer_size); jack_ringbuffer_mlock(jackbuf); // lock buffer into memory so that it never gets paged out - jack_set_error_function(default_jack_error_callback); - jack_set_info_function(default_jack_info_callback); + jack_set_error_function(&error); + jack_set_info_function(&info); pthread_mutex_lock(&client_mutex); jack_status_t status; @@ -186,7 +186,7 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a die("The JACK server is running at the wrong sample rate (%d) for Shairport Sync. Must be 44100 Hz.", sample_rate); } - jack_set_process_callback(client, jack_stream_write_cb, 0); + jack_set_process_callback(client, &process, 0); left_port = jack_port_register(client, "out_L", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); right_port = jack_port_register(client, "out_R", From bda8b99cdaa2190cac916194422c0f6badf0e1b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Sat, 16 Feb 2019 01:53:49 +0000 Subject: [PATCH 475/569] Add graph reorder callback, this is where we now compute the output latencies. --- audio_jack.c | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 9ccc4be7..80c357bf 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -134,7 +134,15 @@ static int process(jack_nframes_t nframes, __attribute__((unused)) void *arg) { return 0; } -// FIXME: set_graph_order_callback(), recompute latencies here! +static int graph(__attribute__((unused)) void * arg) { + jack_port_get_latency_range(left_port, JackPlaybackLatency, &latest_left_latency_range); + jack_port_get_latency_range(right_port, JackPlaybackLatency, &latest_right_latency_range); + debug(1, "JACK graph reorder callback called. Current latencies to terminal downstream port:\n" + "\tLmin = %d, Lmax = %d, Rmin = %d, Rmax = %d", + latest_left_latency_range.min, latest_left_latency_range.max, + latest_right_latency_range.min, latest_right_latency_range.max); + return 0; +} static void error(const char *desc) { debug(2, "jackd error: \"%s\"", desc); } @@ -172,9 +180,6 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a die("Can't allocate %d bytes for the JACK ringbuffer.", buffer_size); jack_ringbuffer_mlock(jackbuf); // lock buffer into memory so that it never gets paged out - jack_set_error_function(&error); - jack_set_info_function(&info); - pthread_mutex_lock(&client_mutex); jack_status_t status; client = jack_client_open(config.jack_client_name, JackNoStartServer, &status); @@ -187,6 +192,10 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a sample_rate); } jack_set_process_callback(client, &process, 0); + jack_set_graph_order_callback(client, &graph, 0); + jack_set_error_function(&error); + jack_set_info_function(&info); + left_port = jack_port_register(client, "out_L", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); right_port = jack_port_register(client, "out_R", @@ -225,7 +234,7 @@ void jack_flush() { } int jack_delay(long *the_delay) { - + jack_nframes_t base_latency; // semantics change: we now look at the last transfer into the lock-free ringbuffer, not // into the jack buffers directly (because locking those would violate real-time constraints). // on average, that should lead to just a constant additional latency. the old comment still applies: @@ -234,7 +243,6 @@ int jack_delay(long *the_delay) { // but then a transfer could occur and we would get the buffer occupancy after another transfer // had occurred // so we could "lose" a full transfer (e.g. 1024 frames @ 44,100 fps ~ 23.2 milliseconds) - pthread_mutex_lock(&buffer_mutex); int64_t time_now = get_absolute_time_in_fp(); // this is the time back to the last time data @@ -248,16 +256,10 @@ int jack_delay(long *the_delay) { pthread_mutex_unlock(&buffer_mutex); int64_t frames_processed_since_latest_latency_check = (delta * 44100) >> 32; - - // FIXME: this should only be done if there was an actual change, i.e. on the jack graph reorder - // callback, to update a static variable which can be checked here. For now, use a fixed arbitrary value: - jack_nframes_t base_latency = 0; - // debug(1,"delta: %" PRId64 " frames.",frames_processed_since_latest_latency_check); - - // jack_nframes_t base_latency = (latest_left_latency_range.min + latest_left_latency_range.max) / 2; - // if (base_latency == 0) - // base_latency = (latest_right_latency_range.min + latest_right_latency_range.max) / 2; + // use the average of the left and right maximum latencies. if max is really different from min, + // there is an anomaly in the graph that we don't have any hope of fixing anyways + base_latency = (latest_left_latency_range.max + latest_right_latency_range.max) / 2; *the_delay = base_latency + audio_occupancy_now - frames_processed_since_latest_latency_check; // debug(1,"reporting a delay of %d frames",*the_delay); return 0; From 4751a4e1bcd555bb6eb28cd99f2cfbb226236aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Sat, 16 Feb 2019 02:03:15 +0000 Subject: [PATCH 476/569] Move jack base latency computation out of delay() into graph() to save cycles. --- audio_jack.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 80c357bf..5eb8f285 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -137,16 +137,22 @@ static int process(jack_nframes_t nframes, __attribute__((unused)) void *arg) { static int graph(__attribute__((unused)) void * arg) { jack_port_get_latency_range(left_port, JackPlaybackLatency, &latest_left_latency_range); jack_port_get_latency_range(right_port, JackPlaybackLatency, &latest_right_latency_range); + jack_latency = (latest_left_latency_range.max + latest_right_latency_range.max) / 2; debug(1, "JACK graph reorder callback called. Current latencies to terminal downstream port:\n" - "\tLmin = %d, Lmax = %d, Rmin = %d, Rmax = %d", + "\tLmin = %d, Lmax = %d, Rmin = %d, Rmax = %d; jack base latency = %d", latest_left_latency_range.min, latest_left_latency_range.max, - latest_right_latency_range.min, latest_right_latency_range.max); + latest_right_latency_range.min, latest_right_latency_range.max, + jack_latency); return 0; } -static void error(const char *desc) { debug(2, "jackd error: \"%s\"", desc); } +static void error(const char *desc) { + debug(2, "JACK error: \"%s\"", desc); +} -static void info(const char *desc) { inform("jackd information: \"%s\"", desc); } +static void info(const char *desc) { + inform("JACK information: \"%s\"", desc); +} int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) { config.audio_backend_latency_offset = 0; @@ -234,7 +240,6 @@ void jack_flush() { } int jack_delay(long *the_delay) { - jack_nframes_t base_latency; // semantics change: we now look at the last transfer into the lock-free ringbuffer, not // into the jack buffers directly (because locking those would violate real-time constraints). // on average, that should lead to just a constant additional latency. the old comment still applies: @@ -259,8 +264,7 @@ int jack_delay(long *the_delay) { // debug(1,"delta: %" PRId64 " frames.",frames_processed_since_latest_latency_check); // use the average of the left and right maximum latencies. if max is really different from min, // there is an anomaly in the graph that we don't have any hope of fixing anyways - base_latency = (latest_left_latency_range.max + latest_right_latency_range.max) / 2; - *the_delay = base_latency + audio_occupancy_now - frames_processed_since_latest_latency_check; + *the_delay = jack_latency + audio_occupancy_now - frames_processed_since_latest_latency_check; // debug(1,"reporting a delay of %d frames",*the_delay); return 0; } From 95246dcd84698216692aab31a4df99d27acb9479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Sat, 16 Feb 2019 02:05:05 +0000 Subject: [PATCH 477/569] Use more conservative JACK client name (a simple token without whitespace). --- audio_jack.c | 2 +- scripts/shairport-sync.conf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 5eb8f285..1e848678 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -179,7 +179,7 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a } if (config.jack_client_name == NULL) - config.jack_client_name = strdup("Shairport Sync"); + config.jack_client_name = strdup("shairport-sync"); jackbuf = jack_ringbuffer_create(buffer_size); if (jackbuf == NULL) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index c06c32e7..dded9294 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -108,7 +108,7 @@ pa = // Parameters for the "jack" JACK Audio Connection Kit backend. jack = { -// client_name = "Shairport Sync"; //Set this to the name of the client that should appear in "Connections" when Shairport Sync is active. +// client_name = "shairport-sync"; //Set this to the name of the client that should appear in "Connections" when Shairport Sync is active. }; // Parameters for the "pipe" audio back end, a back end that directs raw CD-style audio output to a pipe. No interpolation is done. From c00199d664a1042af78ede253f45799e8bca3aa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Sat, 16 Feb 2019 02:15:35 +0000 Subject: [PATCH 478/569] Always use NULL instead of 0 for pointers for clarity. --- audio_jack.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 1e848678..736992f1 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -92,12 +92,10 @@ static void deinterleave_and_convert(const char *interleaved_frames, } static int process(jack_nframes_t nframes, __attribute__((unused)) void *arg) { - jack_default_audio_sample_t *left_buffer = (jack_default_audio_sample_t *)jack_port_get_buffer(left_port, nframes); jack_default_audio_sample_t *right_buffer = (jack_default_audio_sample_t *)jack_port_get_buffer(right_port, nframes); - jack_ringbuffer_data_t v[2] = { 0 }; jack_nframes_t i, thisbuf; int frames_written = 0; @@ -177,7 +175,6 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a config.jack_client_name = (char *)str; } } - if (config.jack_client_name == NULL) config.jack_client_name = strdup("shairport-sync"); @@ -197,15 +194,15 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a die("The JACK server is running at the wrong sample rate (%d) for Shairport Sync. Must be 44100 Hz.", sample_rate); } - jack_set_process_callback(client, &process, 0); - jack_set_graph_order_callback(client, &graph, 0); + jack_set_process_callback(client, &process, NULL); + jack_set_graph_order_callback(client, &graph, NULL); jack_set_error_function(&error); jack_set_info_function(&info); left_port = jack_port_register(client, "out_L", JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, 0); - right_port = jack_port_register(client, "out_R", - JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + JackPortIsOutput, NULL); + right_port = jack_port_register(client, "out_R", JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, NULL); if (jack_activate(client)) { die("Could not activate %s JACK client.", config.jack_client_name); } else { From 90f24c62dfe5ecf56450419663bb8dd90849a614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Sat, 16 Feb 2019 02:20:21 +0000 Subject: [PATCH 479/569] Add myself to (C)omplain-to tag :-D --- audio_jack.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/audio_jack.c b/audio_jack.c index 736992f1..30403f62 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -1,6 +1,7 @@ /* * jack output driver. This file is part of Shairport Sync. - * Copyright (c) 2018 Mike Brady + * Copyright (c) 2019 Mike Brady , + * Jörn Nettingsmeier * * All rights reserved. * From 3a4deb38a2f82fc0890723201850738318f0268b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Sat, 16 Feb 2019 02:26:36 +0000 Subject: [PATCH 480/569] Declare all file-global variables as static, remove unused thread pointer. --- audio_jack.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 30403f62..7095a31b 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -36,8 +36,6 @@ static const int bytes_per_frame = 4; static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER; -pthread_t *open_client_if_necessary_thread = NULL; - int jack_init(int, char **); void jack_deinit(void); void jack_start(int, int); @@ -60,18 +58,18 @@ audio_output audio_jack = {.name = "jack", .parameters = NULL, .mute = NULL}; -jack_port_t *left_port; -jack_port_t *right_port; +static jack_port_t *left_port; +static jack_port_t *right_port; -jack_client_t *client; -jack_nframes_t sample_rate; -jack_nframes_t jack_latency; +static jack_client_t *client; +static jack_nframes_t sample_rate; +static jack_nframes_t jack_latency; static jack_ringbuffer_t *jackbuf; static int flush_please = 0; -jack_latency_range_t latest_left_latency_range, latest_right_latency_range; -int64_t time_of_latest_transfer; +static jack_latency_range_t latest_left_latency_range, latest_right_latency_range; +static int64_t time_of_latest_transfer; static inline jack_default_audio_sample_t sample_conv(short sample) { From 5c1769f87c6bef17607731a1ba0532ac751ddc92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Sat, 16 Feb 2019 02:28:59 +0000 Subject: [PATCH 481/569] Partly revert c00199d664a1042af78ede253f45799e8bca3aa0, some 0 are actually longs. It's late :( --- audio_jack.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 7095a31b..2e89ec95 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -199,9 +199,9 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a jack_set_info_function(&info); left_port = jack_port_register(client, "out_L", JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, NULL); + JackPortIsOutput, 0); right_port = jack_port_register(client, "out_R", JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, NULL); + JackPortIsOutput, 0); if (jack_activate(client)) { die("Could not activate %s JACK client.", config.jack_client_name); } else { From 14aa9d4180a97f224d1117900a339b5fb5e44ca6 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 16 Feb 2019 14:06:40 +0000 Subject: [PATCH 482/569] Quieten a very noisy mutex debug message --- audio_alsa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_alsa.c b/audio_alsa.c index dd114e28..a683f060 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1394,7 +1394,7 @@ int play(void *buf, int samples) { // debug(3,"audio_alsa play called."); int ret = 0; - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 50000, 1); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 50000, 0); if (alsa_backend_state == abm_disconnected) { ret = do_open(); From 617d4bc22883c903affffd4f1dc47fdda1682fd3 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 16 Feb 2019 14:56:34 +0000 Subject: [PATCH 483/569] Turn up debug level to 3 when too long a silence occurs --- rtsp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/rtsp.c b/rtsp.c index 9aff0e65..d3ee35ae 100644 --- a/rtsp.c +++ b/rtsp.c @@ -291,6 +291,7 @@ void *player_watchdog_thread_code(void *arg) { 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\".", From 28ea6972af185ef44674a55512f943a32e57fb04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Sat, 16 Feb 2019 15:33:29 +0000 Subject: [PATCH 484/569] Add dummy option jack_autoconnect_option. --- audio_jack.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/audio_jack.c b/audio_jack.c index 2e89ec95..cd90c796 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -173,6 +173,10 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a if (config_lookup_string(config.cfg, "jack.client_name", &str)) { config.jack_client_name = (char *)str; } + /* Get the autoconnect pattern. */ + if (config_lookup_string(config.cfg, "jack.autoconnect_pattern", &str)) { + config.jack_autoconnect_pattern = (char *)str; + } } if (config.jack_client_name == NULL) config.jack_client_name = strdup("shairport-sync"); @@ -207,6 +211,17 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a } else { debug(2, "JACK client %s activated sucessfully.", config.jack_client_name); } + + if (config.jack_autoconnect_pattern != NULL) { + debug(1, "config.jack_autoconnect_pattern is %s.", config.jack_autoconnect_pattern); + const char** port_list = jack_get_ports(client, config.jack_autoconnect_pattern, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput); + while (*port_list != NULL) { + debug(1, "Found matching JACK port %s.", *port_list); + port_list++; + // FIXME: implement actual connection, warn user if more than 2 hits. + } + } + pthread_mutex_unlock(&client_mutex); return 0; From bd5cfd3583fe75d17f5b44a4af8efff15f6fd54e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Sat, 16 Feb 2019 16:08:40 +0000 Subject: [PATCH 485/569] forgot to add new option autoconnect_pattern to default conf and common.h --- common.h | 1 + scripts/shairport-sync.conf | 1 + 2 files changed, 2 insertions(+) diff --git a/common.h b/common.h index 9a824cbf..b06fd52d 100644 --- a/common.h +++ b/common.h @@ -233,6 +233,7 @@ typedef struct { // debugging. Currently audio packets only... #ifdef CONFIG_JACK char *jack_client_name; + char *jack_autoconnect_pattern; #endif } shairport_cfg; diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index dded9294..addbd1ee 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -109,6 +109,7 @@ pa = jack = { // client_name = "shairport-sync"; //Set this to the name of the client that should appear in "Connections" when Shairport Sync is active. +// autoconnect_pattern = ""; //Set this to a pattern that describes the ports you would like to connect to automatically when we start up, like "system:playback_?" }; // Parameters for the "pipe" audio back end, a back end that directs raw CD-style audio output to a pipe. No interpolation is done. From 4f39e0d83073513e4bbfd85648fda932d056c717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Sat, 16 Feb 2019 16:09:29 +0000 Subject: [PATCH 486/569] Move away from hardcoded channels. Centralizes information, simplifies the code, and might be helpful for future surround support. --- audio_jack.c | 62 ++++++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index cd90c796..6f4e3416 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -58,8 +58,10 @@ audio_output audio_jack = {.name = "jack", .parameters = NULL, .mute = NULL}; -static jack_port_t *left_port; -static jack_port_t *right_port; +// This also affects deinterlacing, so make it exactly the number of incoming audio channels! +#define NPORTS 2 +static jack_port_t *port[NPORTS]; +static const char* port_name[NPORTS] = { "out_L", "out_R" }; static jack_client_t *client; static jack_nframes_t sample_rate; @@ -68,7 +70,7 @@ static jack_nframes_t jack_latency; static jack_ringbuffer_t *jackbuf; static int flush_please = 0; -static jack_latency_range_t latest_left_latency_range, latest_right_latency_range; +static jack_latency_range_t latest_latency_range[NPORTS]; static int64_t time_of_latest_transfer; @@ -77,24 +79,23 @@ static inline jack_default_audio_sample_t sample_conv(short sample) { } static void deinterleave_and_convert(const char *interleaved_frames, - jack_default_audio_sample_t * const jack_buffer_left, - jack_default_audio_sample_t * const jack_buffer_right, + jack_default_audio_sample_t* jack_buffer[], + jack_nframes_t offset, jack_nframes_t nframes) { - jack_nframes_t i; + jack_nframes_t f; short *ifp = (short *)interleaved_frames; // we're dealing with 16bit audio here - jack_default_audio_sample_t *fpl = jack_buffer_left; - jack_default_audio_sample_t *fpr = jack_buffer_right; - for (i=0; i 0) { - left_buffer[frames_written] = 0.0; - right_buffer[frames_written] = 0.0; + for (int i=0; i < NPORTS; i++) { + buffer[i][frames_written] = 0.0; + } frames_written++; nframes--; } @@ -132,14 +134,16 @@ static int process(jack_nframes_t nframes, __attribute__((unused)) void *arg) { } static int graph(__attribute__((unused)) void * arg) { - jack_port_get_latency_range(left_port, JackPlaybackLatency, &latest_left_latency_range); - jack_port_get_latency_range(right_port, JackPlaybackLatency, &latest_right_latency_range); - jack_latency = (latest_left_latency_range.max + latest_right_latency_range.max) / 2; - debug(1, "JACK graph reorder callback called. Current latencies to terminal downstream port:\n" - "\tLmin = %d, Lmax = %d, Rmin = %d, Rmax = %d; jack base latency = %d", - latest_left_latency_range.min, latest_left_latency_range.max, - latest_right_latency_range.min, latest_right_latency_range.max, - jack_latency); + int latency = 0; + debug(1, "JACK graph reorder callback called. Current latencies to terminal downstream port:"); + for (int i=0; i Date: Sat, 16 Feb 2019 17:51:47 +0000 Subject: [PATCH 487/569] fix tinysvcmdns issues reported in CVE-2017-12130 --- tinysvcmdns.c | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/tinysvcmdns.c b/tinysvcmdns.c index d0cbebb8..f0c03187 100644 --- a/tinysvcmdns.c +++ b/tinysvcmdns.c @@ -6,6 +6,8 @@ * tinysvcmdns - a tiny MDNS implementation for publishing services * Copyright (C) 2011 Darell Tan * All rights reserved. + * Updated many times by Mike Brady (c) 2014 -- 2019 + * Includes fixes for CVE-12087 and CVE-2017-12130 * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -81,6 +83,8 @@ struct name_comp { // duplicates a name inline uint8_t *dup_nlabel(const uint8_t *n) { + if (n == NULL) + return NULL; assert(n[0] <= 63); // prevent mis-use return (uint8_t *)strdup((char *)n); } @@ -126,7 +130,8 @@ char *nlabel_to_str(const uint8_t *name) { size_t buf_len = 256; assert(name != NULL); - + if (name == NULL) + return NULL; label = labelp = malloc(buf_len); if (label) { @@ -186,7 +191,9 @@ uint8_t *create_label(const char *txt) { int len; uint8_t *s; - assert(txt != NULL); + // assert(txt != NULL); + if (txt == NULL) + return NULL; len = strlen(txt); if (len > 63) return NULL; @@ -678,9 +685,12 @@ static size_t mdns_parse_qn(uint8_t *pkt_buf, size_t pkt_len, size_t off, struct if (rr) memset(rr, 0, sizeof(struct rr_entry)); else - die("could not allocate memory for \"rr\" in tinysvcmdns"); + goto err; name = uncompress_nlabel(pkt_buf, pkt_len, off); + if (name == NULL) + goto err; + p += label_len(pkt_buf, pkt_len, off); rr->name = name; @@ -694,6 +704,10 @@ static size_t mdns_parse_qn(uint8_t *pkt_buf, size_t pkt_len, size_t off, struct rr_list_append(&pkt->rr_qn, rr); return p - (pkt_buf + off); + +err: + free(rr); + return 0; } // parse the MDNS RR section @@ -716,10 +730,13 @@ static size_t mdns_parse_rr(uint8_t *pkt_buf, size_t pkt_len, size_t off, struct if (rr) memset(rr, 0, sizeof(struct rr_entry)); else - die("could not allocate memory for \"rr (2)\" in tinysvcmdns"); + goto err; name = uncompress_nlabel(pkt_buf, pkt_len, off); - p += label_len(pkt_buf, pkt_len, off); + if (name == NULL) + goto err; + + // parse the MDNS RR section p += label_len(pkt_buf, pkt_len, off); rr->name = name; rr->type = mdns_read_u16(p); @@ -822,6 +839,10 @@ static size_t mdns_parse_rr(uint8_t *pkt_buf, size_t pkt_len, size_t off, struct rr_list_append(&pkt->rr_ans, rr); return p - (pkt_buf + off); + +err: + free(rr); + return 0; } // parse a MDNS packet into an mdns_pkt struct @@ -1716,8 +1737,7 @@ void mdnsd_stop(struct mdnsd *s) { assert(s != NULL); struct timeval tv = { - .tv_sec = 0, - .tv_usec = 500 * 1000, + .tv_sec = 0, .tv_usec = 500 * 1000, }; s->stop_flag = 1; From 97370d8bce0ee18fa06b932f9db16d871b03ec71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Sat, 16 Feb 2019 18:45:51 +0000 Subject: [PATCH 488/569] Actually implement auto-connection, and document it. --- audio_jack.c | 42 ++++++++++++++++++++++++++++++------- scripts/shairport-sync.conf | 10 +++++++-- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 6f4e3416..d98be546 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -20,6 +20,7 @@ #include "audio.h" #include "common.h" +#include #include #include #include @@ -156,6 +157,7 @@ static void info(const char *desc) { } int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) { + int i; config.audio_backend_latency_offset = 0; config.audio_backend_buffer_desired_length = 0.500; config.audio_backend_buffer_interpolation_threshold_in_seconds = @@ -206,7 +208,7 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a jack_set_error_function(&error); jack_set_info_function(&info); - for (int i=0; i < NPORTS; i++) { + for (i=0; i < NPORTS; i++) { port[i] = jack_port_register(client, port_name[i], JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); } @@ -218,14 +220,40 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a if (config.jack_autoconnect_pattern != NULL) { debug(1, "config.jack_autoconnect_pattern is %s.", config.jack_autoconnect_pattern); - const char** port_list = jack_get_ports(client, config.jack_autoconnect_pattern, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput); - while (*port_list != NULL) { - debug(1, "Found matching JACK port %s.", *port_list); - port_list++; - // FIXME: implement actual connection, warn user if more than 2 hits. + const char** port_list = jack_get_ports(client, config.jack_autoconnect_pattern, + JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput); + for (i=0; i Date: Sat, 16 Feb 2019 19:01:37 +0000 Subject: [PATCH 489/569] Use appropriate warn/inform/debug levels. --- audio_jack.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index d98be546..f34e8899 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -136,20 +136,20 @@ static int process(jack_nframes_t nframes, __attribute__((unused)) void *arg) { static int graph(__attribute__((unused)) void * arg) { int latency = 0; - debug(1, "JACK graph reorder callback called. Current latencies to terminal downstream port:"); + debug(2, "JACK graph reorder callback called."); for (int i=0; i Date: Sat, 16 Feb 2019 19:32:13 +0000 Subject: [PATCH 490/569] Add a new command line option: -u to send logs to STDERR. --- common.c | 99 ++++++++++++++++++++--------------------------------- common.h | 2 ++ shairport.c | 14 +++++--- 3 files changed, 50 insertions(+), 65 deletions(-) diff --git a/common.c b/common.c index 6bd31524..cf5c0730 100644 --- a/common.c +++ b/common.c @@ -91,6 +91,28 @@ void set_alsa_out_dev(char *); static volatile int requested_connection_state_to_output = 1; +// this stuff is to direct logging to syslog via libdaemon or directly +// alternatively you can direct it to stderr using a command line option + +#ifdef CONFIG_LIBDAEMON +static void (*sps_log)(int prio, const char *t, ...) = daemon_log; +#else +static void (*sps_log)(int prio, const char *t, ...) = syslog; +#endif + +void do_sps_log(__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); + fprintf(stderr,"%s\n",s); +} + +void log_to_stderr() { + sps_log = do_sps_log; +} + shairport_cfg config; volatile int debuglev = 0; @@ -146,24 +168,13 @@ void die(const char *format, ...) { va_end(args); if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) - - #ifdef CONFIG_LIBDAEMON - daemon_log(LOG_ERR, "|% 20.9f|% 20.9f|*fatal error: %s", tss, tsl, s); + sps_log(LOG_ERR, "|% 20.9f|% 20.9f|*fatal error: %s", tss, tsl, s); else if ((debuglev) && (config.debugger_show_relative_time)) - daemon_log(LOG_ERR, "% 20.9f|*fatal error: %s", tsl, s); + sps_log(LOG_ERR, "% 20.9f|*fatal error: %s", tsl, s); else if ((debuglev) && (config.debugger_show_elapsed_time)) - daemon_log(LOG_ERR, "% 20.9f|*fatal error: %s", tss, s); + sps_log(LOG_ERR, "% 20.9f|*fatal error: %s", tss, s); else - daemon_log(LOG_ERR, "fatal error: %s", s); - #else - syslog(LOG_ERR, "|% 20.9f|% 20.9f|*fatal error: %s", tss, tsl, s); - else if ((debuglev) && (config.debugger_show_relative_time)) - syslog(LOG_ERR, "% 20.9f|*fatal error: %s", tsl, s); - else if ((debuglev) && (config.debugger_show_elapsed_time)) - syslog(LOG_ERR, "% 20.9f|*fatal error: %s", tss, s); - else - syslog(LOG_ERR, "fatal error: %s", s); - #endif + sps_log(LOG_ERR, "fatal error: %s", s); pthread_setcancelstate(oldState, NULL); exit(1); } @@ -184,25 +195,14 @@ void warn(const char *format, ...) { va_start(args, format); vsnprintf(s, sizeof(s), format, args); va_end(args); -#ifdef CONFIG_LIBDAEMON if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) - daemon_log(LOG_WARNING, "|% 20.9f|% 20.9f|*warning: %s", tss, tsl, s); + sps_log(LOG_WARNING, "|% 20.9f|% 20.9f|*warning: %s", tss, tsl, s); else if ((debuglev) && (config.debugger_show_relative_time)) - daemon_log(LOG_WARNING, "% 20.9f|*warning: %s", tsl, s); + sps_log(LOG_WARNING, "% 20.9f|*warning: %s", tsl, s); else if ((debuglev) && (config.debugger_show_elapsed_time)) - daemon_log(LOG_WARNING, "% 20.9f|*warning: %s", tss, s); + sps_log(LOG_WARNING, "% 20.9f|*warning: %s", tss, s); else - daemon_log(LOG_WARNING, "%s", s); -#else - if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) - syslog(LOG_WARNING, "|% 20.9f|% 20.9f|*warning: %s", tss, tsl, s); - else if ((debuglev) && (config.debugger_show_relative_time)) - syslog(LOG_WARNING, "% 20.9f|*warning: %s", tsl, s); - else if ((debuglev) && (config.debugger_show_elapsed_time)) - syslog(LOG_WARNING, "% 20.9f|*warning: %s", tss, s); - else - syslog(LOG_WARNING, "%s", s); -#endif + sps_log(LOG_WARNING, "%s", s); pthread_setcancelstate(oldState, NULL); } @@ -224,25 +224,14 @@ void debug(int level, const char *format, ...) { va_start(args, format); vsnprintf(s, sizeof(s), format, args); va_end(args); -#ifdef CONFIG_LIBDAEMON if ((config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) - daemon_log(LOG_DEBUG, "|% 20.9f|% 20.9f|%s", tss, tsl, s); + sps_log(LOG_DEBUG, "|% 20.9f|% 20.9f|%s", tss, tsl, s); else if (config.debugger_show_relative_time) - daemon_log(LOG_DEBUG, "% 20.9f|%s", tsl, s); + sps_log(LOG_DEBUG, "% 20.9f|%s", tsl, s); else if (config.debugger_show_elapsed_time) - daemon_log(LOG_DEBUG, "% 20.9f|%s", tss, s); + sps_log(LOG_DEBUG, "% 20.9f|%s", tss, s); else - daemon_log(LOG_DEBUG, "%s", s); -#else - if ((config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) - syslog(LOG_DEBUG, "|% 20.9f|% 20.9f|%s", tss, tsl, s); - else if (config.debugger_show_relative_time) - syslog(LOG_DEBUG, "% 20.9f|%s", tsl, s); - else if (config.debugger_show_elapsed_time) - syslog(LOG_DEBUG, "% 20.9f|%s", tss, s); - else - syslog(LOG_DEBUG, "%s", s); -#endif + sps_log(LOG_DEBUG, "%s", s); pthread_setcancelstate(oldState, NULL); } @@ -262,26 +251,14 @@ void inform(const char *format, ...) { va_start(args, format); vsnprintf(s, sizeof(s), format, args); va_end(args); -#ifdef CONFIG_LIBDAEMON if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) - daemon_log(LOG_INFO, "|% 20.9f|% 20.9f|%s", tss, tsl, s); + sps_log(LOG_INFO, "|% 20.9f|% 20.9f|%s", tss, tsl, s); else if ((debuglev) && (config.debugger_show_relative_time)) - daemon_log(LOG_INFO, "% 20.9f|%s", tsl, s); + sps_log(LOG_INFO, "% 20.9f|%s", tsl, s); else if ((debuglev) && (config.debugger_show_elapsed_time)) - daemon_log(LOG_INFO, "% 20.9f|%s", tss, s); + sps_log(LOG_INFO, "% 20.9f|%s", tss, s); else - daemon_log(LOG_INFO, "%s", s); -#else - if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) - syslog(LOG_INFO, "|% 20.9f|% 20.9f|%s", tss, tsl, s); - else if ((debuglev) && (config.debugger_show_relative_time)) - syslog(LOG_INFO, "% 20.9f|%s", tsl, s); - else if ((debuglev) && (config.debugger_show_elapsed_time)) - syslog(LOG_INFO, "% 20.9f|%s", tss, s); - else - syslog(LOG_INFO, "%s", s); - -#endif + sps_log(LOG_INFO, "%s", s); pthread_setcancelstate(oldState, NULL); } diff --git a/common.h b/common.h index 78c9ddda..9dceabb3 100644 --- a/common.h +++ b/common.h @@ -245,6 +245,8 @@ uint16_t nctohs(const uint8_t *p); // read 2 characters from *p and do ntohs on void memory_barrier(); +void log_to_stderr(); // call this to director logging to stderr; + // true if Shairport Sync is supposed to be sending output to the output device, false otherwise int get_requested_connection_state_to_output(); diff --git a/shairport.c b/shairport.c index 0959e5f2..3fdf780d 100644 --- a/shairport.c +++ b/shairport.c @@ -156,9 +156,9 @@ void usage(char *progname) { #ifdef CONFIG_LIBDAEMON printf(" -d, --daemon daemonise.\n"); printf(" -j, --justDaemoniseNoPIDFile daemonise without a PID file.\n"); + printf(" -k, --kill kill the existing shairport daemon.\n"); #endif printf(" -V, --version show version information.\n"); - printf(" -k, --kill kill the existing shairport daemon.\n"); printf(" -c, --configfile=FILE read configuration settings from FILE. Default is " "/etc/shairport-sync.conf.\n"); @@ -171,7 +171,6 @@ void usage(char *progname) { printf(" -L, --latency=FRAMES [Deprecated] Set the latency for audio sent from an unknown " "device.\n"); printf(" The default is to set it automatically.\n"); - printf(" The default is to set it automatically.\n"); printf(" -S, --stuffing=MODE set how to adjust current latency to match desired latency, " "where \n"); printf(" \"basic\" (default) inserts or deletes audio frames from " @@ -213,6 +212,7 @@ void usage(char *progname) { printf(" The default is /tmp/shairport-sync-metadata.\n"); printf(" --get-coverart send cover art through the metadata pipe.\n"); #endif + printf(" -u, --use-stderr log messages through STDERR rather than syslog.\n"); printf("\n"); mdns_ls_backends(); printf("\n"); @@ -229,15 +229,17 @@ int parse_options(int argc, char **argv) { int fResyncthreshold = (int)(config.resyncthreshold * 44100); int fTolerance = (int)(config.tolerance * 44100); poptContext optCon; /* context for parsing command-line options */ +#if CONFIG_LIBDAEMON int daemonisewith = 0; int daemonisewithout = 0; +#endif struct poptOption optionsTable[] = { {"verbose", 'v', POPT_ARG_NONE, NULL, 'v', NULL, NULL}, - {"disconnectFromOutput", 'D', POPT_ARG_NONE, NULL, 0, NULL, NULL}, - {"reconnectToOutput", 'R', POPT_ARG_NONE, NULL, 0, NULL, NULL}, +#if CONFIG_LIBDAEMON {"kill", 'k', POPT_ARG_NONE, NULL, 0, NULL, NULL}, {"daemon", 'd', POPT_ARG_NONE, &daemonisewith, 0, NULL, NULL}, {"justDaemoniseNoPIDFile", 'j', POPT_ARG_NONE, &daemonisewithout, 0, NULL, NULL}, +#endif {"configfile", 'c', POPT_ARG_STRING, &config.configfile, 0, NULL, NULL}, {"statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL, NULL}, {"logOutputLevel", 0, POPT_ARG_NONE, &config.logOutputLevel, 0, NULL, NULL}, @@ -255,6 +257,7 @@ int parse_options(int argc, char **argv) { {"timeout", 't', POPT_ARG_INT, &config.timeout, 't', NULL, NULL}, {"password", 0, POPT_ARG_STRING, &config.password, 0, NULL, NULL}, {"tolerance", 'z', POPT_ARG_INT, &fTolerance, 0, NULL, NULL}, + {"use-stderr", 'u', POPT_ARG_NONE, NULL, 'u', NULL, NULL}, #ifdef CONFIG_METADATA {"metadata-pipename", 'M', POPT_ARG_STRING, &config.metadata_pipename, 'M', NULL, NULL}, {"get-coverart", 'g', POPT_ARG_NONE, &config.get_coverart, 'g', NULL, NULL}, @@ -281,6 +284,9 @@ int parse_options(int argc, char **argv) { case 'v': debuglev++; break; + case 'u': + log_to_stderr(); + break; case 'D': inform("Warning: the option -D or --disconnectFromOutput is deprecated."); break; From 405a028f338ac24ceb3cc5bf744194820e0e5a30 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 16 Feb 2019 19:44:25 +0000 Subject: [PATCH 491/569] quieten some exiting debug messages --- activity_monitor.c | 4 ++-- audio_alsa.c | 4 ++-- dacp.c | 4 ++-- dbus-service.c | 2 +- metadata_hub.c | 2 +- rtsp.c | 4 ++-- shairport.c | 18 +++++++++--------- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/activity_monitor.c b/activity_monitor.c index 4377c89e..51eeb0a2 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -265,9 +265,9 @@ void activity_monitor_start() { void activity_monitor_stop() { if (activity_monitor_running) { - debug(1, "activity_monitor_stop start..."); + debug(3, "activity_monitor_stop start..."); pthread_cancel(activity_monitor_thread); pthread_join(activity_monitor_thread, NULL); - debug(1, "activity_monitor_stop complete"); + debug(2, "activity_monitor_stop complete"); } } diff --git a/audio_alsa.c b/audio_alsa.c index a683f060..4e2fefda 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1059,9 +1059,9 @@ static void deinit(void) { pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable // debug(2,"audio_alsa deinit called."); stop(); - debug(1, "Cancel buffer monitor thread."); + debug(2, "Cancel buffer monitor thread."); pthread_cancel(alsa_buffer_monitor_thread); - debug(1, "Join buffer monitor thread."); + debug(3, "Join buffer monitor thread."); pthread_join(alsa_buffer_monitor_thread, NULL); pthread_setcancelstate(oldState, NULL); } diff --git a/dacp.c b/dacp.c index 4f40e2d0..f5797554 100644 --- a/dacp.c +++ b/dacp.c @@ -894,11 +894,11 @@ void dacp_monitor_start() { void dacp_monitor_stop() { if (dacp_monitor_initialised) { // only if it's been started and initialised - debug(1, "dacp_monitor_stop"); + debug(2, "dacp_monitor_stop"); pthread_cancel(dacp_monitor_thread); pthread_join(dacp_monitor_thread, NULL); pthread_mutex_destroy(&dacp_server_information_lock); - debug(1, "DACP Conversation Lock Mutex Destroyed"); + debug(3, "DACP Conversation Lock Mutex Destroyed"); pthread_mutex_destroy(&dacp_conversation_lock); } } diff --git a/dbus-service.c b/dbus-service.c index 2ed17d0a..a1cde3fd 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -885,7 +885,7 @@ int start_dbus_service() { } void stop_dbus_service() { - debug(1, "stopping dbus service"); + debug(2, "stopping dbus service"); if (ownerID) g_bus_unown_name(ownerID); else diff --git a/metadata_hub.c b/metadata_hub.c index f116f272..4d7def38 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -107,7 +107,7 @@ void metadata_hub_init(void) { void metadata_hub_stop(void) { if (metadata_hub_initialised) { - debug(1, "metadata_hub_stop."); + debug(2, "metadata_hub_stop."); metadata_hub_release_track_artwork(); if (metadata_store.track_metadata) { metadata_hub_release_track_metadata(metadata_store.track_metadata); diff --git a/rtsp.c b/rtsp.c index d3ee35ae..4e5062d3 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1411,7 +1411,7 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) } void metadata_thread_cleanup_function(__attribute__((unused)) void *arg) { - debug(1, "metadata_thread_cleanup_function called"); + debug(2, "metadata_thread_cleanup_function called"); metadata_delete_multicast_socket(); metadata_close(); pc_queue_delete(&metadata_queue); @@ -1462,7 +1462,7 @@ void metadata_init(void) { void metadata_stop(void) { if (metadata_running) { - debug(1, "metadata_stop called."); + debug(2, "metadata_stop called."); pthread_cancel(metadata_thread); pthread_join(metadata_thread, NULL); } diff --git a/shairport.c b/shairport.c index 3fdf780d..4cb316e1 100644 --- a/shairport.c +++ b/shairport.c @@ -107,7 +107,7 @@ static void sig_ignore(__attribute__((unused)) int foo, __attribute__((unused)) __attribute__((unused)) void *baz) {} static void sig_shutdown(__attribute__((unused)) int foo, __attribute__((unused)) siginfo_t *bar, __attribute__((unused)) void *baz) { - debug(1, "shutdown requested..."); + debug(2, "shutdown requested..."); #ifdef CONFIG_LIBDAEMON daemon_retval_send(255); daemon_pid_file_remove(); @@ -1087,7 +1087,7 @@ pthread_t dbus_thread; void *dbus_thread_func(__attribute__((unused)) void *arg) { g_main_loop = g_main_loop_new(NULL, FALSE); g_main_loop_run(g_main_loop); - debug(1, "g_main_loop thread exit"); + debug(2, "g_main_loop thread exit"); pthread_exit(NULL); } #endif @@ -1145,7 +1145,7 @@ const char *pid_file_proc(void) { void main_cleanup_handler(__attribute__((unused)) void *arg) { // it doesn't look like this is called when the main function is cancelled eith a pthread cancel. - debug(1, "main cleanup handler called."); + debug(2, "main cleanup handler called."); #ifdef CONFIG_MQTT if (config.mqtt_enabled) { // terminate_mqtt(); @@ -1160,19 +1160,19 @@ void main_cleanup_handler(__attribute__((unused)) void *arg) { stop_dbus_service(); #endif if (g_main_loop) { - debug(1, "Stopping DBUS Loop Thread"); + debug(2, "Stopping DBUS Loop Thread"); g_main_loop_quit(g_main_loop); pthread_join(dbus_thread, NULL); } #endif #ifdef CONFIG_DACP_CLIENT - debug(1, "Stopping DACP Monitor"); + debug(2, "Stopping DACP Monitor"); dacp_monitor_stop(); #endif #ifdef CONFIG_METADATA_HUB - debug(1, "Stopping metadata hub"); + debug(2, "Stopping metadata hub"); metadata_hub_stop(); #endif @@ -1183,7 +1183,7 @@ void main_cleanup_handler(__attribute__((unused)) void *arg) { activity_monitor_stop(0); if ((config.output) && (config.output->deinit)) { - debug(1, "Deinitialise the audio backend."); + debug(2, "Deinitialise the audio backend."); config.output->deinit(); } #ifdef CONFIG_LIBDAEMON @@ -1191,12 +1191,12 @@ void main_cleanup_handler(__attribute__((unused)) void *arg) { daemon_pid_file_remove(); daemon_signal_done(); #endif - debug(1, "Exit..."); + debug(2, "Exit..."); exit(0); } void exit_function() { - debug(1, "exit function called..."); + debug(2, "exit function called..."); main_cleanup_handler(NULL); if (conns) free(conns); // make sure the connections have been deleted first From b361ad84fceb799dc562d925ac9f576251f0a16b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Sat, 16 Feb 2019 19:57:28 +0000 Subject: [PATCH 492/569] NOP. Reformat code consistently, improve comments. --- audio_jack.c | 143 ++++++++++++++++++++++++++++----------------------- 1 file changed, 79 insertions(+), 64 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index f34e8899..28172833 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -59,7 +59,8 @@ audio_output audio_jack = {.name = "jack", .parameters = NULL, .mute = NULL}; -// This also affects deinterlacing, so make it exactly the number of incoming audio channels! +// This also affects deinterlacing. +// So make it exactly the number of incoming audio channels! #define NPORTS 2 static jack_port_t *port[NPORTS]; static const char* port_name[NPORTS] = { "out_L", "out_R" }; @@ -76,40 +77,55 @@ static int64_t time_of_latest_transfer; static inline jack_default_audio_sample_t sample_conv(short sample) { + // It sounds correct, but I don't understand it. + // Zero int needs to be zero float. Check. + // Plus 32767 int is 1.0. Check. + // Minus 32767 int is -0.99997. And here my brain shuts down. + // In my head, it should be 1.0, and we should tolerate an overflow + // at minus 32768. But I'm sure there's a textbook explanation somewhere. return ((sample < 0) ? (-1.0 * sample / SHRT_MIN) : (1.0 * sample / SHRT_MAX)); } -static void deinterleave_and_convert(const char *interleaved_frames, - jack_default_audio_sample_t* jack_buffer[], +static void deinterleave_and_convert(const char *interleaved_input_buffer, + jack_default_audio_sample_t* jack_output_buffer[], jack_nframes_t offset, jack_nframes_t nframes) { jack_nframes_t f; - short *ifp = (short *)interleaved_frames; // we're dealing with 16bit audio here - for (f=offset; f<(nframes + offset); f++) { - for (int i=0; i nframes) { frames_required = nframes; @@ -122,24 +138,28 @@ static int process(jack_nframes_t nframes, __attribute__((unused)) void *arg) { } jack_ringbuffer_read_advance(jackbuf, frames_written * bytes_per_frame); } - // now, if there are any more frames to put into the buffer, fill them with - // silence + // If there are any more frames to put into the buffer, fill them with + // silence. This is a critical underflow situation. Let's at least keep the JACK + // graph humming along while preventing the motorboat sound of a repeating buffer. while (nframes > 0) { - for (int i=0; i < NPORTS; i++) { + for (i = 0; i < NPORTS; i++) { buffer[i][frames_written] = 0.0; } frames_written++; nframes--; } - return 0; + return 0; // Tell JACK that all is well. } +// This is the JACK graph reorder callback. Now we know some JACK connections +// have changed, so we recompute the latency. static int graph(__attribute__((unused)) void * arg) { int latency = 0; debug(2, "JACK graph reorder callback called."); for (int i=0; i> 32; // debug(1,"delta: %" PRId64 " frames.",frames_processed_since_latest_latency_check); - // use the average of the left and right maximum latencies. if max is really different from min, - // there is an anomaly in the graph that we don't have any hope of fixing anyways + // jack_latency is set by the graph() callback, it's the average of the maximum + // latencies of all our output ports. Adjust this constant baseline delay according + // to the buffer fill level: *the_delay = jack_latency + audio_occupancy_now - frames_processed_since_latest_latency_check; // debug(1,"reporting a delay of %d frames",*the_delay); return 0; @@ -320,13 +334,14 @@ int play(void *buf, int samples) { // copy the samples into the queue size_t bytes_to_transfer, bytes_transferred; bytes_to_transfer = samples * bytes_per_frame; - pthread_mutex_lock(&buffer_mutex); // it's ok to lock here since we're not in the realtime callback + // It's ok to lock here since we're not in the realtime callback: + pthread_mutex_lock(&buffer_mutex); bytes_transferred = jack_ringbuffer_write(jackbuf, buf, bytes_to_transfer); - // semantics change: we now measure the last time audio was moved into the ringbuffer, not the jack output buffers. time_of_latest_transfer = get_absolute_time_in_fp(); pthread_mutex_unlock(&buffer_mutex); if (bytes_transferred < bytes_to_transfer) { - warn("JACK ringbuffer overrun. Only wrote %d of %d bytes.", bytes_transferred, bytes_to_transfer); + warn("JACK ringbuffer overrun. Only wrote %d of %d bytes.", + bytes_transferred, bytes_to_transfer); } return 0; } From e59da78348903a4c31f1064e56beac214fcc10de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Nettingsmeier?= Date: Sat, 16 Feb 2019 21:26:32 +0000 Subject: [PATCH 493/569] Fix segfault due to missing null check... --- audio_jack.c | 58 +++++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/audio_jack.c b/audio_jack.c index 28172833..7b00d1d2 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -242,37 +242,39 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a // I've reported it to the jack-devel mailing list, they're in a better place to fix it. const char** port_list = jack_get_ports(client, config.jack_autoconnect_pattern, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput); - for (i = 0; i < NPORTS ; i++) { - char* full_port_name[NPORTS]; - full_port_name[i] = malloc(sizeof(char) * jack_port_name_size()); - sprintf(full_port_name[i], "%s:%s", config.jack_client_name, port_name[i]); - if (port_list[i] != NULL) { - int err; - debug(2, "Connecting %s to %s.", full_port_name[i], port_list[i]); - err = jack_connect(client, full_port_name[i], port_list[i]); - switch (err) { - case EEXIST: - inform("The requested connection from %s to %s already exists.", - full_port_name[i], port_list[i]); - break; - case 0: - // success - break; - default: - warn("JACK error no. %d occured while trying to connect %s to %s.", - err, full_port_name[i], port_list[i]); - break; + if (port_list != NULL) { + for (i = 0; i < NPORTS ; i++) { + char* full_port_name[NPORTS]; + full_port_name[i] = malloc(sizeof(char) * jack_port_name_size()); + sprintf(full_port_name[i], "%s:%s", config.jack_client_name, port_name[i]); + if (port_list[i] != NULL) { + int err; + debug(2, "Connecting %s to %s.", full_port_name[i], port_list[i]); + err = jack_connect(client, full_port_name[i], port_list[i]); + switch (err) { + case EEXIST: + inform("The requested connection from %s to %s already exists.", + full_port_name[i], port_list[i]); + break; + case 0: + // success + break; + default: + warn("JACK error no. %d occured while trying to connect %s to %s.", + err, full_port_name[i], port_list[i]); + break; + } + } else { + inform("No matching port found in %s to connect %s to. You may not hear audio.", + config.jack_autoconnect_pattern, full_port_name[i]); } - } else { - inform("No matching port found in %s to connect %s to. You may not hear audio.", - config.jack_autoconnect_pattern, full_port_name[i]); + free(full_port_name[i]); } - free(full_port_name[i]); + while (port_list[i++] != NULL) { + inform("Additional matching port %s found. Check that the connections are what you intended."); + } + jack_free(port_list); } - while (port_list[i++] != NULL) { - inform("Additional matching port %s found. Check that the connections are what you intended."); - } - jack_free(port_list); } pthread_mutex_unlock(&client_mutex); From cae8bc0f3cbf7f8a544ad008554d9b841fb24b36 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 17 Feb 2019 10:13:48 +0000 Subject: [PATCH 494/569] Stop increasing the debugging verbosity when a watchdog timeout occurs --- rtsp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtsp.c b/rtsp.c index 4e5062d3..fb4ed4f3 100644 --- a/rtsp.c +++ b/rtsp.c @@ -291,7 +291,7 @@ void *player_watchdog_thread_code(void *arg) { if (time_since_last_bark >= ct) { conn->watchdog_barks++; if (conn->watchdog_barks == 1) { - debuglev = 3; // tell us everything. + // debuglev = 3; // tell us everything. debug(1, "Connection %d: As Yeats almost said, \"Too long a silence / can make a stone " "of the heart\".", From e1efc6e45a0bc9640f57cac920b489433acef64d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 17 Feb 2019 12:32:37 +0000 Subject: [PATCH 495/569] add, but don't use, a check to see if alsa uses CLOCK_MONOLITHIC (or, presumably, otherwise CLOCK_REALTIME) --- audio_alsa.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/audio_alsa.c b/audio_alsa.c index 4e2fefda..e75d9b41 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -114,6 +114,7 @@ int frame_size; // in bytes for interleaved stereo int alsa_device_initialised; // boolean to ensure the initialisation is only // done once snd_pcm_t *alsa_handle = NULL; +int alsa_uses_monolithic_clock; static snd_pcm_hw_params_t *alsa_params = NULL; static snd_pcm_sw_params_t *alsa_swparams = NULL; static snd_ctl_t *ctl = NULL; @@ -457,6 +458,9 @@ int actual_open_alsa_device(void) { snd_strerror(ret)); return ret; } + + alsa_uses_monolithic_clock = snd_pcm_hw_params_is_monotonic (alsa_params); + debug(1,"alsa does%s use CLOCK_MONOLITHIC",alsa_uses_monolithic_clock ? "" : " not"); ret = snd_pcm_sw_params_current(alsa_handle, alsa_swparams); if (ret < 0) { From 984fc90e5c0eeb8cba7556b65806c5caaf16bffb Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 17 Feb 2019 13:00:38 +0000 Subject: [PATCH 496/569] Check further on stall timing --- audio_alsa.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/audio_alsa.c b/audio_alsa.c index e75d9b41..484d6a23 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -461,6 +461,9 @@ int actual_open_alsa_device(void) { alsa_uses_monolithic_clock = snd_pcm_hw_params_is_monotonic (alsa_params); debug(1,"alsa does%s use CLOCK_MONOLITHIC",alsa_uses_monolithic_clock ? "" : " not"); + + debug (1,"sizeof time_t is: %d.", sizeof(time_t)); + debug (1,"sizeof long is: %d.", sizeof(long)); ret = snd_pcm_sw_params_current(alsa_handle, alsa_swparams); if (ret < 0) { @@ -1172,6 +1175,11 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { ", stall_monitor_error_threshold %" PRIX64 ".", time_now_ns, update_timestamp_ns, stall_monitor_start_time, stall_monitor_error_threshold); + debug(2, "DAC seems to have stalled with time_now: %lx,%lx" + ", update_timestamp: %lx,%lx, stall_monitor_start_time %" PRIX64 + ", stall_monitor_error_threshold %" PRIX64 ".", + tn.tv_sec, tn.tv_nsec, update_timestamp.tv_sec, update_timestamp.tv_nsec, stall_monitor_start_time, + stall_monitor_error_threshold); ret = sps_extra_code_output_stalled; } } else { From 9abb349a1da3e8472071735d64dc7d18ac634bd9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 17 Feb 2019 13:13:44 +0000 Subject: [PATCH 497/569] +1 --- audio_alsa.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 484d6a23..1013aaee 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -114,7 +114,7 @@ int frame_size; // in bytes for interleaved stereo int alsa_device_initialised; // boolean to ensure the initialisation is only // done once snd_pcm_t *alsa_handle = NULL; -int alsa_uses_monolithic_clock; +int alsa_uses_monotonic_clock = 0; static snd_pcm_hw_params_t *alsa_params = NULL; static snd_pcm_sw_params_t *alsa_swparams = NULL; static snd_ctl_t *ctl = NULL; @@ -459,8 +459,8 @@ int actual_open_alsa_device(void) { return ret; } - alsa_uses_monolithic_clock = snd_pcm_hw_params_is_monotonic (alsa_params); - debug(1,"alsa does%s use CLOCK_MONOLITHIC",alsa_uses_monolithic_clock ? "" : " not"); + alsa_uses_monotonic_clock = snd_pcm_hw_params_is_monotonic(alsa_params); + debug(1,"alsa_uses_monotonic_clock is %d",alsa_uses_monotonic_clock); debug (1,"sizeof time_t is: %d.", sizeof(time_t)); debug (1,"sizeof long is: %d.", sizeof(long)); From dc8483db14dece7e3853723d07476c0b8736931d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 17 Feb 2019 13:23:34 +0000 Subject: [PATCH 498/569] try using CLOCK_REALTIME -- a bad hack --- audio_alsa.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 484d6a23..2b8d2045 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -114,7 +114,7 @@ int frame_size; // in bytes for interleaved stereo int alsa_device_initialised; // boolean to ensure the initialisation is only // done once snd_pcm_t *alsa_handle = NULL; -int alsa_uses_monolithic_clock; +int alsa_uses_monotonic_clock = 0; static snd_pcm_hw_params_t *alsa_params = NULL; static snd_pcm_sw_params_t *alsa_swparams = NULL; static snd_ctl_t *ctl = NULL; @@ -459,8 +459,8 @@ int actual_open_alsa_device(void) { return ret; } - alsa_uses_monolithic_clock = snd_pcm_hw_params_is_monotonic (alsa_params); - debug(1,"alsa does%s use CLOCK_MONOLITHIC",alsa_uses_monolithic_clock ? "" : " not"); + alsa_uses_monotonic_clock = snd_pcm_hw_params_is_monotonic(alsa_params); + debug(1,"alsa_uses_monotonic_clock is %d",alsa_uses_monotonic_clock); debug (1,"sizeof time_t is: %d.", sizeof(time_t)); debug (1,"sizeof long is: %d.", sizeof(long)); @@ -1158,7 +1158,7 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { if (*state == SND_PCM_STATE_DRAINING) debug(1, "alsa: draining with a delay of %d.", delay); - clock_gettime(CLOCK_MONOTONIC, &tn); + clock_gettime(CLOCK_REALTIME, &tn); uint64_t time_now_ns = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; uint64_t update_timestamp_ns = update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; From 2c8d1dfc711564d2e8ec6297969e760fc71f6b83 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 17 Feb 2019 13:52:38 +0000 Subject: [PATCH 499/569] use CLOCK_REALTIME if alsa is earlier than 1.0.28 --- audio_alsa.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/audio_alsa.c b/audio_alsa.c index 2b8d2045..01882639 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1158,7 +1158,12 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { if (*state == SND_PCM_STATE_DRAINING) debug(1, "alsa: draining with a delay of %d.", delay); +#if SND_LIB_MINOR == 0 && SND_LIB_SUBMINOR < 28 clock_gettime(CLOCK_REALTIME, &tn); +#else + clock_gettime(CLOCK_MONOTONIC, &tn); +#endif + uint64_t time_now_ns = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; uint64_t update_timestamp_ns = update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; From a8038c15c9f408f8db2c067cd06247c712e2aef1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 17 Feb 2019 14:00:10 +0000 Subject: [PATCH 500/569] remove some diagnostics and add in a comment -- using CLOCK_REALTIME with alsa libraries before 1.0.28 --- audio_alsa.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 01882639..52cffe9d 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -114,7 +114,6 @@ int frame_size; // in bytes for interleaved stereo int alsa_device_initialised; // boolean to ensure the initialisation is only // done once snd_pcm_t *alsa_handle = NULL; -int alsa_uses_monotonic_clock = 0; static snd_pcm_hw_params_t *alsa_params = NULL; static snd_pcm_sw_params_t *alsa_swparams = NULL; static snd_ctl_t *ctl = NULL; @@ -459,12 +458,6 @@ int actual_open_alsa_device(void) { return ret; } - alsa_uses_monotonic_clock = snd_pcm_hw_params_is_monotonic(alsa_params); - debug(1,"alsa_uses_monotonic_clock is %d",alsa_uses_monotonic_clock); - - debug (1,"sizeof time_t is: %d.", sizeof(time_t)); - debug (1,"sizeof long is: %d.", sizeof(long)); - ret = snd_pcm_sw_params_current(alsa_handle, alsa_swparams); if (ret < 0) { warn("audio_alsa: Unable to get current sw parameters for device \"%s\": " @@ -1158,6 +1151,8 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { if (*state == SND_PCM_STATE_DRAINING) debug(1, "alsa: draining with a delay of %d.", delay); +// It seems that the alsa library uses CLOCK_REALTIME before 1.0.28, even though +// the check for monotonic returns true. Might have to watch out for this. #if SND_LIB_MINOR == 0 && SND_LIB_SUBMINOR < 28 clock_gettime(CLOCK_REALTIME, &tn); #else From 00be8bc2a6dd29b5d1d71a85ecb6d0467a149f20 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 17 Feb 2019 14:10:45 +0000 Subject: [PATCH 501/569] fix the ability to give attenation priority to hardware or software, set the default to software --- scripts/shairport-sync.conf | 2 +- shairport.c | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index bf6ce4ba..52d3232f 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -29,7 +29,7 @@ general = // volume_control_profile = "standard" ; // use this advanced setting to specify how the airplay volume is transferred to the mixer volume. // "standard" makes the volume change more quickly at lower volumes and slower at higher volumes. // "flat" makes the volume change at the same rate at all volumes. -// volume_range_hw_priority = "yes"; // when extending the volume range by combining the built-in software attenuator with the hardware mixer attenuator, set this to "yes" to reduce volume by using the hardware attenuator first, then the software attenuator. +// volume_range_combined_hardware_priority = "no"; // when extending the volume range by combining the built-in software attenuator with the hardware mixer attenuator, set this to "yes" to reduce volume by using the hardware mixer first, then the built-in software attenuator. // run_this_when_volume_is_set = "/full/path/to/application/and/args"; // Run the specified application whenever the volume control is set or changed. // The desired AirPlay volume is appended to the end of the command line – leave a space if you want it treated as an extra argument. // AirPlay volume goes from 0 to -30 and -144 means "mute". diff --git a/shairport.c b/shairport.c index 4cb316e1..21f4ff52 100644 --- a/shairport.c +++ b/shairport.c @@ -344,8 +344,9 @@ int parse_options(int argc, char **argv) { config.diagnostic_drop_packet_fraction = 0.0; config.active_state_timeout = 10.0; config.volume_range_hw_priority = - 1; // if combining software and hardware volume control, give the hardware priority -// i.e. when reducing volume, reduce the hw first before reducing the software. + 0; // if combining software and hardware volume control, give the software priority +// i.e. when reducing volume, reduce the sw first before reducing the software. +// this is because some hw mixers mute at the bottom of their range, and they don't always advertise this fact #ifdef CONFIG_METADATA_HUB config.cover_art_cache_dir = "/tmp/shairport-sync/.cache/coverart"; @@ -1568,7 +1569,7 @@ int main(int argc, char **argv) { config.volume_range_db); debug( 1, - "combined attenuators (0 -- software is / 1 -- hardware is top of attenuation range) is %d.", + "volume_range_combined_hardware_priority (1 means hardware mixer attenuation is used first) is %d.", config.volume_range_hw_priority); debug(1, "playback_mode is %d (0-stereo, 1-mono, 1-reverse_stereo, 2-both_left, 3-both_right).", config.playback_mode); From 55001a2ab91fcdf6856d0a310ad69d14de7dad42 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 17 Feb 2019 14:41:21 +0000 Subject: [PATCH 502/569] Update RELEASENOTES.md --- RELEASENOTES.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 2b67c5ad..ede9b9cd 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,11 +1,13 @@ -Version 3.3d52 to Version 3.3d40 +Version 3.3d53 to Version 3.3d40 ==== **New Features** * Two new external program/script hooks – `run_this_before_entering_active_state` and `run_this_after_exiting_active_state` are provided for when the system becomes active and when it becomes inactive. Background: Many users use the `run_this_before_play_begins` program hook to turn on an amplifier and the `run_this_after_play_ends` hook to turn it off. A big problem is when another play session starts immediately afterwards. This happens, for example, when a YouTube clip ends and the next one begins, and the time interval can be very short. All this switching can stress electronics and speakers. To get around this, the concept of an *active state* covering a sequence of play sessions is introduced. An active state begins when a play session starts. When the play session ends, the system stays in the active state for a period called the `active_state_timeout`, 10 seconds by default. If another play session starts before that timeout elapses, the system remains in the active state; otherwise the system leaves the active state. * A new `alsa`-only `disable_standby_mode` setting, for controlling the Disable Standby feature, can be set to: `always`, `while_active` or `never`. The `always` setting is recommended for systems where the output device is dedicated to Shairport Sync. The "Disable Standby" state iself can be set or cleared via the D-Bus interface `DisableStandby` property. +* A new command-line option `-u` to direct logging to STDERR as opposed to the system log. Useful when you compile shairport sync without libdaemon, using the `--without-libdaemon` configuration option. +* Use CLOCK_RUNTIME in place of CLOCK_MONOTONIC when doing synchronisation timing using the alsa library prior to 1.0.28. Necessary for compabibility with OpenWRT Barrier Breaker. **Enhancements** -* The volume-control software has been completely rewritten. From a user's point of view, the result should be a much smoother response to volume contol changes, free from artefacts. It is now also possible to combine the hardware mixer and the software attenuator in two ways -- giving priority to the software mixer or giving priority to the hardware mixer. +* The volume-control software has been completely rewritten. From a user's point of view, the result should be a much smoother response to volume contol changes, free from artefacts. It is now also possible to combine the hardware mixer and the software attenuator in two ways -- giving priority to the software mixer or giving priority to the hardware mixer. see the new `volume_range_combined_hardware_priority` setting in the `general` section opf the configuration file. * The muting/unmuting code has been rewritten to be simpler and more consistent. * In the `alsa` backend, new `play()` and `delay()` functions minimise the use of `snd_pcm_recover()` to prevent unnecessary resets of the output DACs. * In the `alsa` backend driver, hardware isn't accessed until the first time it is needed. That is, when Shairport Sync starts up, it no longer needs to access the device momentarily. Instead, it waits for the first use. From 10edce345271eac5c89b1a95866ed0350bc092eb Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 17 Feb 2019 14:42:42 +0000 Subject: [PATCH 503/569] Update configure.ac Add `-u` option and improve compatibility with older `alsa` libraries. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index f8c72f71..30849f33 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.3d52], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d53], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 78685b852820e00558d34effa9f828095824126a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 19 Feb 2019 11:13:17 +0000 Subject: [PATCH 504/569] Big update to audio_jack.c and other stuff --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 30849f33..e59688ef 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.3d53], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d54], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From cbbfe83f3beb72af04c78699c3acd8be742f92aa Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 19 Feb 2019 11:18:13 +0000 Subject: [PATCH 505/569] Update RELEASENOTES.md --- RELEASENOTES.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ede9b9cd..0e3928d6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,12 +1,12 @@ -Version 3.3d53 to Version 3.3d40 +Version 3.3d54 to Version 3.3d40 ==== **New Features** * Two new external program/script hooks – `run_this_before_entering_active_state` and `run_this_after_exiting_active_state` are provided for when the system becomes active and when it becomes inactive. Background: Many users use the `run_this_before_play_begins` program hook to turn on an amplifier and the `run_this_after_play_ends` hook to turn it off. A big problem is when another play session starts immediately afterwards. This happens, for example, when a YouTube clip ends and the next one begins, and the time interval can be very short. All this switching can stress electronics and speakers. To get around this, the concept of an *active state* covering a sequence of play sessions is introduced. An active state begins when a play session starts. When the play session ends, the system stays in the active state for a period called the `active_state_timeout`, 10 seconds by default. If another play session starts before that timeout elapses, the system remains in the active state; otherwise the system leaves the active state. * A new `alsa`-only `disable_standby_mode` setting, for controlling the Disable Standby feature, can be set to: `always`, `while_active` or `never`. The `always` setting is recommended for systems where the output device is dedicated to Shairport Sync. The "Disable Standby" state iself can be set or cleared via the D-Bus interface `DisableStandby` property. -* A new command-line option `-u` to direct logging to STDERR as opposed to the system log. Useful when you compile shairport sync without libdaemon, using the `--without-libdaemon` configuration option. -* Use CLOCK_RUNTIME in place of CLOCK_MONOTONIC when doing synchronisation timing using the alsa library prior to 1.0.28. Necessary for compabibility with OpenWRT Barrier Breaker. +* A new command-line option, `-u`, directs logging to STDERR rahter than the system log. Useful when you compile Shairport Sync without `libdaemon` using the `--without-libdaemon` configuration option. **Enhancements** +* Changes to the Jack Audio back end. The audio_jack.c back end for jack audio has been extensively rewritten by [Jörn Nettingsmeier](https://github.com/nettings) in a way that is more in keeping with the jack audio style. It uses native jack lockless buffers and offers autoconnect facilities that the previous version didn't have. Many thanks to him. * The volume-control software has been completely rewritten. From a user's point of view, the result should be a much smoother response to volume contol changes, free from artefacts. It is now also possible to combine the hardware mixer and the software attenuator in two ways -- giving priority to the software mixer or giving priority to the hardware mixer. see the new `volume_range_combined_hardware_priority` setting in the `general` section opf the configuration file. * The muting/unmuting code has been rewritten to be simpler and more consistent. * In the `alsa` backend, new `play()` and `delay()` functions minimise the use of `snd_pcm_recover()` to prevent unnecessary resets of the output DACs. @@ -16,6 +16,7 @@ Version 3.3d53 to Version 3.3d40 **Bug Fixes** * The `mdns-external` method used to advertise the Shairport Sync AirPlay service on ZeroConf is now an optional build and is omitted by default. Previously it was included with `--with-avahi` and could not be included on its own. * A number of memory leaks associated with the use of OpenSSL have been fixed. +* Use CLOCK_RUNTIME in place of CLOCK_MONOTONIC when doing synchronisation timing using the alsa library prior to 1.0.28. Necessary for compabibility with OpenWRT Barrier Breaker. Version 3.3d39 to Version 3.3d38 ==== From dace63180cf78c6cd1b4300c7a71701b0ebe3491 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 19 Feb 2019 11:18:56 +0000 Subject: [PATCH 506/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0e3928d6..6ce03503 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,7 +1,7 @@ Version 3.3d54 to Version 3.3d40 ==== **New Features** -* Two new external program/script hooks – `run_this_before_entering_active_state` and `run_this_after_exiting_active_state` are provided for when the system becomes active and when it becomes inactive. Background: Many users use the `run_this_before_play_begins` program hook to turn on an amplifier and the `run_this_after_play_ends` hook to turn it off. A big problem is when another play session starts immediately afterwards. This happens, for example, when a YouTube clip ends and the next one begins, and the time interval can be very short. All this switching can stress electronics and speakers. To get around this, the concept of an *active state* covering a sequence of play sessions is introduced. An active state begins when a play session starts. When the play session ends, the system stays in the active state for a period called the `active_state_timeout`, 10 seconds by default. If another play session starts before that timeout elapses, the system remains in the active state; otherwise the system leaves the active state. +* Two new external program/script hooks – `run_this_before_entering_active_state` and `run_this_after_exiting_active_state` are provided for when the system goes active or inactive. Background: Many users use the `run_this_before_play_begins` program hook to turn on an amplifier and the `run_this_after_play_ends` hook to turn it off. A big problem is when another play session starts immediately afterwards. This happens, for example, when a YouTube clip ends and the next one begins, and the time interval can be very short. All this switching can stress electronics and speakers. To get around this, the concept of an *active state* covering a sequence of play sessions is introduced. An active state begins when a play session starts. When the play session ends, the system stays in the active state for a period called the `active_state_timeout`, 10 seconds by default. If another play session starts before that timeout elapses, the system remains in the active state; otherwise the system leaves the active state. * A new `alsa`-only `disable_standby_mode` setting, for controlling the Disable Standby feature, can be set to: `always`, `while_active` or `never`. The `always` setting is recommended for systems where the output device is dedicated to Shairport Sync. The "Disable Standby" state iself can be set or cleared via the D-Bus interface `DisableStandby` property. * A new command-line option, `-u`, directs logging to STDERR rahter than the system log. Useful when you compile Shairport Sync without `libdaemon` using the `--without-libdaemon` configuration option. From 9c6aa4b0697723c71385405534a573b2d6565a5a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 19 Feb 2019 11:22:12 +0000 Subject: [PATCH 507/569] Update RELEASENOTES.md --- RELEASENOTES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6ce03503..7905256a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,9 +1,9 @@ Version 3.3d54 to Version 3.3d40 ==== **New Features** -* Two new external program/script hooks – `run_this_before_entering_active_state` and `run_this_after_exiting_active_state` are provided for when the system goes active or inactive. Background: Many users use the `run_this_before_play_begins` program hook to turn on an amplifier and the `run_this_after_play_ends` hook to turn it off. A big problem is when another play session starts immediately afterwards. This happens, for example, when a YouTube clip ends and the next one begins, and the time interval can be very short. All this switching can stress electronics and speakers. To get around this, the concept of an *active state* covering a sequence of play sessions is introduced. An active state begins when a play session starts. When the play session ends, the system stays in the active state for a period called the `active_state_timeout`, 10 seconds by default. If another play session starts before that timeout elapses, the system remains in the active state; otherwise the system leaves the active state. +* Two new external program/script hooks – `run_this_before_entering_active_state` and `run_this_after_exiting_active_state` are provided for when the system goes active or inactive. Background: Many users use the `run_this_before_play_begins` program hook to turn on an amplifier and the `run_this_after_play_ends` hook to turn it off. A big problem is when another play session starts immediately afterwards. This happens, for example, when a YouTube clip ends and the next one begins, and the time interval can be very short. All this switching can stress electronics and speakers. To get around this, the concept of an *active state* covering a sequence of play sessions is introduced. An active state begins when a play session starts. When the play session ends, the system stays in the active state for a period called the `active_state_timeout`, 10 seconds by default. If another play session starts before that timeout elapses, the system remains in the active state; otherwise the system leaves the active state. The two new hooks mentioned above can be used to execute programs/scripts when the system goes active or inactive. * A new `alsa`-only `disable_standby_mode` setting, for controlling the Disable Standby feature, can be set to: `always`, `while_active` or `never`. The `always` setting is recommended for systems where the output device is dedicated to Shairport Sync. The "Disable Standby" state iself can be set or cleared via the D-Bus interface `DisableStandby` property. -* A new command-line option, `-u`, directs logging to STDERR rahter than the system log. Useful when you compile Shairport Sync without `libdaemon` using the `--without-libdaemon` configuration option. +* A new command-line option, `-u`, directs logging to STDERR rather than the system log. Useful when you compile Shairport Sync without `libdaemon` using the `--without-libdaemon` configuration option. **Enhancements** * Changes to the Jack Audio back end. The audio_jack.c back end for jack audio has been extensively rewritten by [Jörn Nettingsmeier](https://github.com/nettings) in a way that is more in keeping with the jack audio style. It uses native jack lockless buffers and offers autoconnect facilities that the previous version didn't have. Many thanks to him. From 136719e489618bc0015ff1ce1aec38aeb7da25a4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 19 Feb 2019 11:22:49 +0000 Subject: [PATCH 508/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7905256a..e78591ed 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,7 +2,7 @@ Version 3.3d54 to Version 3.3d40 ==== **New Features** * Two new external program/script hooks – `run_this_before_entering_active_state` and `run_this_after_exiting_active_state` are provided for when the system goes active or inactive. Background: Many users use the `run_this_before_play_begins` program hook to turn on an amplifier and the `run_this_after_play_ends` hook to turn it off. A big problem is when another play session starts immediately afterwards. This happens, for example, when a YouTube clip ends and the next one begins, and the time interval can be very short. All this switching can stress electronics and speakers. To get around this, the concept of an *active state* covering a sequence of play sessions is introduced. An active state begins when a play session starts. When the play session ends, the system stays in the active state for a period called the `active_state_timeout`, 10 seconds by default. If another play session starts before that timeout elapses, the system remains in the active state; otherwise the system leaves the active state. The two new hooks mentioned above can be used to execute programs/scripts when the system goes active or inactive. -* A new `alsa`-only `disable_standby_mode` setting, for controlling the Disable Standby feature, can be set to: `always`, `while_active` or `never`. The `always` setting is recommended for systems where the output device is dedicated to Shairport Sync. The "Disable Standby" state iself can be set or cleared via the D-Bus interface `DisableStandby` property. +* A new `alsa`-only `disable_standby_mode` setting, for controlling the Disable Standby feature, can be set to `always`, `while_active` or `never`. The `always` setting is recommended for systems where the output device is dedicated to Shairport Sync. The "Disable Standby" state iself can be set or cleared via the D-Bus interface `DisableStandby` property. * A new command-line option, `-u`, directs logging to STDERR rather than the system log. Useful when you compile Shairport Sync without `libdaemon` using the `--without-libdaemon` configuration option. **Enhancements** From 56235c934fbf6727c1c60e0a9e3724a724105d2f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 19 Feb 2019 11:24:05 +0000 Subject: [PATCH 509/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e78591ed..556e5ed1 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -6,7 +6,7 @@ Version 3.3d54 to Version 3.3d40 * A new command-line option, `-u`, directs logging to STDERR rather than the system log. Useful when you compile Shairport Sync without `libdaemon` using the `--without-libdaemon` configuration option. **Enhancements** -* Changes to the Jack Audio back end. The audio_jack.c back end for jack audio has been extensively rewritten by [Jörn Nettingsmeier](https://github.com/nettings) in a way that is more in keeping with the jack audio style. It uses native jack lockless buffers and offers autoconnect facilities that the previous version didn't have. Many thanks to him. +* Changes to the Jack Audio back end. The back end for jack audio, `audio_jack.c`, has been extensively rewritten by [Jörn Nettingsmeier](https://github.com/nettings) in a way that is more in keeping with the Jack Audio style. It uses native jack lockless buffers and offers autoconnect facilities that the previous version didn't have. Many thanks to him. * The volume-control software has been completely rewritten. From a user's point of view, the result should be a much smoother response to volume contol changes, free from artefacts. It is now also possible to combine the hardware mixer and the software attenuator in two ways -- giving priority to the software mixer or giving priority to the hardware mixer. see the new `volume_range_combined_hardware_priority` setting in the `general` section opf the configuration file. * The muting/unmuting code has been rewritten to be simpler and more consistent. * In the `alsa` backend, new `play()` and `delay()` functions minimise the use of `snd_pcm_recover()` to prevent unnecessary resets of the output DACs. From d3d86d61922254bd58d212b69403969dcd01fae5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 20 Feb 2019 18:43:10 +0000 Subject: [PATCH 510/569] Try to get a private connection to the system D-Bus --- dbus-service.c | 126 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 85 insertions(+), 41 deletions(-) diff --git a/dbus-service.c b/dbus-service.c index a1cde3fd..a51df7f6 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -20,6 +20,7 @@ int service_is_running = 0; ShairportSyncDiagnostics *shairportSyncDiagnosticsSkeleton = NULL; ShairportSyncRemoteControl *shairportSyncRemoteControlSkeleton = NULL; ShairportSyncAdvancedRemoteControl *shairportSyncAdvancedRemoteControlSkeleton = NULL; +GDBusConnection *ourBusConnection = NULL; guint ownerID = 0; @@ -433,12 +434,14 @@ gboolean notify_drift_tolerance_callback(ShairportSync *skeleton, } gboolean notify_disable_standby_mode_callback(ShairportSync *skeleton, - __attribute__((unused)) gpointer user_data) { + __attribute__((unused)) gpointer user_data) { char *th = (char *)shairport_sync_get_disable_standby_mode(skeleton); - if ((strcasecmp(th, "no") == 0) || (strcasecmp(th, "off") == 0) || (strcasecmp(th, "never") == 0)) { + if ((strcasecmp(th, "no") == 0) || (strcasecmp(th, "off") == 0) || + (strcasecmp(th, "never") == 0)) { config.disable_standby_mode = disable_standby_off; config.keep_dac_busy = 0; - } else if ((strcasecmp(th, "yes") == 0) || (strcasecmp(th, "on") == 0) || (strcasecmp(th, "always") == 0)) { + } else if ((strcasecmp(th, "yes") == 0) || (strcasecmp(th, "on") == 0) || + (strcasecmp(th, "always") == 0)) { config.disable_standby_mode = disable_standby_always; config.keep_dac_busy = 1; } else if (strcasecmp(th, "while_active") == 0) @@ -446,17 +449,17 @@ gboolean notify_disable_standby_mode_callback(ShairportSync *skeleton, else { warn("An unrecognised disable_standby_mode: \"%s\" was requested via D-Bus interface.", th); switch (config.disable_standby_mode) { - case disable_standby_off: - shairport_sync_set_disable_standby_mode(skeleton, "off"); - break; - case disable_standby_always: - shairport_sync_set_disable_standby_mode(skeleton, "always"); - break; - case disable_standby_while_active: - shairport_sync_set_disable_standby_mode(skeleton, "while_active"); - break; - default: - break; + case disable_standby_off: + shairport_sync_set_disable_standby_mode(skeleton, "off"); + break; + case disable_standby_always: + shairport_sync_set_disable_standby_mode(skeleton, "always"); + break; + case disable_standby_while_active: + shairport_sync_set_disable_standby_mode(skeleton, "while_active"); + break; + default: + break; } } return TRUE; @@ -656,7 +659,7 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name G_CALLBACK(notify_alacdecoder_callback), NULL); g_signal_connect(shairportSyncSkeleton, "notify::disable-standby-mode", G_CALLBACK(notify_disable_standby_mode_callback), NULL); - g_signal_connect(shairportSyncSkeleton, "notify::volume-control-profile", + g_signal_connect(shairportSyncSkeleton, "notify::volume-control-profile", G_CALLBACK(notify_volume_control_profile_callback), NULL); g_signal_connect(shairportSyncSkeleton, "notify::disable-standby", G_CALLBACK(notify_disable_standby_callback), NULL); @@ -744,21 +747,21 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name debug(1, ">> Active set to \"false\""); switch (config.disable_standby_mode) { - case disable_standby_off: - shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "off"); - debug(1, ">> disable standby mode set to \"off\""); - break; - case disable_standby_always: - shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "always"); - debug(1, ">> disable standby mode set to \"always\""); - break; - case disable_standby_while_active: - shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "while_active"); - debug(1, ">> disable standby mode set to \"while_active\""); - break; - default: - debug(1,"invalid disable_standby mode!"); - break; + case disable_standby_off: + shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "off"); + debug(1, ">> disable standby mode set to \"off\""); + break; + case disable_standby_always: + shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "always"); + debug(1, ">> disable standby mode set to \"always\""); + break; + case disable_standby_while_active: + shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "while_active"); + debug(1, ">> disable standby mode set to \"while_active\""); + break; + default: + debug(1, "invalid disable_standby mode!"); + break; } #ifdef CONFIG_SOXR @@ -846,6 +849,31 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name service_is_running = 1; } +// from http://www.ccp4.ac.uk/dist/checkout/glib-2.34.3/gio/tests/gdbus-tests.c +/* ---------------------------------------------------------------------------------------------------- + */ + +GDBusConnection *_g_bus_get_priv(GBusType bus_type, GCancellable *cancellable, GError **error) { + gchar *address; + GDBusConnection *ret; + + ret = NULL; + + address = g_dbus_address_get_for_bus_sync(bus_type, cancellable, error); + if (address == NULL) + goto out; + + ret = g_dbus_connection_new_for_address_sync(address, + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | + G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, + NULL, /* GDBusAuthObserver */ + cancellable, error); + g_free(address); + +out: + return ret; +} + static void on_dbus_name_lost_again(__attribute__((unused)) GDBusConnection *connection, __attribute__((unused)) const gchar *name, __attribute__((unused)) gpointer user_data) { @@ -863,13 +891,17 @@ static void on_dbus_name_lost(__attribute__((unused)) GDBusConnection *connectio pid_t pid = getpid(); char interface_name[256] = ""; snprintf(interface_name, sizeof(interface_name), "org.gnome.ShairportSync.i%d", pid); - GBusType dbus_bus_type = G_BUS_TYPE_SYSTEM; - if (config.dbus_service_bus_type == DBT_session) - dbus_bus_type = G_BUS_TYPE_SESSION; + // GBusType dbus_bus_type = G_BUS_TYPE_SYSTEM; + // if (config.dbus_service_bus_type == DBT_session) + // dbus_bus_type = G_BUS_TYPE_SESSION; // debug(1, "Looking for a Shairport Sync native D-Bus interface \"%s\" on the %s bus.", // interface_name,(config.dbus_service_bus_type == DBT_session) ? "session" : "system"); - g_bus_own_name(dbus_bus_type, interface_name, G_BUS_NAME_OWNER_FLAGS_NONE, NULL, - on_dbus_name_acquired, on_dbus_name_lost_again, NULL, NULL); + ownerID = + g_bus_own_name_on_connection(ourBusConnection, interface_name, G_BUS_NAME_OWNER_FLAGS_REPLACE, + on_dbus_name_acquired, on_dbus_name_lost_again, NULL, NULL); + + // g_bus_own_name(dbus_bus_type, interface_name, G_BUS_NAME_OWNER_FLAGS_NONE, NULL, + // on_dbus_name_acquired, on_dbus_name_lost_again, NULL, NULL); } int start_dbus_service() { @@ -879,8 +911,18 @@ int start_dbus_service() { dbus_bus_type = G_BUS_TYPE_SESSION; // debug(1, "Looking for a Shairport Sync native D-Bus interface \"org.gnome.ShairportSync\" on // the %s bus.",(config.dbus_service_bus_type == DBT_session) ? "session" : "system"); - ownerID = g_bus_own_name(dbus_bus_type, "org.gnome.ShairportSync", G_BUS_NAME_OWNER_FLAGS_NONE, - NULL, on_dbus_name_acquired, on_dbus_name_lost, NULL, NULL); + + ourBusConnection = _g_bus_get_priv(dbus_bus_type, NULL, NULL); + if (ourBusConnection) { + debug(1, "Got a connection to the bus."); + g_dbus_connection_set_exit_on_close(ourBusConnection, FALSE); + ownerID = g_bus_own_name_on_connection(ourBusConnection, "org.gnome.ShairportSync", + G_BUS_NAME_OWNER_FLAGS_REPLACE, on_dbus_name_acquired, + on_dbus_name_lost, NULL, NULL); + } + + // ownerID = g_bus_own_name(dbus_bus_type, "org.gnome.ShairportSync", G_BUS_NAME_OWNER_FLAGS_NONE, + // NULL, on_dbus_name_acquired, on_dbus_name_lost, NULL, NULL); return 0; // this is just to quieten a compiler warning } @@ -890,9 +932,11 @@ void stop_dbus_service() { g_bus_unown_name(ownerID); else debug(1, "Zero OwnerID for \"org.gnome.ShairportSync\"."); - service_is_running = 0; + if (ourBusConnection) + g_object_unref(ourBusConnection); + else + debug(1, "NULL BusConnection for \"org.gnome.ShairportSync\"."); + service_is_running = 0; } -int dbus_service_is_running() { - return service_is_running; -} +int dbus_service_is_running() { return service_is_running; } From 8ea68614945d6412a4f46bf210e82c9facc63dd6 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 13:20:31 +0000 Subject: [PATCH 511/569] Update CYGWIN.md --- CYGWIN.md | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/CYGWIN.md b/CYGWIN.md index a177ead5..143624ce 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -1,28 +1,29 @@ -Installing to Cygwin +Installing to Cygwin V2 ---- -This is based on installing onto a fresh default installation of Cygwin 2.4.1 (64-bit installation) running in Windows 10 -inside VMWare Fusion on a Mac. +This is based on installing onto a fresh default installation of Cygwin 2.895 (64-bit installation) running in Windows 10 +inside VMWare Fusion on a Mac. -Note: to enable Cygwin to resolve domain names, it was found necessary to go to Windows `Control Panel` > `Network and Internet` > `Network Connections` > -`Ethernet0` > `Properties` > `Internet Protocol Version 4 (TCP/IPv4)` > `Properties`, select `Use the following DNS server addresses` -and enter valid DNS server addresses, e.g. `8.8.8.8`. +The end result is Cygwin Shairport Sync, an AirPlay facility for Windows. + +Setting up Windows +==== +Set up Windows 10 and install all updates. Next, you must install the `Bonjour Service`, available from Apple in an installer called "Bonjour Print Services for Windows v2.0.2". + +* Download and run `Bonjour Print Services for Windows v2.0.2` +* After accepting conditions, the installer will do a preliminary installion of the Bonjour Service and will then pause, inviting you to install Bonjour Print Services. You can decline to do this, as the Bonjour Service will have been installed. + +Setting up Cygwin +==== +* Download the Cygwin installer from the [official website](https://cygwin.com/install.html). Save the installer in the Downloads folder. + +* Open a Window Command Prompt and enter the following (long!) command: +``` +Downloads\setup-x86_64.exe -P cygrunsrv,dbus,avahi,avahi-tools,gnome-keyring,libavahi-client-devel,libglib2.0-devel,openssl,pkg-config,autoconf,automake,clang,libdaemon-devel,popt-devel,make,libao-devel,openssl-devel,libtool,git,wget,flex,bison +``` +This will do a complete installation of Cygwin and all necessary packages. +* Set up the D-Bus and Avahi Services: -* Use `Cygwin Setup` to install the following packages: - * `pkg-config` - * `autoconf` - * `automake` - * `clang` - * `libdaemon-devel` - * `popt-devel` - * `make` - * `libao-devel` - * `openssl-devel` - * `libtool` - * `git` - * `wget` for convenience, - * `flex` for compiling `libconfig` - * `bison` for compiling `libconfig` * Download, configure, compile and install `libconfig`: ``` @@ -39,8 +40,7 @@ $ cd .. $ git clone https://github.com/mikebrady/shairport-sync.git $ cd shairport-sync $ autoreconf -fi -$ PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./configure --with-ao --with-ssl=openssl --with-tinysvcmdns +$ PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./configure --with-ao --with-ssl=openssl --with-avahi --with-dbus-interface --sysconfdir=/etc $ make -$ make install ``` * That's it. There should be a `shairport-sync.exe` file in your directory. From fb4985475b2ec499b6ce3c520b36fede2412cda7 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 13:22:27 +0000 Subject: [PATCH 512/569] Update CYGWIN.md --- CYGWIN.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CYGWIN.md b/CYGWIN.md index 143624ce..8f2e96bf 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -8,7 +8,7 @@ The end result is Cygwin Shairport Sync, an AirPlay facility for Windows. Setting up Windows ==== -Set up Windows 10 and install all updates. Next, you must install the `Bonjour Service`, available from Apple in an installer called "Bonjour Print Services for Windows v2.0.2". +Set up Windows 10 and install all updates. Next, install the `Bonjour Service`, available from Apple in an installer called "Bonjour Print Services for Windows v2.0.2". * Download and run `Bonjour Print Services for Windows v2.0.2` * After accepting conditions, the installer will do a preliminary installion of the Bonjour Service and will then pause, inviting you to install Bonjour Print Services. You can decline to do this, as the Bonjour Service will have been installed. @@ -24,6 +24,7 @@ Downloads\setup-x86_64.exe -P cygrunsrv,dbus,avahi,avahi-tools,gnome-keyring,lib This will do a complete installation of Cygwin and all necessary packages. * Set up the D-Bus and Avahi Services: +TBA * Download, configure, compile and install `libconfig`: ``` From 1310ea20efe3162181fa62a64a634d8a3bc6e3bf Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 14:46:41 +0000 Subject: [PATCH 513/569] Update CYGWIN.md --- CYGWIN.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/CYGWIN.md b/CYGWIN.md index 8f2e96bf..467392e7 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -13,6 +13,9 @@ Set up Windows 10 and install all updates. Next, install the `Bonjour Service`, * Download and run `Bonjour Print Services for Windows v2.0.2` * After accepting conditions, the installer will do a preliminary installion of the Bonjour Service and will then pause, inviting you to install Bonjour Print Services. You can decline to do this, as the Bonjour Service will have been installed. +* Check Bonjour Service is running. In Windows, open the "Services" application and ensure that you can see `Bonjour Services` running. + + Setting up Cygwin ==== * Download the Cygwin installer from the [official website](https://cygwin.com/install.html). Save the installer in the Downloads folder. @@ -24,7 +27,17 @@ Downloads\setup-x86_64.exe -P cygrunsrv,dbus,avahi,avahi-tools,gnome-keyring,lib This will do a complete installation of Cygwin and all necessary packages. * Set up the D-Bus and Avahi Services: -TBA +Open a `Cygwin64 Terminal` window in Administrator mode. Enter the following command: +``` +$ messagebus-config +``` +Enter `yes` for all queries. Next, open the Windows "Services" application (if it's already open, refresh the screen contents) and look for the `CYGWIN D-Bus system service`. Open it and start it. + +Next, open (or return to) a `Cygwin64 Terminal` window in Administrator mode. Enter the following command: +``` +$ /usr/sbin/avahi-daemon-config +``` +Enter `yes` for all queries. Next, open the Windows "Services" application (if it's already open, refresh the screen contents) and look for the `CYGWIN Avahi service`. Open it and start it. * Download, configure, compile and install `libconfig`: ``` From 60ab0a7321d21c1072ea3abfb05274b9924f7240 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 14:47:51 +0000 Subject: [PATCH 514/569] Update CYGWIN.md --- CYGWIN.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CYGWIN.md b/CYGWIN.md index 467392e7..5738ab79 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -1,4 +1,4 @@ -Installing to Cygwin V2 +Installing to Cygwin ---- This is based on installing onto a fresh default installation of Cygwin 2.895 (64-bit installation) running in Windows 10 @@ -6,6 +6,8 @@ inside VMWare Fusion on a Mac. The end result is Cygwin Shairport Sync, an AirPlay facility for Windows. +The is not, as yet, an installer for Shairport Sync in CYGWIN. + Setting up Windows ==== Set up Windows 10 and install all updates. Next, install the `Bonjour Service`, available from Apple in an installer called "Bonjour Print Services for Windows v2.0.2". From 4b51d1bc89df8183bf1ef7d2b4259256dd114cc3 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 14:52:55 +0000 Subject: [PATCH 515/569] Update CYGWIN.md --- CYGWIN.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CYGWIN.md b/CYGWIN.md index 5738ab79..defa1692 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -24,7 +24,9 @@ Setting up Cygwin * Open a Window Command Prompt and enter the following (long!) command: ``` -Downloads\setup-x86_64.exe -P cygrunsrv,dbus,avahi,avahi-tools,gnome-keyring,libavahi-client-devel,libglib2.0-devel,openssl,pkg-config,autoconf,automake,clang,libdaemon-devel,popt-devel,make,libao-devel,openssl-devel,libtool,git,wget,flex,bison +Downloads\setup-x86_64.exe -P cygrunsrv,dbus,avahi,avahi-tools,gnome-keyring,libavahi-client-devel, ^ +libglib2.0-devel,openssl,pkg-config,autoconf,automake,clang,libdaemon-devel,popt-devel,make,libao-devel, ^ +openssl-devel,libtool,git,wget,flex,bison ``` This will do a complete installation of Cygwin and all necessary packages. * Set up the D-Bus and Avahi Services: From 9a9b302b6b80dfe2a9afaa0e4fa97e2c96658af7 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 14:53:44 +0000 Subject: [PATCH 516/569] Update CYGWIN.md --- CYGWIN.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CYGWIN.md b/CYGWIN.md index defa1692..e1174d4e 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -25,8 +25,8 @@ Setting up Cygwin * Open a Window Command Prompt and enter the following (long!) command: ``` Downloads\setup-x86_64.exe -P cygrunsrv,dbus,avahi,avahi-tools,gnome-keyring,libavahi-client-devel, ^ -libglib2.0-devel,openssl,pkg-config,autoconf,automake,clang,libdaemon-devel,popt-devel,make,libao-devel, ^ -openssl-devel,libtool,git,wget,flex,bison +libglib2.0-devel,openssl,pkg-config,autoconf,automake,clang,libdaemon-devel,popt-devel, ^ +make,libao-devel,openssl-devel,libtool,git,wget,flex,bison ``` This will do a complete installation of Cygwin and all necessary packages. * Set up the D-Bus and Avahi Services: @@ -58,7 +58,8 @@ $ cd .. $ git clone https://github.com/mikebrady/shairport-sync.git $ cd shairport-sync $ autoreconf -fi -$ PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./configure --with-ao --with-ssl=openssl --with-avahi --with-dbus-interface --sysconfdir=/etc +$ PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./configure --with-ao --with-ssl=openssl \ + --with-avahi --with-dbus-interface --sysconfdir=/etc $ make ``` * That's it. There should be a `shairport-sync.exe` file in your directory. From 2e6a1a819e22472586281a4e83836f2d130e6ddc Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 14:57:20 +0000 Subject: [PATCH 517/569] Update CYGWIN.md --- CYGWIN.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CYGWIN.md b/CYGWIN.md index e1174d4e..0bef5cef 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -24,7 +24,7 @@ Setting up Cygwin * Open a Window Command Prompt and enter the following (long!) command: ``` -Downloads\setup-x86_64.exe -P cygrunsrv,dbus,avahi,avahi-tools,gnome-keyring,libavahi-client-devel, ^ +C:\Users\mike> Downloads\setup-x86_64.exe -P cygrunsrv,dbus,avahi,avahi-tools,gnome-keyring,libavahi-client-devel, ^ libglib2.0-devel,openssl,pkg-config,autoconf,automake,clang,libdaemon-devel,popt-devel, ^ make,libao-devel,openssl-devel,libtool,git,wget,flex,bison ``` @@ -39,7 +39,7 @@ Enter `yes` for all queries. Next, open the Windows "Services" application (if i Next, open (or return to) a `Cygwin64 Terminal` window in Administrator mode. Enter the following command: ``` -$ /usr/sbin/avahi-daemon-config +$ /usr/sbin/avahi-daemon-config ``` Enter `yes` for all queries. Next, open the Windows "Services" application (if it's already open, refresh the screen contents) and look for the `CYGWIN Avahi service`. Open it and start it. From 3ecd3034eca387f6439de86a36b9aa575ca2b8b2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 15:00:04 +0000 Subject: [PATCH 518/569] Update CYGWIN.md --- CYGWIN.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CYGWIN.md b/CYGWIN.md index 0bef5cef..0838c5b6 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -1,5 +1,5 @@ Installing to Cygwin ----- +==== This is based on installing onto a fresh default installation of Cygwin 2.895 (64-bit installation) running in Windows 10 inside VMWare Fusion on a Mac. @@ -9,7 +9,7 @@ The end result is Cygwin Shairport Sync, an AirPlay facility for Windows. The is not, as yet, an installer for Shairport Sync in CYGWIN. Setting up Windows -==== +---- Set up Windows 10 and install all updates. Next, install the `Bonjour Service`, available from Apple in an installer called "Bonjour Print Services for Windows v2.0.2". * Download and run `Bonjour Print Services for Windows v2.0.2` @@ -19,7 +19,7 @@ Set up Windows 10 and install all updates. Next, install the `Bonjour Service`, Setting up Cygwin -==== +---- * Download the Cygwin installer from the [official website](https://cygwin.com/install.html). Save the installer in the Downloads folder. * Open a Window Command Prompt and enter the following (long!) command: @@ -43,6 +43,8 @@ $ /usr/sbin/avahi-daemon-config ``` Enter `yes` for all queries. Next, open the Windows "Services" application (if it's already open, refresh the screen contents) and look for the `CYGWIN Avahi service`. Open it and start it. +The `libconfig` Library +---- * Download, configure, compile and install `libconfig`: ``` $ git clone https://github.com/hyperrealm/libconfig.git @@ -53,6 +55,9 @@ $ make $ make install $ cd .. ``` + +Shairport Sync +---- * Next, download, configure and compile Shairport Sync: ``` $ git clone https://github.com/mikebrady/shairport-sync.git From ed395cb29252b3fcad4fe76daf4bcd6b7254c99b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 15:00:52 +0000 Subject: [PATCH 519/569] Update CYGWIN.md --- CYGWIN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CYGWIN.md b/CYGWIN.md index 0838c5b6..79ecb606 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -4,7 +4,7 @@ Installing to Cygwin This is based on installing onto a fresh default installation of Cygwin 2.895 (64-bit installation) running in Windows 10 inside VMWare Fusion on a Mac. -The end result is Cygwin Shairport Sync, an AirPlay facility for Windows. +The end result will be `Cygwin Shairport Sync`, an AirPlay service for Windows. The is not, as yet, an installer for Shairport Sync in CYGWIN. From 623b9b4b9feb10e857a3384a9a919b03bbad9837 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 16:10:30 +0000 Subject: [PATCH 520/569] Add a CYGWIN installer. --- Makefile.am | 11 ++ configure.ac | 4 + scripts/shairport-sync-config | 169 ++++++++++++++++++ .../shairport-sync-dbus-policy-cygwin.conf | 18 ++ .../shairport-sync-mpris-policy-cygwin.conf | 18 ++ 5 files changed, 220 insertions(+) create mode 100755 scripts/shairport-sync-config create mode 100644 scripts/shairport-sync-dbus-policy-cygwin.conf create mode 100644 scripts/shairport-sync-mpris-policy-cygwin.conf diff --git a/Makefile.am b/Makefile.am index f6d2fdf7..e17bf39a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -163,12 +163,20 @@ if INSTALL_CONFIG_FILES cp scripts/shairport-sync.conf $(DESTDIR)$(sysconfdir)/shairport-sync.conf.sample [ -f $(DESTDIR)$(sysconfdir)/shairport-sync.conf ] || cp scripts/shairport-sync.conf $(DESTDIR)$(sysconfdir)/shairport-sync.conf if USE_DBUS +if INSTALL_CYGWIN_SERVICE + cp scripts/shairport-sync-dbus-policy-cygwin.conf $(DESTDIR)$(sysconfdir)/dbus-1/system.d/shairport-sync-dbus.conf +else cp scripts/shairport-sync-dbus-policy.conf $(DESTDIR)$(sysconfdir)/dbus-1/system.d/shairport-sync-dbus.conf endif +endif if USE_MPRIS +if INSTALL_CYGWIN_SERVICE + cp scripts/shairport-sync-mpris-policy-cygwin.conf $(DESTDIR)$(sysconfdir)/dbus-1/system.d/shairport-sync-dbus.conf +else cp scripts/shairport-sync-mpris-policy.conf $(DESTDIR)$(sysconfdir)/dbus-1/system.d/shairport-sync-mpris.conf endif endif +endif if INSTALL_SYSTEMV getent group shairport-sync &>/dev/null || groupadd -r shairport-sync >/dev/null getent passwd shairport-sync &> /dev/null || useradd -r -M -g shairport-sync -s /usr/bin/nologin -G audio shairport-sync >/dev/null @@ -189,3 +197,6 @@ if INSTALL_FREEBSD_SERVICE [ -f /usr/local/etc/rc.d/shairport_sync ] || cp scripts/shairport-sync.freebsd /usr/local/etc/rc.d/shairport_sync chmod 555 /usr/local/etc/rc.d/shairport_sync endif +if INSTALL_CYGWIN_SERVICE + cp scripts/shairport-sync-config /usr/local/bin +endif diff --git a/configure.ac b/configure.ac index e59688ef..24c85b90 100644 --- a/configure.ac +++ b/configure.ac @@ -115,6 +115,10 @@ AC_ARG_WITH([freebsd-service], [ --with-freebsd-service = install a FreeBSD startup script during a make install], , ) AM_CONDITIONAL([INSTALL_FREEBSD_SERVICE], [test "x$with_freebsd_service" = "xyes"]) +AC_ARG_WITH([cygwin-service], +[ --with-cygwin-service = install a CYGWIN config script during a make install], , ) +AM_CONDITIONAL([INSTALL_CYGWIN_SERVICE], [test "x$with_cygwin_service" = "xyes"]) + AC_ARG_WITH([external-mdns],[ --with-external-mdns = support the use of 'avahi-publish-service' or 'mDNSPublish' to advertise the service on Bonjour/ZeroConf ],[ AC_MSG_RESULT(>>Including external mdns support) AC_DEFINE([CONFIG_EXTERNAL_MDNS], 1, [Needed by the compiler.]) ], ) AM_CONDITIONAL([USE_EXTERNAL_MDNS], [test "x$with_external_mdns" = "xyes" ]) diff --git a/scripts/shairport-sync-config b/scripts/shairport-sync-config new file mode 100755 index 00000000..ce852aa4 --- /dev/null +++ b/scripts/shairport-sync-config @@ -0,0 +1,169 @@ +#!/bin/sh +# +# shairport-sync-config, Copyright 2019 Mike Brady +# +# Based, with thanks, on: +# avahi-daemon-config, Copyright 2011 Yaakov Selkowitz +# +# This file is part of the Cygwin port of shairport-sync. + +# ====================================================================== +# Initialization +# ====================================================================== +PROGNAME=$(basename $0) +_tdir=$(dirname $0) +PROGDIR=$(cd $_tdir && pwd) + +CSIH_SCRIPT=/usr/share/csih/cygwin-service-installation-helper.sh + +# Subdirectory where the new package is being installed +PREFIX=/usr/local/bin + +# Directory where the config files are stored +SYSCONFDIR=/etc +LOGDIR=/var/log +SOCKDIR=/var/run + +source ${CSIH_SCRIPT} + +# ====================================================================== +# Routine: install_service +# Install shairport-sync as a service +# ====================================================================== +install_service() { + + if csih_is_nt + then + + # Check if shairport-sync is installed and remove on user request. + if cygrunsrv -Q shairport-sync > /dev/null 2>&1 + then + csih_warning "The shairport-sync service is already installed." + echo + if csih_request "Do you want to reinstall it with different args?" + then + cygrunsrv -E shairport-sync + cygrunsrv -R shairport-sync + fi + fi + + # Install shairport-sync service if it is not already installed + if ! cygrunsrv -Q shairport-sync > /dev/null 2>&1 + then + echo + csih_warning "The following function requires administrator privileges!" + if csih_request "Do you want to install shairport-sync as a service?" + then + if cygrunsrv -I shairport-sync --disp "CYGWIN Shairport Sync" --desc "AirPlay Audio. Use your computer's speakers or headphones to listen to audio from your iOS devices or other AirPlay sources on your network." --path $PREFIX/shairport-sync --dep avahi-daemon --dep messagebus + then + echo + csih_inform "The shairport-sync service has been installed under the LocalSystem" + csih_inform "account (also known as SYSTEM). To start the service now, call" + csih_inform "\`net start shairport-sync' or \`cygrunsrv -S shairport-sync'. Otherwise," + csih_inform "it will start automatically after the next reboot." + echo + csih_inform "Check ${SYSCONFDIR}/shairport-sync.conf first, if it suits your needs." + fi + fi # user allowed us to install shairport-sync + fi # shairport-sync already installed + fi # csih_is_nt +} # --- End of install_service --- # + + +# ====================================================================== +# Main Entry Point +# ====================================================================== + + +# Check how the script has been started. If +# (1) it has been started by giving the full path and +# that path is /etc/postinstall, OR +# (2) Otherwise, if the environment variable +# CONFIG_AUTO_ANSWER_NO is set +# then set auto_answer to "no". This allows automatic +# creation of the config files in /etc w/o overwriting +# them if they already exist. In both cases, color +# escape sequences are suppressed, so as to prevent +# cluttering setup's logfiles. +if [ "$PROGDIR" = "/etc/postinstall" ] +then + csih_auto_answer="no" + csih_disable_color +fi +if [ -n "${CONFIG_AUTO_ANSWER_NO}" ] +then + csih_auto_answer="no" + csih_disable_color +fi + + +# ====================================================================== +# Parse options +# ====================================================================== +while : +do + case $# in + 0) + break + ;; + esac + + option=$1 + shift + + case "$option" in + -d | --debug ) + set -x + csih_trace_on + ;; + + -y | --yes ) + csih_auto_answer=yes + ;; + + -n | --no ) + csih_auto_answer=no + ;; + + *) + echo "usage: ${PROGNAME} [OPTION]..." + echo + echo "This script creates a basic shairport-sync configuration." + echo + echo "Options:" + echo " --debug -d Enable shell's debug output." + echo " --yes -y Answer all questions with \"yes\" automatically." + echo " --no -n Answer all questions with \"no\" automatically." + echo + exit 1 + ;; + + esac +done + +# ====================================================================== +# Action! +# ====================================================================== + +# Check for ${SYSCONFDIR} directory +csih_make_dir "${SYSCONFDIR}" "Cannot create global configuration files." +chmod 775 "${SYSCONFDIR}" +setfacl -m u:system:rwx "${SYSCONFDIR}" + +# Check for ${LOGDIR} directory +csih_make_dir "${LOGDIR}" "Syslogging using shairport-sync will not work." +chmod 775 "${LOGDIR}" +setfacl -m u:system:rwx "${LOGDIR}" + +# Check for ${SOCKDIR} directory +csih_make_dir "${SOCKDIR}" "SOCKET files of running processes will not be created." +chmod 775 "${SOCKDIR}" +setfacl -m u:system:rwx "${SOCKDIR}" + +# maybe: csih_auto_answer=no will skip, +# interactive user will get a chance to override +install_service + + +echo +echo "Configuration finished. Have fun!" diff --git a/scripts/shairport-sync-dbus-policy-cygwin.conf b/scripts/shairport-sync-dbus-policy-cygwin.conf new file mode 100644 index 00000000..7c47c01d --- /dev/null +++ b/scripts/shairport-sync-dbus-policy-cygwin.conf @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + diff --git a/scripts/shairport-sync-mpris-policy-cygwin.conf b/scripts/shairport-sync-mpris-policy-cygwin.conf new file mode 100644 index 00000000..ea839afd --- /dev/null +++ b/scripts/shairport-sync-mpris-policy-cygwin.conf @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + From 1c9b681b77c7ed5c9206e8ff6c1834d4dca3b1fc Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 16:24:03 +0000 Subject: [PATCH 521/569] Update CYGWIN.md --- CYGWIN.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/CYGWIN.md b/CYGWIN.md index 79ecb606..6284a335 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -6,8 +6,6 @@ inside VMWare Fusion on a Mac. The end result will be `Cygwin Shairport Sync`, an AirPlay service for Windows. -The is not, as yet, an installer for Shairport Sync in CYGWIN. - Setting up Windows ---- Set up Windows 10 and install all updates. Next, install the `Bonjour Service`, available from Apple in an installer called "Bonjour Print Services for Windows v2.0.2". @@ -66,5 +64,20 @@ $ autoreconf -fi $ PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./configure --with-ao --with-ssl=openssl \ --with-avahi --with-dbus-interface --sysconfdir=/etc $ make +$ make install ``` -* That's it. There should be a `shairport-sync.exe` file in your directory. +* The last step above will install the shairport-sync application in `/usr/local/bin` and will also install a service installer script and two D-Bus policy files. + +Shairport Sync Service +---- +* To install Shairport Sync as a CYGWIN Service, open (or return to) a `Cygwin64 Terminal` window in Administrator mode. Enter the following command: +``` +shairport-sync-config +``` +Enter `yes` for all queries. Next, open the Windows "Services" application (if it's already open, refresh the screen contents) and look for the `CYGWIN Shairport Sync` service. Open it and start it. An AirPlay player should now see a new AirPlay output device on the local network, with the same name as the Windows machine's name, e.g. `DESKTOP-0RHGN0`. You can set a different name by changing the settings in the configuration file, installed at `/etc/shairport-sync.conf`. + +Since this is now a Cygwin Service, you do not need to open Cygwin to launch it -- it should launch automatically whenever Windows is booted up. + +Known Issues +---- +* Shairport Sync cannot access the D-Bus "system" bus to make its facilities available. The cause of this problem is unknown. (While the Avahi daemon can access the D-Bus "system" bus, Shairport Sync can not. The two applications use different DBus libraries, so perhaps the issue likes there.) From 8a747dcc8e06ce468d6bbad057bfb6d74f486ed5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 16:25:38 +0000 Subject: [PATCH 522/569] Update CYGWIN.md --- CYGWIN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CYGWIN.md b/CYGWIN.md index 6284a335..a0c731cf 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -62,7 +62,7 @@ $ git clone https://github.com/mikebrady/shairport-sync.git $ cd shairport-sync $ autoreconf -fi $ PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./configure --with-ao --with-ssl=openssl \ - --with-avahi --with-dbus-interface --sysconfdir=/etc + --with-avahi --with-dbus-interface --sysconfdir=/etc --with-cygwin-service $ make $ make install ``` From f22af27e2fb6a136ef754cbc126b0981bf0129b2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 16:27:01 +0000 Subject: [PATCH 523/569] Update CYGWIN.md --- CYGWIN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CYGWIN.md b/CYGWIN.md index a0c731cf..9ded4ecf 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -4,7 +4,7 @@ Installing to Cygwin This is based on installing onto a fresh default installation of Cygwin 2.895 (64-bit installation) running in Windows 10 inside VMWare Fusion on a Mac. -The end result will be `Cygwin Shairport Sync`, an AirPlay service for Windows. +The end result is a Windows Service called `CYGWIN Shairport Sync`, providing an AirPlay service. Setting up Windows ---- From 7998cde10842dd8a9263977cb4b56ce68e6809bb Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 16:27:47 +0000 Subject: [PATCH 524/569] Update CYGWIN.md --- CYGWIN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CYGWIN.md b/CYGWIN.md index 9ded4ecf..4ed469cb 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -11,7 +11,7 @@ Setting up Windows Set up Windows 10 and install all updates. Next, install the `Bonjour Service`, available from Apple in an installer called "Bonjour Print Services for Windows v2.0.2". * Download and run `Bonjour Print Services for Windows v2.0.2` -* After accepting conditions, the installer will do a preliminary installion of the Bonjour Service and will then pause, inviting you to install Bonjour Print Services. You can decline to do this, as the Bonjour Service will have been installed. +* After accepting conditions, the installer will do a preliminary installation of the Bonjour Service and will then pause, inviting you to install Bonjour Print Services. You can decline to do this, as the Bonjour Service will have just been installed. * Check Bonjour Service is running. In Windows, open the "Services" application and ensure that you can see `Bonjour Services` running. From 37a5184e9e14f6a99b751e2e6774aed973ba8381 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 16:29:18 +0000 Subject: [PATCH 525/569] Update CYGWIN.md --- CYGWIN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CYGWIN.md b/CYGWIN.md index 4ed469cb..44360809 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -20,7 +20,7 @@ Setting up Cygwin ---- * Download the Cygwin installer from the [official website](https://cygwin.com/install.html). Save the installer in the Downloads folder. -* Open a Window Command Prompt and enter the following (long!) command: +* Open a Windows `Command Prompt` window and enter the following (long!) command, omitting the `C:\Users\mike>` prompt: ``` C:\Users\mike> Downloads\setup-x86_64.exe -P cygrunsrv,dbus,avahi,avahi-tools,gnome-keyring,libavahi-client-devel, ^ libglib2.0-devel,openssl,pkg-config,autoconf,automake,clang,libdaemon-devel,popt-devel, ^ From 20dc38fabf57904e36ab6ddc9a9874cbc514ea9d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 16:36:25 +0000 Subject: [PATCH 526/569] Update CYGWIN.md --- CYGWIN.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CYGWIN.md b/CYGWIN.md index 44360809..8a703a81 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -13,7 +13,7 @@ Set up Windows 10 and install all updates. Next, install the `Bonjour Service`, * Download and run `Bonjour Print Services for Windows v2.0.2` * After accepting conditions, the installer will do a preliminary installation of the Bonjour Service and will then pause, inviting you to install Bonjour Print Services. You can decline to do this, as the Bonjour Service will have just been installed. -* Check Bonjour Service is running. In Windows, open the "Services" application and ensure that you can see `Bonjour Services` running. +* Check Bonjour Service is running. In Windows, open the `Services` desktop application and ensure that you can see `Bonjour Services` running. Setting up Cygwin @@ -33,16 +33,17 @@ Open a `Cygwin64 Terminal` window in Administrator mode. Enter the following com ``` $ messagebus-config ``` -Enter `yes` for all queries. Next, open the Windows "Services" application (if it's already open, refresh the screen contents) and look for the `CYGWIN D-Bus system service`. Open it and start it. +Enter `yes` for all queries. Next, open the Windows `Services` desktop application (if it's already open, refresh the screen contents: `Actions > Refresh`) and look for the `CYGWIN D-Bus system service`. Open it and start it. Next, open (or return to) a `Cygwin64 Terminal` window in Administrator mode. Enter the following command: ``` $ /usr/sbin/avahi-daemon-config ``` -Enter `yes` for all queries. Next, open the Windows "Services" application (if it's already open, refresh the screen contents) and look for the `CYGWIN Avahi service`. Open it and start it. +Enter `yes` for all queries. Next, open the Windows `Services` desktop application (if it's already open, refresh the screen contents: `Actions > Refresh`) and look for the `CYGWIN Avahi service`. Open it and start it. The `libconfig` Library ---- +Shairport Sync relies on one further library – `libconfig` – that is not a Cygwin package, so it must be downloaded, compiled and installed: * Download, configure, compile and install `libconfig`: ``` $ git clone https://github.com/hyperrealm/libconfig.git From a43f05bd01b305e6ab6da0a1e2eb50cea7fedcca Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 16:38:08 +0000 Subject: [PATCH 527/569] Update CYGWIN.md --- CYGWIN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CYGWIN.md b/CYGWIN.md index 8a703a81..e6ab6cf9 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -4,7 +4,7 @@ Installing to Cygwin This is based on installing onto a fresh default installation of Cygwin 2.895 (64-bit installation) running in Windows 10 inside VMWare Fusion on a Mac. -The end result is a Windows Service called `CYGWIN Shairport Sync`, providing an AirPlay service. +The end result is a Windows Service called `CYGWIN Shairport Sync` which provides an AirPlay service by which iOS devices or other AirPlay sources on the network can play audio through Windows. Setting up Windows ---- From b02004be3f010cabf5179b67208e7b2b08706c78 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 16:53:05 +0000 Subject: [PATCH 528/569] Update CYGWIN.md --- CYGWIN.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/CYGWIN.md b/CYGWIN.md index e6ab6cf9..5c6ec07d 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -6,16 +6,19 @@ inside VMWare Fusion on a Mac. The end result is a Windows Service called `CYGWIN Shairport Sync` which provides an AirPlay service by which iOS devices or other AirPlay sources on the network can play audio through Windows. +Windows Firewall +---- +While getting everything working, it is suggested that you temporarily disable the Windows Firewall. Shairport Sync uses port 5000 for TCP and uses a range of ports -- a minimum of three and preferably at least 10, from 6001 upwards -- while it is working. The Bonjour Service, used in conjunction with the Avahi daemon, advertises Shairport Syncv over a number of further ports. Once everything is working, one can consider re-enabling the firewall, if necessary. + Setting up Windows ---- -Set up Windows 10 and install all updates. Next, install the `Bonjour Service`, available from Apple in an installer called "Bonjour Print Services for Windows v2.0.2". +Set up Windows 10 and install all updates. Install the `Bonjour Service`, available from Apple in an installer called "Bonjour Print Services for Windows v2.0.2". * Download and run `Bonjour Print Services for Windows v2.0.2` * After accepting conditions, the installer will do a preliminary installation of the Bonjour Service and will then pause, inviting you to install Bonjour Print Services. You can decline to do this, as the Bonjour Service will have just been installed. * Check Bonjour Service is running. In Windows, open the `Services` desktop application and ensure that you can see `Bonjour Services` running. - Setting up Cygwin ---- * Download the Cygwin installer from the [official website](https://cygwin.com/install.html). Save the installer in the Downloads folder. @@ -71,11 +74,13 @@ $ make install Shairport Sync Service ---- -* To install Shairport Sync as a CYGWIN Service, open (or return to) a `Cygwin64 Terminal` window in Administrator mode. Enter the following command: +* To install Shairport Sync as a Cygwin Service, open (or return to) a `Cygwin64 Terminal` window in Administrator mode. Enter the following command: ``` shairport-sync-config ``` -Enter `yes` for all queries. Next, open the Windows "Services" application (if it's already open, refresh the screen contents) and look for the `CYGWIN Shairport Sync` service. Open it and start it. An AirPlay player should now see a new AirPlay output device on the local network, with the same name as the Windows machine's name, e.g. `DESKTOP-0RHGN0`. You can set a different name by changing the settings in the configuration file, installed at `/etc/shairport-sync.conf`. +Enter `yes` for all queries. Next, open the Windows "Services" application (if it's already open, refresh the screen contents: `Actions > Refresh`) and look for the `CYGWIN Shairport Sync` service. Open it and start it. + +An AirPlay player should now see a new AirPlay output device on the local network, with the same name as the Windows machine's name, e.g. `DESKTOP-0RHGN0`. You can set a different name by changing the settings in the configuration file, installed at `/etc/shairport-sync.conf`. Since this is now a Cygwin Service, you do not need to open Cygwin to launch it -- it should launch automatically whenever Windows is booted up. From d9e75d030b59fb781d0a2f8c53f755ba47b131d4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 16:54:27 +0000 Subject: [PATCH 529/569] Update CYGWIN.md --- CYGWIN.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CYGWIN.md b/CYGWIN.md index 5c6ec07d..e3a4c2a8 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -1,7 +1,7 @@ Installing to Cygwin ==== -This is based on installing onto a fresh default installation of Cygwin 2.895 (64-bit installation) running in Windows 10 +This is based on installing onto a fresh installation of Cygwin 2.895 (64-bit installation) running in Windows 10 inside VMWare Fusion on a Mac. The end result is a Windows Service called `CYGWIN Shairport Sync` which provides an AirPlay service by which iOS devices or other AirPlay sources on the network can play audio through Windows. @@ -70,7 +70,7 @@ $ PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./configure --with-ao --with-ssl=open $ make $ make install ``` -* The last step above will install the shairport-sync application in `/usr/local/bin` and will also install a service installer script and two D-Bus policy files. +* The last step above will install the shairport-sync application in `/usr/local/bin` and will also install a configuration file, a service configuration script and two D-Bus policy files. Shairport Sync Service ---- @@ -80,7 +80,7 @@ shairport-sync-config ``` Enter `yes` for all queries. Next, open the Windows "Services" application (if it's already open, refresh the screen contents: `Actions > Refresh`) and look for the `CYGWIN Shairport Sync` service. Open it and start it. -An AirPlay player should now see a new AirPlay output device on the local network, with the same name as the Windows machine's name, e.g. `DESKTOP-0RHGN0`. You can set a different name by changing the settings in the configuration file, installed at `/etc/shairport-sync.conf`. +An AirPlay player should now see a new AirPlay output device on the local network, with the same name as the Windows machine's name, e.g. `DESKTOP-0RHGN0`. You can set a different name by changing the settings in the Shairport Sync configuration file, installed at `/etc/shairport-sync.conf`. Since this is now a Cygwin Service, you do not need to open Cygwin to launch it -- it should launch automatically whenever Windows is booted up. From 1ac8bdf6a343561c70007479dcb9108c87585620 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 17:02:43 +0000 Subject: [PATCH 530/569] Update CYGWIN.md --- CYGWIN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CYGWIN.md b/CYGWIN.md index e3a4c2a8..e91ea1f6 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -8,7 +8,7 @@ The end result is a Windows Service called `CYGWIN Shairport Sync` which provide Windows Firewall ---- -While getting everything working, it is suggested that you temporarily disable the Windows Firewall. Shairport Sync uses port 5000 for TCP and uses a range of ports -- a minimum of three and preferably at least 10, from 6001 upwards -- while it is working. The Bonjour Service, used in conjunction with the Avahi daemon, advertises Shairport Syncv over a number of further ports. Once everything is working, one can consider re-enabling the firewall, if necessary. +While getting everything working, it is suggested that you temporarily disable the Windows Firewall. Shairport Sync uses port 5000 for TCP and uses a range of ports -- a minimum of three and preferably at least 10, from 6001 upwards. The Bonjour Service, used in conjunction with the Avahi daemon, advertises Shairport Syncv over a number of further ports. Once everything is working, the firewall can be re-enabled gradually. Setting up Windows ---- From 22dafaac60a16cd95fbb0a4e18541b10dbe67468 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 17:04:43 +0000 Subject: [PATCH 531/569] Update CYGWIN.md --- CYGWIN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CYGWIN.md b/CYGWIN.md index e91ea1f6..f3b4adc2 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -76,7 +76,7 @@ Shairport Sync Service ---- * To install Shairport Sync as a Cygwin Service, open (or return to) a `Cygwin64 Terminal` window in Administrator mode. Enter the following command: ``` -shairport-sync-config +$ shairport-sync-config ``` Enter `yes` for all queries. Next, open the Windows "Services" application (if it's already open, refresh the screen contents: `Actions > Refresh`) and look for the `CYGWIN Shairport Sync` service. Open it and start it. From a3f8ead96fc2c2a1c30e54b5fc33c0285009c7d5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 17:08:27 +0000 Subject: [PATCH 532/569] Update CYGWIN.md --- CYGWIN.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CYGWIN.md b/CYGWIN.md index f3b4adc2..56a41cf4 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -50,7 +50,7 @@ Shairport Sync relies on one further library – `libconfig` – that is not a C * Download, configure, compile and install `libconfig`: ``` $ git clone https://github.com/hyperrealm/libconfig.git -$ cd libconfig +$ cd libconfigthe $ autoreconf -fi $ ./configure $ make @@ -80,7 +80,7 @@ $ shairport-sync-config ``` Enter `yes` for all queries. Next, open the Windows "Services" application (if it's already open, refresh the screen contents: `Actions > Refresh`) and look for the `CYGWIN Shairport Sync` service. Open it and start it. -An AirPlay player should now see a new AirPlay output device on the local network, with the same name as the Windows machine's name, e.g. `DESKTOP-0RHGN0`. You can set a different name by changing the settings in the Shairport Sync configuration file, installed at `/etc/shairport-sync.conf`. +An AirPlay player should now see a new AirPlay output device on the local network, with the computer's Device Name, e.g. `DESKTOP-0RHGN0`. You can set a different name by changing the settings in the Shairport Sync configuration file, installed at `/etc/shairport-sync.conf`. Since this is now a Cygwin Service, you do not need to open Cygwin to launch it -- it should launch automatically whenever Windows is booted up. From dc33c7957fa97bf10caa4784df5f0f31bff259f6 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 17:09:13 +0000 Subject: [PATCH 533/569] Update CYGWIN.md --- CYGWIN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CYGWIN.md b/CYGWIN.md index 56a41cf4..a2076e84 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -86,4 +86,4 @@ Since this is now a Cygwin Service, you do not need to open Cygwin to launch it Known Issues ---- -* Shairport Sync cannot access the D-Bus "system" bus to make its facilities available. The cause of this problem is unknown. (While the Avahi daemon can access the D-Bus "system" bus, Shairport Sync can not. The two applications use different DBus libraries, so perhaps the issue likes there.) +* Shairport Sync cannot access the D-Bus "system" bus to make its Dbus interface available. The cause of this problem is unknown. (While the Avahi daemon can access the D-Bus "system" bus, Shairport Sync can not. The two applications use different DBus libraries, so perhaps the issue likes there.) From 00e2a602e2fa74c7db6b08eb8730cb27339bb241 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 17:09:32 +0000 Subject: [PATCH 534/569] Update CYGWIN.md --- CYGWIN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CYGWIN.md b/CYGWIN.md index a2076e84..7143cb5a 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -86,4 +86,4 @@ Since this is now a Cygwin Service, you do not need to open Cygwin to launch it Known Issues ---- -* Shairport Sync cannot access the D-Bus "system" bus to make its Dbus interface available. The cause of this problem is unknown. (While the Avahi daemon can access the D-Bus "system" bus, Shairport Sync can not. The two applications use different DBus libraries, so perhaps the issue likes there.) +* Shairport Sync cannot access the D-Bus "system" bus to make its Dbus interface available. The cause of this problem is unknown. (While the Avahi daemon can access the D-Bus "system" bus, Shairport Sync can not. The two applications use different DBus libraries, so perhaps the issue lies there.) From 1e7c734e980f01745a18e228d22f579def6697e1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 17:13:52 +0000 Subject: [PATCH 535/569] Update CYGWIN.md --- CYGWIN.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CYGWIN.md b/CYGWIN.md index 7143cb5a..bf03641f 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -8,7 +8,7 @@ The end result is a Windows Service called `CYGWIN Shairport Sync` which provide Windows Firewall ---- -While getting everything working, it is suggested that you temporarily disable the Windows Firewall. Shairport Sync uses port 5000 for TCP and uses a range of ports -- a minimum of three and preferably at least 10, from 6001 upwards. The Bonjour Service, used in conjunction with the Avahi daemon, advertises Shairport Syncv over a number of further ports. Once everything is working, the firewall can be re-enabled gradually. +While getting everything working, it is suggested that you temporarily disable the Windows Firewall. Shairport Sync uses port 5000 for TCP and uses three ports for UDP, so you should leave a minimum of three, and preferably at least 10, open from 6001 upwards. The Bonjour Service, used in conjunction with the Avahi daemon, advertises Shairport Sync over a number of further ports. Once everything is working, the firewall can be re-enabled gradually. Setting up Windows ---- @@ -46,7 +46,7 @@ Enter `yes` for all queries. Next, open the Windows `Services` desktop applicati The `libconfig` Library ---- -Shairport Sync relies on one further library – `libconfig` – that is not a Cygwin package, so it must be downloaded, compiled and installed: +Shairport Sync relies on a library – `libconfig` – that is not a Cygwin package, so it must be downloaded, compiled and installed: * Download, configure, compile and install `libconfig`: ``` $ git clone https://github.com/hyperrealm/libconfig.git @@ -80,9 +80,9 @@ $ shairport-sync-config ``` Enter `yes` for all queries. Next, open the Windows "Services" application (if it's already open, refresh the screen contents: `Actions > Refresh`) and look for the `CYGWIN Shairport Sync` service. Open it and start it. -An AirPlay player should now see a new AirPlay output device on the local network, with the computer's Device Name, e.g. `DESKTOP-0RHGN0`. You can set a different name by changing the settings in the Shairport Sync configuration file, installed at `/etc/shairport-sync.conf`. +An AirPlay player on the local network should now see a new AirPlay output device bearing the computer's Device Name, e.g. `DESKTOP-0RHGN0`. You can set a different name by changing the settings in the Shairport Sync configuration file, installed at `/etc/shairport-sync.conf`. -Since this is now a Cygwin Service, you do not need to open Cygwin to launch it -- it should launch automatically whenever Windows is booted up. +Since Shairport Sync is now a Cygwin Service, you do not need to open Cygwin to launch it -- it should launch automatically when Windows is booted up. Known Issues ---- From b235757007579e3a90c8b98f6723f15381b81eea Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 17:14:49 +0000 Subject: [PATCH 536/569] Update CYGWIN.md --- CYGWIN.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CYGWIN.md b/CYGWIN.md index bf03641f..143cf187 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -4,7 +4,7 @@ Installing to Cygwin This is based on installing onto a fresh installation of Cygwin 2.895 (64-bit installation) running in Windows 10 inside VMWare Fusion on a Mac. -The end result is a Windows Service called `CYGWIN Shairport Sync` which provides an AirPlay service by which iOS devices or other AirPlay sources on the network can play audio through Windows. +The end result is a Windows Service called `CYGWIN Shairport Sync` which provides an AirPlay service by which iOS devices or other AirPlay sources on the network can play audio through the Windows device. Windows Firewall ---- @@ -86,4 +86,4 @@ Since Shairport Sync is now a Cygwin Service, you do not need to open Cygwin to Known Issues ---- -* Shairport Sync cannot access the D-Bus "system" bus to make its Dbus interface available. The cause of this problem is unknown. (While the Avahi daemon can access the D-Bus "system" bus, Shairport Sync can not. The two applications use different DBus libraries, so perhaps the issue lies there.) +* Shairport Sync cannot access the D-Bus system bus to make its Dbus interface available. The cause of this problem is unknown. (While the Avahi daemon can access the D-Bus system bus, Shairport Sync can not. The two applications use different D-Bus libraries, so perhaps the issue lies there.) From c4d6e9597a840c90b4138936964c225bd8dd0713 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 17:16:21 +0000 Subject: [PATCH 537/569] Update CYGWIN.md --- CYGWIN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CYGWIN.md b/CYGWIN.md index 143cf187..9b344b21 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -78,7 +78,7 @@ Shairport Sync Service ``` $ shairport-sync-config ``` -Enter `yes` for all queries. Next, open the Windows "Services" application (if it's already open, refresh the screen contents: `Actions > Refresh`) and look for the `CYGWIN Shairport Sync` service. Open it and start it. +Enter `yes` for all queries. Next, open the Windows `Services` desktop application (if it's already open, refresh the screen contents: `Actions > Refresh`) and look for the `CYGWIN Shairport Sync` service. Open it and start it. An AirPlay player on the local network should now see a new AirPlay output device bearing the computer's Device Name, e.g. `DESKTOP-0RHGN0`. You can set a different name by changing the settings in the Shairport Sync configuration file, installed at `/etc/shairport-sync.conf`. From d4016aca4285f7469f3c91d631873993eadb3401 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 17:16:55 +0000 Subject: [PATCH 538/569] Update CYGWIN.md --- CYGWIN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CYGWIN.md b/CYGWIN.md index 9b344b21..ccf27204 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -4,7 +4,7 @@ Installing to Cygwin This is based on installing onto a fresh installation of Cygwin 2.895 (64-bit installation) running in Windows 10 inside VMWare Fusion on a Mac. -The end result is a Windows Service called `CYGWIN Shairport Sync` which provides an AirPlay service by which iOS devices or other AirPlay sources on the network can play audio through the Windows device. +The end result is a Windows Service called `CYGWIN Shairport Sync`, providing an AirPlay service by which iOS devices or other AirPlay sources on the network can play audio through the Windows device. Windows Firewall ---- From 8602f0e2b36476ae8b2ce01b969acd22eb35971a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 17:20:13 +0000 Subject: [PATCH 539/569] Update CYGWIN.md --- CYGWIN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CYGWIN.md b/CYGWIN.md index ccf27204..a5da44ff 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -80,7 +80,7 @@ $ shairport-sync-config ``` Enter `yes` for all queries. Next, open the Windows `Services` desktop application (if it's already open, refresh the screen contents: `Actions > Refresh`) and look for the `CYGWIN Shairport Sync` service. Open it and start it. -An AirPlay player on the local network should now see a new AirPlay output device bearing the computer's Device Name, e.g. `DESKTOP-0RHGN0`. You can set a different name by changing the settings in the Shairport Sync configuration file, installed at `/etc/shairport-sync.conf`. +An AirPlay player on the local network should now be able to see an AirPlay output device bearing the computer's Device Name, e.g. `DESKTOP-0RHGN0`. You can set a different name by changing the settings in the Shairport Sync configuration file, installed at `/etc/shairport-sync.conf`. Since Shairport Sync is now a Cygwin Service, you do not need to open Cygwin to launch it -- it should launch automatically when Windows is booted up. From 340faba1eb8ad73da5c034fc58007a2d15f1f5a6 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 17:21:02 +0000 Subject: [PATCH 540/569] Update CYGWIN.md --- CYGWIN.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CYGWIN.md b/CYGWIN.md index a5da44ff..62f87dfe 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -1,10 +1,10 @@ Installing to Cygwin ==== -This is based on installing onto a fresh installation of Cygwin 2.895 (64-bit installation) running in Windows 10 +This guide is based on installing onto a fresh installation of Cygwin 2.895 (64-bit installation) running in Windows 10 inside VMWare Fusion on a Mac. -The end result is a Windows Service called `CYGWIN Shairport Sync`, providing an AirPlay service by which iOS devices or other AirPlay sources on the network can play audio through the Windows device. +The end result is a new Windows Service called `CYGWIN Shairport Sync`, providing an AirPlay service by which iOS devices or other AirPlay sources on the network can play audio through the Windows device. Windows Firewall ---- From 0af17b6c9933f8ec33ed13c67531592d10441762 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 17:22:23 +0000 Subject: [PATCH 541/569] Update CYGWIN.md --- CYGWIN.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CYGWIN.md b/CYGWIN.md index 62f87dfe..ed829dba 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -83,7 +83,6 @@ Enter `yes` for all queries. Next, open the Windows `Services` desktop applicati An AirPlay player on the local network should now be able to see an AirPlay output device bearing the computer's Device Name, e.g. `DESKTOP-0RHGN0`. You can set a different name by changing the settings in the Shairport Sync configuration file, installed at `/etc/shairport-sync.conf`. Since Shairport Sync is now a Cygwin Service, you do not need to open Cygwin to launch it -- it should launch automatically when Windows is booted up. - Known Issues ---- -* Shairport Sync cannot access the D-Bus system bus to make its Dbus interface available. The cause of this problem is unknown. (While the Avahi daemon can access the D-Bus system bus, Shairport Sync can not. The two applications use different D-Bus libraries, so perhaps the issue lies there.) +* Shairport Sync cannot access the D-Bus system bus to make its D-Bus interface available. The cause of this problem is unknown. (While the Avahi daemon can access the D-Bus system bus, Shairport Sync can not. The two applications use different D-Bus libraries, so perhaps the issue lies there.) From d4fdc2f18f2b43f7cb6fb831c625bcaa666cb6a1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 17:22:49 +0000 Subject: [PATCH 542/569] Update CYGWIN.md --- CYGWIN.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CYGWIN.md b/CYGWIN.md index ed829dba..57dfe34b 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -83,6 +83,7 @@ Enter `yes` for all queries. Next, open the Windows `Services` desktop applicati An AirPlay player on the local network should now be able to see an AirPlay output device bearing the computer's Device Name, e.g. `DESKTOP-0RHGN0`. You can set a different name by changing the settings in the Shairport Sync configuration file, installed at `/etc/shairport-sync.conf`. Since Shairport Sync is now a Cygwin Service, you do not need to open Cygwin to launch it -- it should launch automatically when Windows is booted up. + Known Issues ---- * Shairport Sync cannot access the D-Bus system bus to make its D-Bus interface available. The cause of this problem is unknown. (While the Avahi daemon can access the D-Bus system bus, Shairport Sync can not. The two applications use different D-Bus libraries, so perhaps the issue lies there.) From c99f15e1f0a28e94d74bc0058023c9c4b300d8ee Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 21:26:08 +0000 Subject: [PATCH 543/569] Update CYGWIN.md --- CYGWIN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CYGWIN.md b/CYGWIN.md index 57dfe34b..59eac560 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -17,7 +17,7 @@ Set up Windows 10 and install all updates. Install the `Bonjour Service`, availa * Download and run `Bonjour Print Services for Windows v2.0.2` * After accepting conditions, the installer will do a preliminary installation of the Bonjour Service and will then pause, inviting you to install Bonjour Print Services. You can decline to do this, as the Bonjour Service will have just been installed. -* Check Bonjour Service is running. In Windows, open the `Services` desktop application and ensure that you can see `Bonjour Services` running. +* Check Bonjour Service is running. In Windows, open the `Services` desktop application and ensure that you can see `Bonjour Service` running. Setting up Cygwin ---- From 9ee199dfbc02b9b58ec727352ac9a45fe76a8405 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 21:34:27 +0000 Subject: [PATCH 544/569] Update CYGWIN.md --- CYGWIN.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/CYGWIN.md b/CYGWIN.md index 59eac560..4fe8c184 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -1,4 +1,4 @@ -Installing to Cygwin +Installing Shairport Sync into Cygwin ==== This guide is based on installing onto a fresh installation of Cygwin 2.895 (64-bit installation) running in Windows 10 @@ -15,7 +15,7 @@ Setting up Windows Set up Windows 10 and install all updates. Install the `Bonjour Service`, available from Apple in an installer called "Bonjour Print Services for Windows v2.0.2". * Download and run `Bonjour Print Services for Windows v2.0.2` -* After accepting conditions, the installer will do a preliminary installation of the Bonjour Service and will then pause, inviting you to install Bonjour Print Services. You can decline to do this, as the Bonjour Service will have just been installed. +* After accepting conditions and clicking the `Install` button, the installer will do a preliminary installation, installing just the Bonjour Service. It will then pause, inviting you to install Bonjour Print Services. You can decline this, as the Bonjour Service will have been installed during the first part of the installation. * Check Bonjour Service is running. In Windows, open the `Services` desktop application and ensure that you can see `Bonjour Service` running. @@ -23,7 +23,7 @@ Setting up Cygwin ---- * Download the Cygwin installer from the [official website](https://cygwin.com/install.html). Save the installer in the Downloads folder. -* Open a Windows `Command Prompt` window and enter the following (long!) command, omitting the `C:\Users\mike>` prompt: +* Open a Windows `Command Prompt` window and enter the following multi-line command, omitting the `C:\Users\mike>` prompt: ``` C:\Users\mike> Downloads\setup-x86_64.exe -P cygrunsrv,dbus,avahi,avahi-tools,gnome-keyring,libavahi-client-devel, ^ libglib2.0-devel,openssl,pkg-config,autoconf,automake,clang,libdaemon-devel,popt-devel, ^ @@ -36,13 +36,13 @@ Open a `Cygwin64 Terminal` window in Administrator mode. Enter the following com ``` $ messagebus-config ``` -Enter `yes` for all queries. Next, open the Windows `Services` desktop application (if it's already open, refresh the screen contents: `Actions > Refresh`) and look for the `CYGWIN D-Bus system service`. Open it and start it. +Answer `yes` to all queries. Open the Windows `Services` desktop application (if it's already open, refresh the screen contents: `Actions > Refresh`) and look for the `CYGWIN D-Bus system service`. Open it and start it. Next, open (or return to) a `Cygwin64 Terminal` window in Administrator mode. Enter the following command: ``` $ /usr/sbin/avahi-daemon-config ``` -Enter `yes` for all queries. Next, open the Windows `Services` desktop application (if it's already open, refresh the screen contents: `Actions > Refresh`) and look for the `CYGWIN Avahi service`. Open it and start it. +Answer `yes` to all queries. Open the Windows `Services` desktop application (if it's already open, refresh the screen contents: `Actions > Refresh`) and look for the `CYGWIN Avahi service`. Open it and start it. The `libconfig` Library ---- @@ -60,7 +60,7 @@ $ cd .. Shairport Sync ---- -* Next, download, configure and compile Shairport Sync: +* Download, configure and compile Shairport Sync: ``` $ git clone https://github.com/mikebrady/shairport-sync.git $ cd shairport-sync @@ -70,7 +70,7 @@ $ PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./configure --with-ao --with-ssl=open $ make $ make install ``` -* The last step above will install the shairport-sync application in `/usr/local/bin` and will also install a configuration file, a service configuration script and two D-Bus policy files. +* The last step above installs the `shairport-sync` application into `/usr/local/bin` and also installs a configuration file, a service configuration script and two D-Bus policy files. Shairport Sync Service ---- @@ -78,11 +78,11 @@ Shairport Sync Service ``` $ shairport-sync-config ``` -Enter `yes` for all queries. Next, open the Windows `Services` desktop application (if it's already open, refresh the screen contents: `Actions > Refresh`) and look for the `CYGWIN Shairport Sync` service. Open it and start it. +Answer `yes` to all queries. Open the Windows `Services` desktop application (if it's already open, refresh the screen contents: `Actions > Refresh`) and look for the `CYGWIN Shairport Sync` service. Open it and start it. An AirPlay player on the local network should now be able to see an AirPlay output device bearing the computer's Device Name, e.g. `DESKTOP-0RHGN0`. You can set a different name by changing the settings in the Shairport Sync configuration file, installed at `/etc/shairport-sync.conf`. -Since Shairport Sync is now a Cygwin Service, you do not need to open Cygwin to launch it -- it should launch automatically when Windows is booted up. +Since Shairport Sync is now a Cygwin Service, you do not need to open Cygwin to launch it – it should launch automatically when Windows is booted up. Known Issues ---- From 313eb0c8601142c7d3ccd1001cd587b05a32d057 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 21:40:38 +0000 Subject: [PATCH 545/569] Update CYGWIN.md --- CYGWIN.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CYGWIN.md b/CYGWIN.md index 4fe8c184..f407e3e5 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -25,8 +25,8 @@ Setting up Cygwin * Open a Windows `Command Prompt` window and enter the following multi-line command, omitting the `C:\Users\mike>` prompt: ``` -C:\Users\mike> Downloads\setup-x86_64.exe -P cygrunsrv,dbus,avahi,avahi-tools,gnome-keyring,libavahi-client-devel, ^ -libglib2.0-devel,openssl,pkg-config,autoconf,automake,clang,libdaemon-devel,popt-devel, ^ +C:\Users\mike> Downloads\setup-x86_64.exe -P cygrunsrv,dbus,avahi,avahi-tools,gnome-keyring,libavahi-client-devel, ^ +libglib2.0-devel,openssl,pkg-config,autoconf,automake,clang,libdaemon-devel,popt-devel, ^ make,libao-devel,openssl-devel,libtool,git,wget,flex,bison ``` This will do a complete installation of Cygwin and all necessary packages. From a82144dab851f046235e710ddfd7f9c5bfc1be0c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 21:42:21 +0000 Subject: [PATCH 546/569] Update CYGWIN.md --- CYGWIN.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CYGWIN.md b/CYGWIN.md index f407e3e5..4fe8c184 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -25,8 +25,8 @@ Setting up Cygwin * Open a Windows `Command Prompt` window and enter the following multi-line command, omitting the `C:\Users\mike>` prompt: ``` -C:\Users\mike> Downloads\setup-x86_64.exe -P cygrunsrv,dbus,avahi,avahi-tools,gnome-keyring,libavahi-client-devel, ^ -libglib2.0-devel,openssl,pkg-config,autoconf,automake,clang,libdaemon-devel,popt-devel, ^ +C:\Users\mike> Downloads\setup-x86_64.exe -P cygrunsrv,dbus,avahi,avahi-tools,gnome-keyring,libavahi-client-devel, ^ +libglib2.0-devel,openssl,pkg-config,autoconf,automake,clang,libdaemon-devel,popt-devel, ^ make,libao-devel,openssl-devel,libtool,git,wget,flex,bison ``` This will do a complete installation of Cygwin and all necessary packages. From 072551bd70931d1e8430552ec63e40a6b8b4a853 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 21:47:38 +0000 Subject: [PATCH 547/569] Update CYGWIN.md --- CYGWIN.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CYGWIN.md b/CYGWIN.md index 4fe8c184..663a2374 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -25,8 +25,8 @@ Setting up Cygwin * Open a Windows `Command Prompt` window and enter the following multi-line command, omitting the `C:\Users\mike>` prompt: ``` -C:\Users\mike> Downloads\setup-x86_64.exe -P cygrunsrv,dbus,avahi,avahi-tools,gnome-keyring,libavahi-client-devel, ^ -libglib2.0-devel,openssl,pkg-config,autoconf,automake,clang,libdaemon-devel,popt-devel, ^ +Downloads\setup-x86_64.exe -P cygrunsrv,dbus,avahi,avahi-tools,gnome-keyring,libavahi-client-devel,^ +libglib2.0-devel,openssl,pkg-config,autoconf,automake,clang,libdaemon-devel,popt-devel,^ make,libao-devel,openssl-devel,libtool,git,wget,flex,bison ``` This will do a complete installation of Cygwin and all necessary packages. From 506e0a33797363191de3253079bd9d5104b89ee1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 21:49:39 +0000 Subject: [PATCH 548/569] Update CYGWIN.md --- CYGWIN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CYGWIN.md b/CYGWIN.md index 663a2374..175becc9 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -50,7 +50,7 @@ Shairport Sync relies on a library – `libconfig` – that is not a Cygwin pack * Download, configure, compile and install `libconfig`: ``` $ git clone https://github.com/hyperrealm/libconfig.git -$ cd libconfigthe +$ cd libconfig $ autoreconf -fi $ ./configure $ make From dbc17504558d398b539212940cff8633d6897635 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 21:53:38 +0000 Subject: [PATCH 549/569] Update CYGWIN.md --- CYGWIN.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CYGWIN.md b/CYGWIN.md index 175becc9..096857f2 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -64,6 +64,7 @@ Shairport Sync ``` $ git clone https://github.com/mikebrady/shairport-sync.git $ cd shairport-sync +$ git checkout development // this is temporary $ autoreconf -fi $ PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./configure --with-ao --with-ssl=openssl \ --with-avahi --with-dbus-interface --sysconfdir=/etc --with-cygwin-service From 4c81d7007ac65d2465f0d11c3b734c8e5820f560 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 Feb 2019 22:08:17 +0000 Subject: [PATCH 550/569] Update CYGWIN.md --- CYGWIN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CYGWIN.md b/CYGWIN.md index 096857f2..f477c19e 100644 --- a/CYGWIN.md +++ b/CYGWIN.md @@ -25,7 +25,7 @@ Setting up Cygwin * Open a Windows `Command Prompt` window and enter the following multi-line command, omitting the `C:\Users\mike>` prompt: ``` -Downloads\setup-x86_64.exe -P cygrunsrv,dbus,avahi,avahi-tools,gnome-keyring,libavahi-client-devel,^ +C:\Users\mike> Downloads\setup-x86_64.exe -P cygrunsrv,dbus,avahi,avahi-tools,gnome-keyring,libavahi-client-devel,^ libglib2.0-devel,openssl,pkg-config,autoconf,automake,clang,libdaemon-devel,popt-devel,^ make,libao-devel,openssl-devel,libtool,git,wget,flex,bison ``` From ca325d0459c657b8e4d0847f5575c28defd8973a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 24 Feb 2019 13:16:55 +0000 Subject: [PATCH 551/569] Fix an error in non_blocking_write, expose a more general version of it, use it when writing an RTSP response with a three-second timeout. --- common.c | 13 +++++++++---- common.h | 2 ++ rtsp.c | 4 ++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/common.c b/common.c index cf5c0730..08b5bed1 100644 --- a/common.c +++ b/common.c @@ -955,7 +955,8 @@ uint64_t get_absolute_time_in_fp() { return time_now_fp; } -ssize_t non_blocking_write(int fd, const void *buf, size_t count) { +ssize_t non_blocking_write_with_timeout(int fd, const void *buf, size_t count, int timeout) { + // timeout is in milliseconds void *ibuf = (void *)buf; size_t bytes_remaining = count; int rc = 1; @@ -964,7 +965,7 @@ ssize_t non_blocking_write(int fd, const void *buf, size_t count) { // check that we can do some writing ufds[0].fd = fd; ufds[0].events = POLLOUT; - rc = poll(ufds, 1, 5000); + rc = poll(ufds, 1, timeout); if (rc < 0) { // debug(1, "non-blocking write error waiting for pipe to become ready for writing..."); } else if (rc == 0) { @@ -975,20 +976,24 @@ ssize_t non_blocking_write(int fd, const void *buf, size_t count) { ssize_t bytes_written = write(fd, ibuf, bytes_remaining); if (bytes_written == -1) { // debug(1,"Error %d in non_blocking_write: \"%s\".",errno,strerror(errno)); - rc = -1; + rc = bytes_written; // to imitate the return from write() } else { ibuf += bytes_written; bytes_remaining -= bytes_written; } } } - if (rc == 0) + if (rc > 0) return count - bytes_remaining; // this is just to mimic a normal write/3. else return rc; // return write(fd,buf,count); } +ssize_t non_blocking_write(int fd, const void *buf, size_t count) { + return non_blocking_write_with_timeout(fd,buf,count,5000); // default is 5 seconds. +} + /* from * http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722 */ diff --git a/common.h b/common.h index e3e02635..9bc849e1 100644 --- a/common.h +++ b/common.h @@ -251,6 +251,8 @@ int get_requested_connection_state_to_output(); void set_requested_connection_state_to_output(int v); +ssize_t non_blocking_write_with_timeout(int fd, const void *buf, size_t count, int timeout); // timeout in milliseconds + ssize_t non_blocking_write(int fd, const void *buf, size_t count); // used in a few places /* from diff --git a/rtsp.c b/rtsp.c index fb4ed4f3..6279d7a1 100644 --- a/rtsp.c +++ b/rtsp.c @@ -779,7 +779,7 @@ int msg_write_response(int fd, rtsp_message *resp) { debug(1, "Attempted to write overlong RTSP packet 3"); return -3; } - ssize_t reply = write(fd, pkt, p - pkt); + ssize_t reply = non_blocking_write_with_timeout(fd, pkt, p - pkt, 3000); // wait three seconds (3,000 milliseconds) for it to become available if (reply == -1) { char errorstring[1024]; strerror_r(errno, (char *)errorstring, sizeof(errorstring)); @@ -787,7 +787,7 @@ int msg_write_response(int fd, rtsp_message *resp) { return -4; } if (reply != p - pkt) { - debug(1, "msg_write_response error -- requested bytes not fully written."); + debug(1, "msg_write_response error -- requested bytes: %d not fully written: %d.",p - pkt, reply); return -5; } return 0; From b26d8155292057e4acf400dc8e40f321e6137a7b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 25 Feb 2019 11:38:08 +0000 Subject: [PATCH 552/569] Add indication of dacp-id, source and self IP numbers to some diagnostics. --- rtsp.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/rtsp.c b/rtsp.c index 6279d7a1..923f4ee7 100644 --- a/rtsp.c +++ b/rtsp.c @@ -986,8 +986,10 @@ 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 with UDP ports Control: %d, Timing: %d and Audio: %d.", - conn->connection_number, conn->local_control_port, conn->local_timing_port, + 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); } else { @@ -1882,8 +1884,10 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag resp->respcode = 200; } else { resp->respcode = 453; - debug(1, "Connection %d: failed because a connection is already playing.", - conn->connection_number); + debug(1, "Connection %d: DACP-ID \"%s\" on %s failed because a connection is already playing.", + conn->connection_number, + conn->dacp_id, + conn->client_ip_string); } out: From 20967812ad76e39dec183fd66420212dd23b6ee1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 26 Feb 2019 15:23:11 +0000 Subject: [PATCH 553/569] Fix a bug in the activity_monitor software that could lock up a player thread so that it couln't be cancelled. If a player was cancelled while performing an activity script, the activity_monitor_mutex would remain locked. --- activity_monitor.c | 8 +-- audio_alsa.c | 124 +++++++++++++++++++++++++++++---------------- audio_sndio.c | 3 +- common.c | 3 ++ common.h | 1 + player.c | 12 +++++ rtsp.c | 10 ++-- shairport.c | 2 +- 8 files changed, 109 insertions(+), 54 deletions(-) diff --git a/activity_monitor.c b/activity_monitor.c index 51eeb0a2..3c525c19 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -111,7 +111,9 @@ void going_inactive(int block) { } void activity_monitor_signify_activity(int active) { - pthread_mutex_lock(&activity_monitor_mutex); + // this could be pthread_cancelled and they is likely to be cancellation points in the + // hooked-on procedures + pthread_cleanup_debug_mutex_lock(&activity_monitor_mutex, 10000, 1); player_state = active == 0 ? ps_inactive : ps_active; // Now, although we could simply let the state machine in the activity monitor thread // look after eveything, we will change state here in two situations: @@ -123,7 +125,7 @@ void activity_monitor_signify_activity(int active) { // // The reason for all this is that we might want to perform the attached scripts // and wait for them to complete before continuing. If they were perfomed in the - // activity monitor thread, then we coldn't wait for them to complete. + // activity monitor thread, then we couldn't wait for them to complete. // Thus, the only time the thread will execute a going_... function is when a non-zero // timeout actually matures. @@ -138,7 +140,7 @@ void activity_monitor_signify_activity(int active) { } pthread_cond_signal(&activity_monitor_cv); - pthread_mutex_unlock(&activity_monitor_mutex); + pthread_cleanup_pop(1); // release the mutex } void activity_thread_cleanup_handler(__attribute__((unused)) void *arg) { diff --git a/audio_alsa.c b/audio_alsa.c index ee134a54..5896b431 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -323,48 +323,7 @@ int actual_open_alsa_device(void) { snd_strerror(ret)); return ret; } - snd_pcm_format_t sf; - switch (sample_format) { - case SPS_FORMAT_S8: - sf = SND_PCM_FORMAT_S8; - frame_size = 2; - break; - case SPS_FORMAT_U8: - sf = SND_PCM_FORMAT_U8; - frame_size = 2; - break; - case SPS_FORMAT_S16: - sf = SND_PCM_FORMAT_S16; - frame_size = 4; - break; - case SPS_FORMAT_S24: - sf = SND_PCM_FORMAT_S24; - frame_size = 8; - break; - case SPS_FORMAT_S24_3LE: - sf = SND_PCM_FORMAT_S24_3LE; - frame_size = 6; - break; - case SPS_FORMAT_S24_3BE: - sf = SND_PCM_FORMAT_S24_3BE; - frame_size = 6; - break; - case SPS_FORMAT_S32: - sf = SND_PCM_FORMAT_S32; - frame_size = 8; - break; - default: - sf = SND_PCM_FORMAT_S16; // this is just to quieten a compiler warning - frame_size = 4; - debug(1, "Unsupported output format at audio_alsa.c"); - return -EINVAL; - } - ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sf); - if (ret < 0) { - warn("audio_alsa: Sample format %d not available for device \"%s\": %s", sample_format, - alsa_out_dev, snd_strerror(ret)); - return ret; - } + ret = snd_pcm_hw_params_set_channels(alsa_handle, alsa_params, 2); if (ret < 0) { @@ -379,6 +338,83 @@ int actual_open_alsa_device(void) { snd_strerror(ret)); return ret; } + + + if (sample_format == SPS_FORMAT_AUTO) { + + struct alsa_formats { + snd_pcm_format_t alsa_sound_format; + enum sps_format_t sps_sound_format; + int frame_size; + char * description; + }; + + struct alsa_formats formats[] = {{SND_PCM_FORMAT_S32,SPS_FORMAT_S32,8,"S32"}, + {SND_PCM_FORMAT_S24,SPS_FORMAT_S24,8,"S24"}, + {SND_PCM_FORMAT_S24_3LE,SPS_FORMAT_S24_3LE,6,"S24_LE"}, + {SND_PCM_FORMAT_S24_3BE,SPS_FORMAT_S24_3BE,6,"S24_BE"}, + {SND_PCM_FORMAT_S16,SPS_FORMAT_S16,4,"S16"}}; + + int acceptable_format_found = 0; + unsigned int i = 0; + while((!acceptable_format_found) && (i < sizeof(formats) / sizeof(formats[0]))) { + if (snd_pcm_hw_params_set_format(alsa_handle, alsa_params, formats[i].alsa_sound_format) == 0) { + acceptable_format_found = 1; + frame_size = formats[i].frame_size; + config.output_format = formats[i].sps_sound_format; + debug(1,"alsa: output format automatically chosen to be \"%s\".",formats[i].description); + } else { + i++; + } + } + if (!acceptable_format_found) { + die("Can't find a suitable automatic output format setting."); + } + } else { + snd_pcm_format_t sf; + switch (sample_format) { + case SPS_FORMAT_S8: + sf = SND_PCM_FORMAT_S8; + frame_size = 2; + break; + case SPS_FORMAT_U8: + sf = SND_PCM_FORMAT_U8; + frame_size = 2; + break; + case SPS_FORMAT_S16: + sf = SND_PCM_FORMAT_S16; + frame_size = 4; + break; + case SPS_FORMAT_S24: + sf = SND_PCM_FORMAT_S24; + frame_size = 8; + break; + case SPS_FORMAT_S24_3LE: + sf = SND_PCM_FORMAT_S24_3LE; + frame_size = 6; + break; + case SPS_FORMAT_S24_3BE: + sf = SND_PCM_FORMAT_S24_3BE; + frame_size = 6; + break; + case SPS_FORMAT_S32: + sf = SND_PCM_FORMAT_S32; + frame_size = 8; + break; + default: + sf = SND_PCM_FORMAT_S16; // this is just to quieten a compiler warning + frame_size = 4; + debug(1, "Unsupported output format at audio_alsa.c"); + return -EINVAL; + } + + ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sf); + if (ret < 0) { + warn("audio_alsa: Sample format %d not available for device \"%s\": %s", sample_format, + alsa_out_dev, snd_strerror(ret)); + return ret; + } + } if (set_period_size_request != 0) { debug(1, "Attempting to set the period size"); @@ -898,8 +934,10 @@ static int init(int argc, char **argv) { config.output_format = SPS_FORMAT_U8; else if (strcasecmp(str, "S8") == 0) config.output_format = SPS_FORMAT_S8; + else if (strcasecmp(str, "AUTOMATIC") == 0) + config.output_format = SPS_FORMAT_AUTO; else { - warn("Invalid output format \"%s\". It should be \"U8\", \"S8\", " + warn("Invalid output format \"%s\". It should be \"Automatic\", \"U8\", \"S8\", " "\"S16\", \"S24\", " "\"S24_3LE\", \"S24_3BE\" or " "\"S32\". It is set to \"S16\".", diff --git a/audio_sndio.c b/audio_sndio.c index f22ed5af..3a544b9a 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -75,6 +75,7 @@ struct sndio_formats { static struct sndio_formats formats[] = {{"S8", SPS_FORMAT_S8, 8, 1, 1, SIO_LE_NATIVE}, {"U8", SPS_FORMAT_U8, 8, 1, 0, SIO_LE_NATIVE}, {"S16", SPS_FORMAT_S16, 16, 2, 1, SIO_LE_NATIVE}, + {"AUTOMATIC", SPS_FORMAT_S16, 16, 2, 1, SIO_LE_NATIVE}, // TODO: make this really automatic? {"S24", SPS_FORMAT_S24, 24, 4, 1, SIO_LE_NATIVE}, {"S24_3LE", SPS_FORMAT_S24_3LE, 24, 3, 1, 1}, {"S24_3BE", SPS_FORMAT_S24_3BE, 24, 3, 1, 0}, @@ -146,7 +147,7 @@ static int init(int argc, char **argv) { } if (!found) die("Invalid output format \"%s\". Should be one of: S8, U8, S16, S24, " - "S24_3LE, S24_3BE, S32", + "S24_3LE, S24_3BE, S32, Automatic", tmp); } } diff --git a/common.c b/common.c index 08b5bed1..ec70e2fe 100644 --- a/common.c +++ b/common.c @@ -1390,6 +1390,9 @@ int64_t generate_zero_frames(char *outp, size_t number_of_frames, enum sps_forma break; case SPS_FORMAT_UNKNOWN: die("Unexpected SPS_FORMAT_UNKNOWN while calculating dither mask."); + break; + case SPS_FORMAT_AUTO: + die("Unexpected SPS_FORMAT_AUTO while calculating dither mask."); } dither_mask -= 1; // int64_t r = r64i(); diff --git a/common.h b/common.h index 9bc849e1..6f92abbe 100644 --- a/common.h +++ b/common.h @@ -80,6 +80,7 @@ enum sps_format_t { SPS_FORMAT_S24_3LE, SPS_FORMAT_S24_3BE, SPS_FORMAT_S32, + SPS_FORMAT_AUTO, } sps_format_t; typedef struct { diff --git a/player.c b/player.c index 72a79ef8..881a7359 100644 --- a/player.c +++ b/player.c @@ -682,6 +682,10 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t break; case SPS_FORMAT_UNKNOWN: die("Unexpected SPS_FORMAT_UNKNOWN while calculating dither mask."); + break; + case SPS_FORMAT_AUTO: + die("Unexpected SPS_FORMAT_AUTO while calculating dither mask."); + break; } dither_mask -= 1; // int64_t r = r64i(); @@ -758,6 +762,10 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t break; case SPS_FORMAT_UNKNOWN: die("Unexpected SPS_FORMAT_UNKNOWN while outputting samples"); + break; + case SPS_FORMAT_AUTO: + die("Unexpected SPS_FORMAT_AUTO while outputting samples"); + break; } *outp += result; @@ -1644,6 +1652,10 @@ void *player_thread_func(void *arg) { break; case SPS_FORMAT_UNKNOWN: die("Unknown format choosing output bit depth"); + break; + case SPS_FORMAT_AUTO: + die("Unexpected SPS_FORMAT_AUTO while outputting samples"); + break; } debug(3, "Output bit depth is %d.", output_bit_depth); diff --git a/rtsp.c b/rtsp.c index 923f4ee7..3f27a982 100644 --- a/rtsp.c +++ b/rtsp.c @@ -187,7 +187,7 @@ int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) { int rc; if (the_queue) { if (block == 0) { - rc = pthread_mutex_trylock(&the_queue->pc_queue_lock); + rc = debug_mutex_lock(&the_queue->pc_queue_lock, 10000, 2); if (rc == EBUSY) return EBUSY; } else @@ -1718,7 +1718,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag } if (have_the_player) { - debug(3, "RTSP conversation thread %d has acquired play lock.", conn->connection_number); + debug(3, "Connection %d: ANNOUNCE has acquired play lock.", conn->connection_number); // now, if this new session did not break in, then it's okay to reset the next UDP ports // to the start of the range @@ -1884,10 +1884,8 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag resp->respcode = 200; } else { resp->respcode = 453; - debug(1, "Connection %d: DACP-ID \"%s\" on %s failed because a connection is already playing.", - conn->connection_number, - conn->dacp_id, - conn->client_ip_string); + debug(1, "Connection %d: ANNOUNCE failed because another connection is already playing.", + conn->connection_number); } out: diff --git a/shairport.c b/shairport.c index 21f4ff52..02aa362b 100644 --- a/shairport.c +++ b/shairport.c @@ -1577,7 +1577,7 @@ int main(int argc, char **argv) { debug(1, "use_mmap_if_available is %d.", config.no_mmap ? 0 : 1); debug(1, "output_rate is %d.", config.output_rate); debug(1, - "output_format is %d (0-unknown, 1-S8, 2-U8, 3-S16, 4-S24, 5-S24_3LE, 6-S24_3BE, 7-S32).", + "output_format is %d (0-unknown, 1-S8, 2-U8, 3-S16, 4-S24, 5-S24_3LE, 6-S24_3BE, 7-S32, 8-Automatic).", config.output_format); debug(1, "audio backend desired buffer length is %f seconds.", config.audio_backend_buffer_desired_length); From 4f1608fcbd184dc098e5daeb1a1971ad70b78c04 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 26 Feb 2019 15:24:49 +0000 Subject: [PATCH 554/569] Fix a bug in the activity_monitor and add automatic bit-depth selection. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 24c85b90..fc133aa7 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.3d54], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d55], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 621f70a4df72b7b07fb1e595d4d34c04373bc57a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 27 Feb 2019 11:58:03 +0000 Subject: [PATCH 555/569] Remove a change that proved unnecessary to replay a write() with a non-blocking write(). --- rtsp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtsp.c b/rtsp.c index 3f27a982..5b6748f1 100644 --- a/rtsp.c +++ b/rtsp.c @@ -779,7 +779,7 @@ int msg_write_response(int fd, rtsp_message *resp) { debug(1, "Attempted to write overlong RTSP packet 3"); return -3; } - ssize_t reply = non_blocking_write_with_timeout(fd, pkt, p - pkt, 3000); // wait three seconds (3,000 milliseconds) for it to become available + ssize_t reply = write(fd, pkt, p - pkt); if (reply == -1) { char errorstring[1024]; strerror_r(errno, (char *)errorstring, sizeof(errorstring)); From f74e2c2c9651f8c4ce304d36cce3266dda1cdaf7 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 27 Feb 2019 12:53:09 +0000 Subject: [PATCH 556/569] Revert some experimental changes made to explore problems with Cygwin D-Bus access. --- dbus-service.c | 126 ++++++++++++++++--------------------------------- 1 file changed, 41 insertions(+), 85 deletions(-) diff --git a/dbus-service.c b/dbus-service.c index a51df7f6..a1cde3fd 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -20,7 +20,6 @@ int service_is_running = 0; ShairportSyncDiagnostics *shairportSyncDiagnosticsSkeleton = NULL; ShairportSyncRemoteControl *shairportSyncRemoteControlSkeleton = NULL; ShairportSyncAdvancedRemoteControl *shairportSyncAdvancedRemoteControlSkeleton = NULL; -GDBusConnection *ourBusConnection = NULL; guint ownerID = 0; @@ -434,14 +433,12 @@ gboolean notify_drift_tolerance_callback(ShairportSync *skeleton, } gboolean notify_disable_standby_mode_callback(ShairportSync *skeleton, - __attribute__((unused)) gpointer user_data) { + __attribute__((unused)) gpointer user_data) { char *th = (char *)shairport_sync_get_disable_standby_mode(skeleton); - if ((strcasecmp(th, "no") == 0) || (strcasecmp(th, "off") == 0) || - (strcasecmp(th, "never") == 0)) { + if ((strcasecmp(th, "no") == 0) || (strcasecmp(th, "off") == 0) || (strcasecmp(th, "never") == 0)) { config.disable_standby_mode = disable_standby_off; config.keep_dac_busy = 0; - } else if ((strcasecmp(th, "yes") == 0) || (strcasecmp(th, "on") == 0) || - (strcasecmp(th, "always") == 0)) { + } else if ((strcasecmp(th, "yes") == 0) || (strcasecmp(th, "on") == 0) || (strcasecmp(th, "always") == 0)) { config.disable_standby_mode = disable_standby_always; config.keep_dac_busy = 1; } else if (strcasecmp(th, "while_active") == 0) @@ -449,17 +446,17 @@ gboolean notify_disable_standby_mode_callback(ShairportSync *skeleton, else { warn("An unrecognised disable_standby_mode: \"%s\" was requested via D-Bus interface.", th); switch (config.disable_standby_mode) { - case disable_standby_off: - shairport_sync_set_disable_standby_mode(skeleton, "off"); - break; - case disable_standby_always: - shairport_sync_set_disable_standby_mode(skeleton, "always"); - break; - case disable_standby_while_active: - shairport_sync_set_disable_standby_mode(skeleton, "while_active"); - break; - default: - break; + case disable_standby_off: + shairport_sync_set_disable_standby_mode(skeleton, "off"); + break; + case disable_standby_always: + shairport_sync_set_disable_standby_mode(skeleton, "always"); + break; + case disable_standby_while_active: + shairport_sync_set_disable_standby_mode(skeleton, "while_active"); + break; + default: + break; } } return TRUE; @@ -659,7 +656,7 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name G_CALLBACK(notify_alacdecoder_callback), NULL); g_signal_connect(shairportSyncSkeleton, "notify::disable-standby-mode", G_CALLBACK(notify_disable_standby_mode_callback), NULL); - g_signal_connect(shairportSyncSkeleton, "notify::volume-control-profile", + g_signal_connect(shairportSyncSkeleton, "notify::volume-control-profile", G_CALLBACK(notify_volume_control_profile_callback), NULL); g_signal_connect(shairportSyncSkeleton, "notify::disable-standby", G_CALLBACK(notify_disable_standby_callback), NULL); @@ -747,21 +744,21 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name debug(1, ">> Active set to \"false\""); switch (config.disable_standby_mode) { - case disable_standby_off: - shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "off"); - debug(1, ">> disable standby mode set to \"off\""); - break; - case disable_standby_always: - shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "always"); - debug(1, ">> disable standby mode set to \"always\""); - break; - case disable_standby_while_active: - shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "while_active"); - debug(1, ">> disable standby mode set to \"while_active\""); - break; - default: - debug(1, "invalid disable_standby mode!"); - break; + case disable_standby_off: + shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "off"); + debug(1, ">> disable standby mode set to \"off\""); + break; + case disable_standby_always: + shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "always"); + debug(1, ">> disable standby mode set to \"always\""); + break; + case disable_standby_while_active: + shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "while_active"); + debug(1, ">> disable standby mode set to \"while_active\""); + break; + default: + debug(1,"invalid disable_standby mode!"); + break; } #ifdef CONFIG_SOXR @@ -849,31 +846,6 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name service_is_running = 1; } -// from http://www.ccp4.ac.uk/dist/checkout/glib-2.34.3/gio/tests/gdbus-tests.c -/* ---------------------------------------------------------------------------------------------------- - */ - -GDBusConnection *_g_bus_get_priv(GBusType bus_type, GCancellable *cancellable, GError **error) { - gchar *address; - GDBusConnection *ret; - - ret = NULL; - - address = g_dbus_address_get_for_bus_sync(bus_type, cancellable, error); - if (address == NULL) - goto out; - - ret = g_dbus_connection_new_for_address_sync(address, - G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | - G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, - NULL, /* GDBusAuthObserver */ - cancellable, error); - g_free(address); - -out: - return ret; -} - static void on_dbus_name_lost_again(__attribute__((unused)) GDBusConnection *connection, __attribute__((unused)) const gchar *name, __attribute__((unused)) gpointer user_data) { @@ -891,17 +863,13 @@ static void on_dbus_name_lost(__attribute__((unused)) GDBusConnection *connectio pid_t pid = getpid(); char interface_name[256] = ""; snprintf(interface_name, sizeof(interface_name), "org.gnome.ShairportSync.i%d", pid); - // GBusType dbus_bus_type = G_BUS_TYPE_SYSTEM; - // if (config.dbus_service_bus_type == DBT_session) - // dbus_bus_type = G_BUS_TYPE_SESSION; + GBusType dbus_bus_type = G_BUS_TYPE_SYSTEM; + if (config.dbus_service_bus_type == DBT_session) + dbus_bus_type = G_BUS_TYPE_SESSION; // debug(1, "Looking for a Shairport Sync native D-Bus interface \"%s\" on the %s bus.", // interface_name,(config.dbus_service_bus_type == DBT_session) ? "session" : "system"); - ownerID = - g_bus_own_name_on_connection(ourBusConnection, interface_name, G_BUS_NAME_OWNER_FLAGS_REPLACE, - on_dbus_name_acquired, on_dbus_name_lost_again, NULL, NULL); - - // g_bus_own_name(dbus_bus_type, interface_name, G_BUS_NAME_OWNER_FLAGS_NONE, NULL, - // on_dbus_name_acquired, on_dbus_name_lost_again, NULL, NULL); + g_bus_own_name(dbus_bus_type, interface_name, G_BUS_NAME_OWNER_FLAGS_NONE, NULL, + on_dbus_name_acquired, on_dbus_name_lost_again, NULL, NULL); } int start_dbus_service() { @@ -911,18 +879,8 @@ int start_dbus_service() { dbus_bus_type = G_BUS_TYPE_SESSION; // debug(1, "Looking for a Shairport Sync native D-Bus interface \"org.gnome.ShairportSync\" on // the %s bus.",(config.dbus_service_bus_type == DBT_session) ? "session" : "system"); - - ourBusConnection = _g_bus_get_priv(dbus_bus_type, NULL, NULL); - if (ourBusConnection) { - debug(1, "Got a connection to the bus."); - g_dbus_connection_set_exit_on_close(ourBusConnection, FALSE); - ownerID = g_bus_own_name_on_connection(ourBusConnection, "org.gnome.ShairportSync", - G_BUS_NAME_OWNER_FLAGS_REPLACE, on_dbus_name_acquired, - on_dbus_name_lost, NULL, NULL); - } - - // ownerID = g_bus_own_name(dbus_bus_type, "org.gnome.ShairportSync", G_BUS_NAME_OWNER_FLAGS_NONE, - // NULL, on_dbus_name_acquired, on_dbus_name_lost, NULL, NULL); + ownerID = g_bus_own_name(dbus_bus_type, "org.gnome.ShairportSync", G_BUS_NAME_OWNER_FLAGS_NONE, + NULL, on_dbus_name_acquired, on_dbus_name_lost, NULL, NULL); return 0; // this is just to quieten a compiler warning } @@ -932,11 +890,9 @@ void stop_dbus_service() { g_bus_unown_name(ownerID); else debug(1, "Zero OwnerID for \"org.gnome.ShairportSync\"."); - if (ourBusConnection) - g_object_unref(ourBusConnection); - else - debug(1, "NULL BusConnection for \"org.gnome.ShairportSync\"."); - service_is_running = 0; + service_is_running = 0; } -int dbus_service_is_running() { return service_is_running; } +int dbus_service_is_running() { + return service_is_running; +} From 03eb21a9465b13ad92d66fd6bdde9b9a4dae8bb7 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 27 Feb 2019 18:27:53 +0000 Subject: [PATCH 557/569] Check rate when gwtting negotiated sndio parameters back. --- audio_sndio.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/audio_sndio.c b/audio_sndio.c index 3a544b9a..16367662 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -65,21 +65,21 @@ struct sio_par par; struct sndio_formats { const char *name; enum sps_format_t fmt; - + int rate; unsigned int bits; unsigned int bps; unsigned int sig; unsigned int le; }; -static struct sndio_formats formats[] = {{"S8", SPS_FORMAT_S8, 8, 1, 1, SIO_LE_NATIVE}, - {"U8", SPS_FORMAT_U8, 8, 1, 0, SIO_LE_NATIVE}, - {"S16", SPS_FORMAT_S16, 16, 2, 1, SIO_LE_NATIVE}, - {"AUTOMATIC", SPS_FORMAT_S16, 16, 2, 1, SIO_LE_NATIVE}, // TODO: make this really automatic? - {"S24", SPS_FORMAT_S24, 24, 4, 1, SIO_LE_NATIVE}, - {"S24_3LE", SPS_FORMAT_S24_3LE, 24, 3, 1, 1}, - {"S24_3BE", SPS_FORMAT_S24_3BE, 24, 3, 1, 0}, - {"S32", SPS_FORMAT_S32, 24, 4, 1, SIO_LE_NATIVE}}; +static struct sndio_formats formats[] = {{"S8", SPS_FORMAT_S8, 44100, 8, 1, 1, SIO_LE_NATIVE}, + {"U8", SPS_FORMAT_U8, 44100, 8, 1, 0, SIO_LE_NATIVE}, + {"S16", SPS_FORMAT_S16, 44100, 16, 2, 1, SIO_LE_NATIVE}, + {"AUTOMATIC", SPS_FORMAT_S16, 44100, 16, 2, 1, SIO_LE_NATIVE}, // TODO: make this really automatic? + {"S24", SPS_FORMAT_S24, 44100, 24, 4, 1, SIO_LE_NATIVE}, + {"S24_3LE", SPS_FORMAT_S24_3LE, 44100, 24, 3, 1, 1}, + {"S24_3BE", SPS_FORMAT_S24_3BE, 44100, 24, 3, 1, 0}, + {"S32", SPS_FORMAT_S32, 44100, 24, 4, 1, SIO_LE_NATIVE}}; static void help() { printf(" -d output-device set the output device [default*|...]\n"); } @@ -175,6 +175,7 @@ static int init(int argc, char **argv) { written = played = 0; time_of_last_onmove_cb = 0; at_least_one_onmove_cb_seen = 0; + for (i = 0; i < sizeof(formats) / sizeof(formats[0]); i++) { if (formats[i].fmt == config.output_format) { @@ -185,12 +186,12 @@ static int init(int argc, char **argv) { break; } } - + if (!sio_setpar(hdl, &par) || !sio_getpar(hdl, &par)) die("sndio: failed to set audio parameters"); for (i = 0, found = 0; i < sizeof(formats) / sizeof(formats[0]); i++) { if (formats[i].bits == par.bits && formats[i].bps == par.bps && formats[i].sig == par.sig && - formats[i].le == par.le) { + formats[i].le == par.le && formats[i].rate == par.rate) { config.output_format = formats[i].fmt; found = 1; break; @@ -198,6 +199,7 @@ static int init(int argc, char **argv) { } if (!found) die("sndio: failed to negotiate audio parameters"); + framesize = par.bps * par.pchan; config.output_rate = par.rate; From 1b47023c7857249d676761daa3affac086badfc1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 27 Feb 2019 18:45:45 +0000 Subject: [PATCH 558/569] Make a dying error message a bit more meaningful. --- audio_sndio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_sndio.c b/audio_sndio.c index 16367662..dd403894 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -198,7 +198,7 @@ static int init(int argc, char **argv) { } } if (!found) - die("sndio: failed to negotiate audio parameters"); + die("sndio: could not set output device to the required format and rate."); framesize = par.bps * par.pchan; From 1a0cd5096ed246566fbbe8342f22862ceb4ed793 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 27 Feb 2019 18:46:15 +0000 Subject: [PATCH 559/569] Quieten a warning when there is no cnfiguration file. --- shairport.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shairport.c b/shairport.c index 02aa362b..8f87e1c9 100644 --- a/shairport.c +++ b/shairport.c @@ -867,7 +867,7 @@ int parse_options(int argc, char **argv) { } else { if (config_error_type(&config_file_stuff) == CONFIG_ERR_FILE_IO) - debug(1, "Error reading configuration file \"%s\": \"%s\".", + debug(2, "Error reading configuration file \"%s\": \"%s\".", config_error_file(&config_file_stuff), config_error_text(&config_file_stuff)); else { die("Line %d of the configuration file \"%s\":\n%s", config_error_line(&config_file_stuff), From dc393b98596a4d306c386f0610b280539b1126f0 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 27 Feb 2019 19:30:32 +0000 Subject: [PATCH 560/569] make the formats rate unsigned to avoid a warning. --- audio_sndio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_sndio.c b/audio_sndio.c index dd403894..fcfcc4f6 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -65,7 +65,7 @@ struct sio_par par; struct sndio_formats { const char *name; enum sps_format_t fmt; - int rate; + unsigned int rate; unsigned int bits; unsigned int bps; unsigned int sig; From 042d940415ecd7d87388e5f8810e255282c0128e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 28 Feb 2019 14:21:05 +0000 Subject: [PATCH 561/569] Remove the AUTO bit-depth setting. It's a hack and needs more consideration. --- audio_alsa.c | 122 +++++++++++++++++++-------------------------------- common.c | 2 - common.h | 1 - player.c | 9 ---- shairport.c | 2 +- 5 files changed, 45 insertions(+), 91 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 5896b431..2312d839 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -339,83 +339,51 @@ int actual_open_alsa_device(void) { return ret; } - - if (sample_format == SPS_FORMAT_AUTO) { - - struct alsa_formats { - snd_pcm_format_t alsa_sound_format; - enum sps_format_t sps_sound_format; - int frame_size; - char * description; - }; - - struct alsa_formats formats[] = {{SND_PCM_FORMAT_S32,SPS_FORMAT_S32,8,"S32"}, - {SND_PCM_FORMAT_S24,SPS_FORMAT_S24,8,"S24"}, - {SND_PCM_FORMAT_S24_3LE,SPS_FORMAT_S24_3LE,6,"S24_LE"}, - {SND_PCM_FORMAT_S24_3BE,SPS_FORMAT_S24_3BE,6,"S24_BE"}, - {SND_PCM_FORMAT_S16,SPS_FORMAT_S16,4,"S16"}}; - - int acceptable_format_found = 0; - unsigned int i = 0; - while((!acceptable_format_found) && (i < sizeof(formats) / sizeof(formats[0]))) { - if (snd_pcm_hw_params_set_format(alsa_handle, alsa_params, formats[i].alsa_sound_format) == 0) { - acceptable_format_found = 1; - frame_size = formats[i].frame_size; - config.output_format = formats[i].sps_sound_format; - debug(1,"alsa: output format automatically chosen to be \"%s\".",formats[i].description); - } else { - i++; - } - } - if (!acceptable_format_found) { - die("Can't find a suitable automatic output format setting."); - } - } else { - snd_pcm_format_t sf; - switch (sample_format) { - case SPS_FORMAT_S8: - sf = SND_PCM_FORMAT_S8; - frame_size = 2; - break; - case SPS_FORMAT_U8: - sf = SND_PCM_FORMAT_U8; - frame_size = 2; - break; - case SPS_FORMAT_S16: - sf = SND_PCM_FORMAT_S16; - frame_size = 4; - break; - case SPS_FORMAT_S24: - sf = SND_PCM_FORMAT_S24; - frame_size = 8; - break; - case SPS_FORMAT_S24_3LE: - sf = SND_PCM_FORMAT_S24_3LE; - frame_size = 6; - break; - case SPS_FORMAT_S24_3BE: - sf = SND_PCM_FORMAT_S24_3BE; - frame_size = 6; - break; - case SPS_FORMAT_S32: - sf = SND_PCM_FORMAT_S32; - frame_size = 8; - break; - default: - sf = SND_PCM_FORMAT_S16; // this is just to quieten a compiler warning - frame_size = 4; - debug(1, "Unsupported output format at audio_alsa.c"); - return -EINVAL; - } - - ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sf); - if (ret < 0) { - warn("audio_alsa: Sample format %d not available for device \"%s\": %s", sample_format, - alsa_out_dev, snd_strerror(ret)); - return ret; - } + snd_pcm_format_t sf; + switch (sample_format) { + case SPS_FORMAT_S8: + sf = SND_PCM_FORMAT_S8; + frame_size = 2; + break; + case SPS_FORMAT_U8: + sf = SND_PCM_FORMAT_U8; + frame_size = 2; + break; + case SPS_FORMAT_S16: + sf = SND_PCM_FORMAT_S16; + frame_size = 4; + break; + case SPS_FORMAT_S24: + sf = SND_PCM_FORMAT_S24; + frame_size = 8; + break; + case SPS_FORMAT_S24_3LE: + sf = SND_PCM_FORMAT_S24_3LE; + frame_size = 6; + break; + case SPS_FORMAT_S24_3BE: + sf = SND_PCM_FORMAT_S24_3BE; + frame_size = 6; + break; + case SPS_FORMAT_S32: + sf = SND_PCM_FORMAT_S32; + frame_size = 8; + break; + default: + sf = SND_PCM_FORMAT_S16; // this is just to quieten a compiler warning + frame_size = 4; + debug(1, "Unsupported output format at audio_alsa.c"); + return -EINVAL; } + ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sf); + if (ret < 0) { + warn("audio_alsa: Sample format %d not available for device \"%s\": %s", sample_format, + alsa_out_dev, snd_strerror(ret)); + return ret; + } + + if (set_period_size_request != 0) { debug(1, "Attempting to set the period size"); ret = snd_pcm_hw_params_set_period_size_near(alsa_handle, alsa_params, &period_size_requested, @@ -934,10 +902,8 @@ static int init(int argc, char **argv) { config.output_format = SPS_FORMAT_U8; else if (strcasecmp(str, "S8") == 0) config.output_format = SPS_FORMAT_S8; - else if (strcasecmp(str, "AUTOMATIC") == 0) - config.output_format = SPS_FORMAT_AUTO; else { - warn("Invalid output format \"%s\". It should be \"Automatic\", \"U8\", \"S8\", " + warn("Invalid output format \"%s\". It should be \"U8\", \"S8\", " "\"S16\", \"S24\", " "\"S24_3LE\", \"S24_3BE\" or " "\"S32\". It is set to \"S16\".", diff --git a/common.c b/common.c index ec70e2fe..4db41149 100644 --- a/common.c +++ b/common.c @@ -1391,8 +1391,6 @@ int64_t generate_zero_frames(char *outp, size_t number_of_frames, enum sps_forma case SPS_FORMAT_UNKNOWN: die("Unexpected SPS_FORMAT_UNKNOWN while calculating dither mask."); break; - case SPS_FORMAT_AUTO: - die("Unexpected SPS_FORMAT_AUTO while calculating dither mask."); } dither_mask -= 1; // int64_t r = r64i(); diff --git a/common.h b/common.h index 6f92abbe..9bc849e1 100644 --- a/common.h +++ b/common.h @@ -80,7 +80,6 @@ enum sps_format_t { SPS_FORMAT_S24_3LE, SPS_FORMAT_S24_3BE, SPS_FORMAT_S32, - SPS_FORMAT_AUTO, } sps_format_t; typedef struct { diff --git a/player.c b/player.c index 881a7359..485166ee 100644 --- a/player.c +++ b/player.c @@ -683,9 +683,6 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t case SPS_FORMAT_UNKNOWN: die("Unexpected SPS_FORMAT_UNKNOWN while calculating dither mask."); break; - case SPS_FORMAT_AUTO: - die("Unexpected SPS_FORMAT_AUTO while calculating dither mask."); - break; } dither_mask -= 1; // int64_t r = r64i(); @@ -763,9 +760,6 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t case SPS_FORMAT_UNKNOWN: die("Unexpected SPS_FORMAT_UNKNOWN while outputting samples"); break; - case SPS_FORMAT_AUTO: - die("Unexpected SPS_FORMAT_AUTO while outputting samples"); - break; } *outp += result; @@ -1653,9 +1647,6 @@ void *player_thread_func(void *arg) { case SPS_FORMAT_UNKNOWN: die("Unknown format choosing output bit depth"); break; - case SPS_FORMAT_AUTO: - die("Unexpected SPS_FORMAT_AUTO while outputting samples"); - break; } debug(3, "Output bit depth is %d.", output_bit_depth); diff --git a/shairport.c b/shairport.c index 8f87e1c9..3a0502ca 100644 --- a/shairport.c +++ b/shairport.c @@ -1577,7 +1577,7 @@ int main(int argc, char **argv) { debug(1, "use_mmap_if_available is %d.", config.no_mmap ? 0 : 1); debug(1, "output_rate is %d.", config.output_rate); debug(1, - "output_format is %d (0-unknown, 1-S8, 2-U8, 3-S16, 4-S24, 5-S24_3LE, 6-S24_3BE, 7-S32, 8-Automatic).", + "output_format is %d (0-unknown, 1-S8, 2-U8, 3-S16, 4-S24, 5-S24_3LE, 6-S24_3BE, 7-S32).", config.output_format); debug(1, "audio backend desired buffer length is %f seconds.", config.audio_backend_buffer_desired_length); From 41b584bd4314a119ac8cb9b8b8fe7eb9a8f0088e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 28 Feb 2019 14:48:41 +0000 Subject: [PATCH 562/569] bring the active state timeout to 1 second and tidy up a few debug messages. --- audio_alsa.c | 4 ++-- scripts/shairport-sync.conf | 2 +- shairport.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 2312d839..a8449af6 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1002,7 +1002,7 @@ static int init(int argc, char **argv) { } } - debug(1, "alsa: disable_standby_mode is %d.", config.disable_standby_mode); + debug(1, "alsa: disable_standby_mode is \"%s\".", config.disable_standby_mode == disable_standby_off ? "never" : config.disable_standby_mode == disable_standby_always ? "always" : "while_active"); } optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour @@ -1552,7 +1552,7 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { int okb = -1; while (1) { if (okb != config.keep_dac_busy) { - debug(1,"keep_dac_busy is now %d",config.keep_dac_busy); + debug(1,"keep_dac_busy is now \"%s\"",config.keep_dac_busy == 0 ? "no" : "yes"); okb = config.keep_dac_busy; } if ((config.keep_dac_busy != 0) && (alsa_device_initialised == 0)) { diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index c5d83eb5..2108e2d1 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -59,7 +59,7 @@ 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. // run_this_before_entering_active_state = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line // run_this_after_exiting_active_state = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line -// active_state_timeout = 10; // wait for this number of seconds after play ends before leaving the active state, unless another play session begins. +// active_state_timeout = 1; // wait for this number of seconds after play ends before leaving the active state, unless another play session begins. // run_this_if_an_unfixable_error_is_detected = "/full/path/to/application and args"; // if a problem occurs that can't be cleared by Shairport Sync itself, hook a program on here to deal with it. An error code-string is passed as the last argument. // Many of these "unfixable" problems are caused by malfunctioning output devices, and sometimes it is necessary to restart the whole device to clear the problem. diff --git a/shairport.c b/shairport.c index 3a0502ca..47f82702 100644 --- a/shairport.c +++ b/shairport.c @@ -342,7 +342,7 @@ int parse_options(int argc, char **argv) { // nothing else comes in first. config.fixedLatencyOffset = 11025; // this sounds like it works properly. config.diagnostic_drop_packet_fraction = 0.0; - config.active_state_timeout = 10.0; + config.active_state_timeout = 1.0; config.volume_range_hw_priority = 0; // if combining software and hardware volume control, give the software priority // i.e. when reducing volume, reduce the sw first before reducing the software. From a98a7906de9b7019a72f977a35efba8d69cfffb1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 28 Feb 2019 15:05:32 +0000 Subject: [PATCH 563/569] set active state timeout to 3 seconds. Enough for a YouTube movie switch. --- scripts/shairport-sync.conf | 2 +- shairport.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 2108e2d1..213fd2b0 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -59,7 +59,7 @@ 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. // run_this_before_entering_active_state = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line // run_this_after_exiting_active_state = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line -// active_state_timeout = 1; // wait for this number of seconds after play ends before leaving the active state, unless another play session begins. +// active_state_timeout = 3; // wait for this number of seconds after play ends before leaving the active state, unless another play session begins. // run_this_if_an_unfixable_error_is_detected = "/full/path/to/application and args"; // if a problem occurs that can't be cleared by Shairport Sync itself, hook a program on here to deal with it. An error code-string is passed as the last argument. // Many of these "unfixable" problems are caused by malfunctioning output devices, and sometimes it is necessary to restart the whole device to clear the problem. diff --git a/shairport.c b/shairport.c index 47f82702..b65db5ef 100644 --- a/shairport.c +++ b/shairport.c @@ -342,7 +342,7 @@ int parse_options(int argc, char **argv) { // nothing else comes in first. config.fixedLatencyOffset = 11025; // this sounds like it works properly. config.diagnostic_drop_packet_fraction = 0.0; - config.active_state_timeout = 1.0; + config.active_state_timeout = 3.0; config.volume_range_hw_priority = 0; // if combining software and hardware volume control, give the software priority // i.e. when reducing volume, reduce the sw first before reducing the software. From d2e1910fbaad060d27d6cd4b192ef158af68a3db Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 28 Feb 2019 17:33:51 +0000 Subject: [PATCH 564/569] set active state timeout to 10 seconds. Enough for a YouTube movie switch -- 3 or 5 seconds doesn't seem to be enough. --- scripts/shairport-sync.conf | 2 +- shairport.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 213fd2b0..e2ffeba4 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -59,7 +59,7 @@ 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. // run_this_before_entering_active_state = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line // run_this_after_exiting_active_state = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line -// active_state_timeout = 3; // wait for this number of seconds after play ends before leaving the active state, unless another play session begins. +// active_state_timeout = 10.0; // wait for this number of seconds after play ends before leaving the active state, unless another play session begins. // run_this_if_an_unfixable_error_is_detected = "/full/path/to/application and args"; // if a problem occurs that can't be cleared by Shairport Sync itself, hook a program on here to deal with it. An error code-string is passed as the last argument. // Many of these "unfixable" problems are caused by malfunctioning output devices, and sometimes it is necessary to restart the whole device to clear the problem. diff --git a/shairport.c b/shairport.c index b65db5ef..3a0502ca 100644 --- a/shairport.c +++ b/shairport.c @@ -342,7 +342,7 @@ int parse_options(int argc, char **argv) { // nothing else comes in first. config.fixedLatencyOffset = 11025; // this sounds like it works properly. config.diagnostic_drop_packet_fraction = 0.0; - config.active_state_timeout = 3.0; + config.active_state_timeout = 10.0; config.volume_range_hw_priority = 0; // if combining software and hardware volume control, give the software priority // i.e. when reducing volume, reduce the sw first before reducing the software. From a3736837d359277b12fb511d25a45143c0d1a8b7 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 28 Feb 2019 21:51:11 +0000 Subject: [PATCH 565/569] set default for disable standby to 'never' --- audio_alsa.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index a8449af6..4010629d 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -985,7 +985,7 @@ static int init(int argc, char **argv) { /* Get the optional disable_standby_mode setting. */ - config.disable_standby_mode = disable_standby_while_active; + config.disable_standby_mode = disable_standby_off; config.keep_dac_busy = 0; if (config_lookup_string(config.cfg, "alsa.disable_standby_mode", &str)) { if ((strcasecmp(str, "no") == 0) || (strcasecmp(str, "off") == 0) || (strcasecmp(str, "never") == 0)) @@ -998,7 +998,7 @@ static int init(int argc, char **argv) { else { warn("Invalid disable_standby_mode option choice \"%s\". It should be " "\"always\", \"while_active\" or \"never\". " - "It is set to \"while_active\"."); + "It is set to \"never\"."); } } From 182c0a31a4454636f97a152cf68a707368fa0670 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 28 Feb 2019 21:53:07 +0000 Subject: [PATCH 566/569] set default for disable standby to 'never' --- scripts/shairport-sync.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index e2ffeba4..9d4edcda 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -86,7 +86,7 @@ alsa = // use_mmap_if_available = "yes"; // Use this optional advanced setting to control whether MMAP-based output is used to communicate with the DAC. Default is "yes" // use_hardware_mute_if_available = "no"; // Use this optional advanced setting to control whether the hardware in the DAC is used for muting. Default is "no", for compatibility with other audio players. // maximum_stall_time = 0.200; // Use this optional advanced setting to control how long to wait for data to be consumed by the output device before considering it an error. It should never approach 200 ms. -// disable_standby_mode = "while_active"; // Some DACs make small "popping" noises when they go in and out of standby mode. Settings can be: "always", "while_active" or "never". Default is "while_active". You can use "yes" instead of "always" and "no instead of "never". +// disable_standby_mode = "never"; // Some DACs make small "popping" noises when they go in and out of standby mode. Settings can be: "always", "while_active" or "never". Default is "never", but only for backwards compatibility. You can use "yes" instead of "always" and "no instead of "never". }; // Parameters for the "sndio" audio back end. All are optional. From 0ef617924f1d5cd67abda05caacddfc08c38d637 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 28 Feb 2019 22:13:21 +0000 Subject: [PATCH 567/569] fix active mode lockup problem, set defaults for active mode and disable standby. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index fc133aa7..7126a1b0 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.3d55], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d56], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 24f6eb7ef10d3ef1795d1f2f7e566baea8e7a5d3 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 28 Feb 2019 22:36:27 +0000 Subject: [PATCH 568/569] Update RELEASENOTES.md --- RELEASENOTES.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 556e5ed1..41510a48 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,12 +1,12 @@ -Version 3.3d54 to Version 3.3d40 +Version 3.3d56 to Version 3.3d40 ==== **New Features** -* Two new external program/script hooks – `run_this_before_entering_active_state` and `run_this_after_exiting_active_state` are provided for when the system goes active or inactive. Background: Many users use the `run_this_before_play_begins` program hook to turn on an amplifier and the `run_this_after_play_ends` hook to turn it off. A big problem is when another play session starts immediately afterwards. This happens, for example, when a YouTube clip ends and the next one begins, and the time interval can be very short. All this switching can stress electronics and speakers. To get around this, the concept of an *active state* covering a sequence of play sessions is introduced. An active state begins when a play session starts. When the play session ends, the system stays in the active state for a period called the `active_state_timeout`, 10 seconds by default. If another play session starts before that timeout elapses, the system remains in the active state; otherwise the system leaves the active state. The two new hooks mentioned above can be used to execute programs/scripts when the system goes active or inactive. +* Two new external program/script hooks – `run_this_before_entering_active_state` and `run_this_after_exiting_active_state` are provided for when the system goes active or inactive. Background: Many users use the `run_this_before_play_begins` program hook to turn on an amplifier and the `run_this_after_play_ends` hook to turn it off. A big problem is when another play session starts immediately after a play session ends, causing the amplifier to be switched off and then on again very quickly. This happens, for example, when a YouTube clip ends and the next one begins. To get around this, the concept of an *active state* covering a sequence of play sessions is introduced. When a play session starts the system goes active, i.e. it enters the active state. When the play session ends, the system remains active for a period determined by the `active_state_timeout` setting, 10 seconds by default. If another play session starts before the period elapses, the system stays active; otherwise the system goes inactive -- it leaves the active state. The two new hooks mentioned above can be used to execute programs/scripts when the system goes active or inactive. * A new `alsa`-only `disable_standby_mode` setting, for controlling the Disable Standby feature, can be set to `always`, `while_active` or `never`. The `always` setting is recommended for systems where the output device is dedicated to Shairport Sync. The "Disable Standby" state iself can be set or cleared via the D-Bus interface `DisableStandby` property. * A new command-line option, `-u`, directs logging to STDERR rather than the system log. Useful when you compile Shairport Sync without `libdaemon` using the `--without-libdaemon` configuration option. **Enhancements** -* Changes to the Jack Audio back end. The back end for jack audio, `audio_jack.c`, has been extensively rewritten by [Jörn Nettingsmeier](https://github.com/nettings) in a way that is more in keeping with the Jack Audio style. It uses native jack lockless buffers and offers autoconnect facilities that the previous version didn't have. Many thanks to him. +* Changes to the Jack Audio back end. The back end for Jack Audio, `audio_jack.c`, has been extensively rewritten by [Jörn Nettingsmeier](https://github.com/nettings) in a way that is more in keeping with the Jack Audio style. It uses native Jack Audio lockless buffers and offers autoconnect facilities that the previous version didn't have. Many thanks to him. * The volume-control software has been completely rewritten. From a user's point of view, the result should be a much smoother response to volume contol changes, free from artefacts. It is now also possible to combine the hardware mixer and the software attenuator in two ways -- giving priority to the software mixer or giving priority to the hardware mixer. see the new `volume_range_combined_hardware_priority` setting in the `general` section opf the configuration file. * The muting/unmuting code has been rewritten to be simpler and more consistent. * In the `alsa` backend, new `play()` and `delay()` functions minimise the use of `snd_pcm_recover()` to prevent unnecessary resets of the output DACs. @@ -23,13 +23,13 @@ Version 3.3d39 to Version 3.3d38 **New Feature** -A new feature called *Disable Standby* keeps the output DAC in the play state all the time and helps to remove some annoying clicking / popping noises. It is really pretty impressive, especially combined with the fixes to the dithering code described below. If you can enable 24- or 32-bit audio output to your output device, you can get even better results. +A new feature called *Disable Standby* keeps the output DAC in the play state all the time and helps to remove some annoying clicking / popping noises. It is really pretty impressive, especially combined with fixes to the dithering code described below. If you can enable 24- or 32-bit audio output to your output device, you can get even better results. This is an ALSA-specific attempt to remove the annoying low-level clicking sounds that some output devices make just when they start processing audio and sometimes when they stop. Typically a faint click might be heard just before a play session starts or just before audio resumes after a pause. Similarly, a faint click can sometimes be heard just after a play session ends. -It is extremely difficult to remove these clicks completely from the hardware, so this new feature ensures that — once Shairport Sync has started — the output device avoids situations where these clicks might be generated by *always* playing audio. To accomplish this, if Shairport Sync isn't actually playing anything, audio frames consisting of silence are sent to the output device. Apart from the initial startup transition, the output device never stops playing, avoiding both starting and stopping transitions. +It is extremely difficult to remove these clicks completely from the hardware, so this new feature ensures that the output device avoids situations where these clicks might be generated by *always* playing audio. To accomplish this, if Shairport Sync isn't actually playing anything, audio frames consisting of silence are sent to the output device, keeping it playing. Apart from the initial startup transition, the output device never stops playing and thus never transitions to and from standby mode, avoiding the possibility of generating associated audio disturbances. -To enable this feature, a new `alsa` group setting with the name `disable_standby_mode` is available. (If you do a full `$ sudo make install`, a new sample configuration file with this setting in it is installed at `/etc/shairport-sync.conf.sample` or `/usr/local/etc/shairport-sync.conf.sample`.) The default for the setting is `"no"`. *(Note -- this was changed subsequently; please see above.)* +To enable this feature, a new `alsa` group setting with the name `disable_standby_mode` is available. (If you do a full `$ sudo make install`, a new sample configuration file with this setting in it is installed at `/etc/shairport-sync.conf.sample` or `/usr/local/etc/shairport-sync.conf.sample`.) A downside to this feature is that the output device totally dedicated to Shairport Sync. For this reason, this new feature is disabled by default. @@ -37,7 +37,7 @@ Note -- this setting is likely to change. It will probably be necessary to modif **Enhancements and Bug Fixes** -* At present, Shairport Sync adds dither to the audio if (a) the built-in software-based volume control is used, (b) if the audio is mixed to mono or (3) if there is a change in sample size, say from 16- to 24-bit. The code for generating dither has been much improved. Due to a poor pseudo-random number arrangement, the dither noise didn't sound like white noise -- now it does. In addition, if dither is to be added, it is also added to the silence inserted just prior to the start of play, and is also added to the silent frames used to prevent the output device from going into standby mode, if selected. +* At present, Shairport Sync adds dither to the audio (a) if the built-in software-based volume control is used, (b) if the audio is mixed to mono or (3) if there is a reduction in sample size, say from 16- to 8-bit. The code for generating dither has been much improved. Due to a poor pseudo-random number arrangement, the dither noise didn't sound like white noise -- now it does. In addition, if dither is to be added, it is also added to the silence inserted just prior to the start of play, and is also added to the silent frames used to prevent the output device from going into standby mode, if selected. Version 3.3d37 to Version 3.3d22 ==== @@ -46,7 +46,7 @@ These updates are about stability. **Enhancements and Bug Fixes** -With great help from [gibman](https://github.com/gibman) — see [#772](https://github.com/mikebrady/shairport-sync/issues/772) for the gory details — myriad issues have been identified and fixed. In particular, [gibman](https://github.com/gibman) shared an automated way of stress-testing Shairport Sync, and this has resulted in the detection of many bugs. +With great help from [gibman](https://github.com/gibman) — see [#772](https://github.com/mikebrady/shairport-sync/issues/772) for the gory details — a myriad of issues have been identified and fixed. In particular, [gibman](https://github.com/gibman) shared an automated way of stress-testing Shairport Sync, and this has resulted in the detection of many bugs. And so, with apologies to Shakespeare, we have taken up arms against a sea of troubles, and by opposing we have ended them. It is hoped that the result is considerably more stable and can better withstand the, uh, slings and arrows of outrageous fortune. @@ -67,7 +67,7 @@ Here is a flavour of some of the issues addressed: * Fix a compilation error and a warning when using the `--with-convolution` configuration option. **New Feature** -* A new `run_this_if_an_unfixable_error_is_detected` (in the `sessioncontrol` group of settings) program hook is provided. At the moment, two conditions can cause this to be activated. The first is if the watchdog is unable to terminate a play session. The second is if the output device stalls for a long period. Both conditions can be caused by malfunctioning DACs. The external program could, for example, reboot the device. +* A new `run_this_if_an_unfixable_error_is_detected` (in the `sessioncontrol` group of settings) program hook is provided. At the moment, two conditions can trigger this. The first is if the watchdog is unable to terminate a play session. The second is if the output device stalls for a long period. Both conditions can be caused by malfunctioning DACs. The external program could, for example, reboot the device. Version 3.3d21 ==== From 704352a4d64d07a63222b4bacb56548489364ff4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 3 Mar 2019 14:14:58 +0000 Subject: [PATCH 569/569] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 41510a48..f3da1dd9 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,7 +1,7 @@ Version 3.3d56 to Version 3.3d40 ==== **New Features** -* Two new external program/script hooks – `run_this_before_entering_active_state` and `run_this_after_exiting_active_state` are provided for when the system goes active or inactive. Background: Many users use the `run_this_before_play_begins` program hook to turn on an amplifier and the `run_this_after_play_ends` hook to turn it off. A big problem is when another play session starts immediately after a play session ends, causing the amplifier to be switched off and then on again very quickly. This happens, for example, when a YouTube clip ends and the next one begins. To get around this, the concept of an *active state* covering a sequence of play sessions is introduced. When a play session starts the system goes active, i.e. it enters the active state. When the play session ends, the system remains active for a period determined by the `active_state_timeout` setting, 10 seconds by default. If another play session starts before the period elapses, the system stays active; otherwise the system goes inactive -- it leaves the active state. The two new hooks mentioned above can be used to execute programs/scripts when the system goes active or inactive. +* Two new external program/script hooks – `run_this_before_entering_active_state` and `run_this_after_exiting_active_state` are provided for when the system goes active or inactive. Background: Many users use the `run_this_before_play_begins` program hook to turn on an amplifier and the `run_this_after_play_ends` hook to turn it off. A big problem is when another play session starts immediately after a play session ends, causing the amplifier to be switched off and then on again very quickly. This happens, for example, when a YouTube clip ends and the next one begins. To get around this, the concept of an *active state* covering a sequence of play sessions is introduced. When a play session starts, the system goes active, i.e. it enters the active state. When the play session ends, the system remains active for a period determined by the `active_state_timeout` setting, 10 seconds by default. If another play session starts before the period elapses, the system stays active; otherwise the system goes inactive -- it leaves the active state. The two new hooks mentioned above can be used to execute programs/scripts when the system goes active or inactive. * A new `alsa`-only `disable_standby_mode` setting, for controlling the Disable Standby feature, can be set to `always`, `while_active` or `never`. The `always` setting is recommended for systems where the output device is dedicated to Shairport Sync. The "Disable Standby" state iself can be set or cleared via the D-Bus interface `DisableStandby` property. * A new command-line option, `-u`, directs logging to STDERR rather than the system log. Useful when you compile Shairport Sync without `libdaemon` using the `--without-libdaemon` configuration option.