From c2e3fa5aa7ccb0d658869e4d70d9240c9f6153ec Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 14 Jul 2018 13:38:29 +0100 Subject: [PATCH 01/31] 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 02/31] 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 03/31] 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 04/31] 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 05/31] 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 06/31] 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 07/31] 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 08/31] 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 09/31] 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 10/31] 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 11/31] 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 12/31] 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 a4eaace76b07027f5832071de4ed44c726b91786 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 17 Jul 2018 10:28:06 +0100 Subject: [PATCH 13/31] 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 14/31] 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 15/31] 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 2eaaa9cdb3fdd25ba96f165fdf68c813cfacf25f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 21 Jul 2018 15:35:07 +0100 Subject: [PATCH 16/31] 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 17/31] 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 18/31] 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 19/31] 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 20/31] 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 21/31] 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 22/31] 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 23/31] 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 24/31] 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 25/31] 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 26/31] 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 27/31] 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 28/31] 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 29/31] 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 30/31] 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 31/31] 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.