diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 93ed43d9..0a1be528 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,3 +1,24 @@ +Version-4.1-dev-160-g9c2f9f7a +==== +#### Bugfix +* Fix a crashing bug in AirPlay 1 ("Classic") mode, whereby if a session was abruptly interrupted, e.g. by the sudden loss of a network, Shairport Sync would crash. The problem was that the interrupted session was not correctly terminated. Thanks to [th0u](https://github.com/th0u) for reporting issue [#1398](https://github.com/mikebrady/shairport-sync/issues/1398). + +Version-4.1-dev-158-gb5860fc4 +==== +#### Enhancements +The following enhancements are mainly experimental, for advanced users only, and are intended to enable multiple instances of Shairport Sync (SPS) to run in one computer and provide multiple AirPlay 2 endpoints. At this point, multiple instances of SPS can indeed run on one machine and provide multiple AirPlay 2 endpoints, but unfortunately the presence of multiple services at the same address seems to confuse AirPlay 2 clients. So, it seems that while multiple instances of SPS can now run successfully on one device, they can't be used on the same network. +* Use a uniquely-named named SMI interface to get dedicated access to NQPTP. +* Simplify validation of the mDNS interface setting, if given. +* Exit with a warning if the player is using a name that already exists. +* Stop storing the timing peer list. +* Generate an initial timing list during setup — don’t wait for the first timing peer list message. May gain around 50 ms. +* Fix a bug and clean up generation of device's timing addresses. +* Add some control to the generation of the Device ID: +1. Revise how the Device ID is generated from the device’s MAC addresses +2. Add a setting to add a fixed offset to the automatically-generated Device ID. +3. Add a setting to specify a complete Device ID, replacing the automatically-generated Device ID. +* Be more careful to create default file names based on the name of the app. + Version-4.1-dev-146-g42e508b4 ==== #### Enhancements diff --git a/common.h b/common.h index f4139e56..904b9279 100644 --- a/common.h +++ b/common.h @@ -152,7 +152,7 @@ typedef struct { int mqtt_publish_cover; int mqtt_enable_remote; #endif - uint8_t hw_addr[8]; // only needs 6 but 8 is handy when converting this to a number + uint8_t hw_addr[8]; // only needs 6 but 8 is handy when converting this to a number int port; int udp_port_base; int udp_port_range; @@ -302,6 +302,7 @@ typedef struct { char *airplay_device_id; // for the Bonjour advertisement and the GETINFO PList char *airplay_pin; // non-NULL, 4 char PIN, if required for pairing char *airplay_pi; // UUID in the Bonjour advertisement and the GETINFO Plist + char *nqptp_shared_memory_interface_name; // client name for nqptp service #endif } shairport_cfg; diff --git a/mdns_avahi.c b/mdns_avahi.c index 7103b1ee..3dc58565 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -242,14 +242,21 @@ static void register_service(AvahiClient *c) { else selected_interface = AVAHI_IF_UNSPEC; if (ap2_text_record_string_list) { - ret = avahi_entry_group_add_service_strlst(group, selected_interface, AVAHI_PROTO_UNSPEC, 0, + ret = avahi_entry_group_add_service_strlst(group, selected_interface, AVAHI_PROTO_UNSPEC, 0, ap2_service_name, config.regtype2, NULL, NULL, port, ap2_text_record_string_list); + if (ret == AVAHI_ERR_COLLISION) { + die("Error: AirPlay 2 name \"%s\" is already in use.", ap2_service_name); + } + } if ((ret == 0) && (text_record_string_list)) { ret = avahi_entry_group_add_service_strlst(group, selected_interface, AVAHI_PROTO_UNSPEC, 0, service_name, config.regtype, NULL, NULL, port, text_record_string_list); + if (ret == AVAHI_ERR_COLLISION) { + die("Error: AirPlay 1 name \"%s\" is already in use.", service_name); + } } if (ret == 0) { ret = avahi_entry_group_commit(group); diff --git a/mqtt.c b/mqtt.c index b898ab9e..c4982d99 100644 --- a/mqtt.c +++ b/mqtt.c @@ -211,7 +211,6 @@ void mqtt_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t le case 'svip': mqtt_publish("server_ip", data, length); break; - } } } diff --git a/nqptp-shm-structures.h b/nqptp-shm-structures.h index f4db8c6e..0791197d 100644 --- a/nqptp-shm-structures.h +++ b/nqptp-shm-structures.h @@ -1,6 +1,6 @@ /* * This file is part of the nqptp distribution (https://github.com/mikebrady/nqptp). - * Copyright (c) 2021 Mike Brady. + * Copyright (c) 2021--2022 Mike Brady. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,16 +20,27 @@ #ifndef NQPTP_SHM_STRUCTURES_H #define NQPTP_SHM_STRUCTURES_H -#define STORAGE_ID "/nqptp" -#define MAX_CLOCKS 32 -#define NQPTP_SHM_STRUCTURES_VERSION 6 +#define NQPTP_SHM_STRUCTURES_VERSION 7 #define NQPTP_CONTROL_PORT 9000 -// the control port will accept a UDP packet with the first letter being: -// "T", followed by a space and then a space-delimited -// list of ip numbers, either IPv4 or IPv6 -// the whole not to exceed 4096 characters in total -// The IPs will become the new list of timing peers, replacing any previous +// The control port expects a UDP packet with the first space-delimited string +// being the name of the shared memory interface (SMI) to be used. +// This allows client applications to have a dedicated named SMI interface with +// a timing peer list independent of other clients. +// The name given must be a valid SMI name and must contain no spaces. +// If the named SMI interface doesn't exist it will be created by NQPTP. +// The SMI name should be delimited by a space and followed by a command letter. +// At present, the only command is "T", which must followed by nothing or by +// a space and a space-delimited list of IPv4 or IPv6 numbers, +// the whole not to exceed 4096 characters in total. +// The IPs, if provided, will become the new list of timing peers, replacing any +// previous list. If the master clock of the new list is the same as that of the old list, +// the master clock is retained without resynchronisation; this means that non-master +// devices can be added and removed without disturbing the SMI's existing master clock. +// If no timing list is provided, the existing timing list is deleted. +// (In future version of NQPTP the SMI interface may also be deleted at this point.) +// SMI interfaces are not currently deleted or garbage collected. + #include #include @@ -37,7 +48,6 @@ struct shm_structure { pthread_mutex_t shm_mutex; // for safely accessing the structure uint16_t version; // check this is equal to NQPTP_SHM_STRUCTURES_VERSION - uint32_t flags; // unused uint64_t master_clock_id; // the current master clock char master_clock_ip[64]; // where it's coming from uint64_t local_time; // the time when the offset was calculated diff --git a/player.c b/player.c index db7ec169..914a3b43 100644 --- a/player.c +++ b/player.c @@ -1063,22 +1063,24 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { int64_t lt = conn->first_packet_time_to_play - local_time_now; - if (lt < 130000000) { - debug(1, "Connection %d: Short lead time for first frame %" PRId64 ": %f seconds. Flushing 0.5 seconds", - conn->connection_number, conn->first_packet_timestamp, lt * 0.000000001); + if (lt < 100000000) { + debug(1, + "Connection %d: Short lead time for first frame %" PRId64 + ": %f seconds. Flushing 0.5 seconds", + conn->connection_number, conn->first_packet_timestamp, lt * 0.000000001); do_flush(conn->first_packet_timestamp + 5 * 4410, conn); } else { debug(2, "Connection %d: Lead time for first frame %" PRId64 ": %f seconds.", - conn->connection_number, conn->first_packet_timestamp, lt * 0.000000001); + conn->connection_number, conn->first_packet_timestamp, lt * 0.000000001); } -/* - int64_t lateness = local_time_now - conn->first_packet_time_to_play; - if (lateness > 0) { - debug(1, "First packet is %" PRId64 " nanoseconds late! Flushing 0.5 seconds", - lateness); + /* + int64_t lateness = local_time_now - conn->first_packet_time_to_play; + if (lateness > 0) { + debug(1, "First packet is %" PRId64 " nanoseconds late! Flushing 0.5 + seconds", lateness); - } -*/ + } + */ } if (conn->first_packet_time_to_play != 0) { @@ -1304,6 +1306,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { wait = 1; // keep waiting until the timing information becomes available } if (wait) { + if (conn->input_rate == 0) + die("input_rate is zero -- should never happen!"); uint64_t time_to_wait_for_wakeup_ns = 1000000000 / conn->input_rate; // this is time period of one frame time_to_wait_for_wakeup_ns *= 2 * 352; // two full 352-frame packets diff --git a/player.h b/player.h index edf80837..526c99f2 100644 --- a/player.h +++ b/player.h @@ -350,9 +350,6 @@ typedef struct { uint64_t type; uint64_t networkTimeTimelineID; // the clock ID used by the player uint8_t groupContainsGroupLeader; // information coming from the SETUP - - char *ap2_timing_peer_list_message; - #endif // used as the initials values for calculating the rate at which the source thinks it's sending diff --git a/ptp-utilities.c b/ptp-utilities.c index fa1c6407..e04058c6 100644 --- a/ptp-utilities.c +++ b/ptp-utilities.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #ifdef COMPILE_FOR_FREEBSD @@ -116,22 +117,28 @@ int ptp_get_clock_info(uint64_t *actual_clock_id, uint64_t *time_of_sample, uint int ptp_shm_interface_open() { mapped_addr = NULL; - int shared_memory_file_descriptor = shm_open("/nqptp", O_RDWR, 0); - int response = 0; - if (shared_memory_file_descriptor >= 0) { - mapped_addr = - // needs to be PROT_READ | PROT_WRITE to allow the mapped memory to be writable for the - // mutex to lock and unlock - mmap(NULL, sizeof(struct shm_structure), PROT_READ | PROT_WRITE, MAP_SHARED, - shared_memory_file_descriptor, 0); - if (mapped_addr == MAP_FAILED) { - response = -1; - } - if (close(shared_memory_file_descriptor) == -1) { + int response = -1; + if (strcmp(config.nqptp_shared_memory_interface_name, "") != 0) { + response = 0; + int shared_memory_file_descriptor = + shm_open(config.nqptp_shared_memory_interface_name, O_RDWR, 0); + if (shared_memory_file_descriptor >= 0) { + mapped_addr = + // needs to be PROT_READ | PROT_WRITE to allow the mapped memory to be writable for the + // mutex to lock and unlock + mmap(NULL, sizeof(struct shm_structure), PROT_READ | PROT_WRITE, MAP_SHARED, + shared_memory_file_descriptor, 0); + if (mapped_addr == MAP_FAILED) { + response = -1; + } + if (close(shared_memory_file_descriptor) == -1) { + response = -1; + } + } else { response = -1; } } else { - response = -1; + debug(1, "No config.nqptp_shared_memory_interface_name"); } return response; } @@ -148,27 +155,41 @@ int ptp_shm_interface_close() { } void ptp_send_control_message_string(const char *msg) { - debug(2, "Send control message to NQPTP: \"%s\"", msg); - int s; - unsigned short port = htons(NQPTP_CONTROL_PORT); - struct sockaddr_in server; + size_t full_message_size = + strlen(config.nqptp_shared_memory_interface_name) + strlen(" ") + strlen(msg) + 1; + char *full_message = malloc(full_message_size); + if (full_message != NULL) { + *full_message = '\0'; + snprintf(full_message, full_message_size, "%s %s", config.nqptp_shared_memory_interface_name, + msg); + debug(2, "Send control message to NQPTP: \"%s\"", full_message); + int s; + unsigned short port = htons(NQPTP_CONTROL_PORT); + struct sockaddr_in server; - /* Create a datagram socket in the internet domain and use the - * default protocol (UDP). - */ - if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { - die("Can't open a socket to NQPTP"); + /* Create a datagram socket in the internet domain and use the + * default protocol (UDP). + */ + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + die("Can't open a socket to NQPTP"); + } + + /* Set up the server name */ + server.sin_family = AF_INET; /* Internet Domain */ + server.sin_port = port; /* Server Port */ + server.sin_addr.s_addr = 0; /* Server's Address */ + + /* Send the message in buf to the server */ + if (sendto(s, full_message, full_message_size, 0, (struct sockaddr *)&server, sizeof(server)) < + 0) { + die("error sending timing_peer_list to NQPTP"); + } + /* Deallocate the socket */ + close(s); + + /* deallocate the message string */ + free(full_message); + } else { + debug(1, "Couldn't allocate memory to prepare a qualified ptp control message string."); } - - /* Set up the server name */ - server.sin_family = AF_INET; /* Internet Domain */ - server.sin_port = port; /* Server Port */ - server.sin_addr.s_addr = 0; /* Server's Address */ - - /* Send the message in buf to the server */ - if (sendto(s, msg, (strlen(msg) + 1), 0, (struct sockaddr *)&server, sizeof(server)) < 0) { - die("error sending timing_peer_list to NQPTP"); - } - /* Deallocate the socket */ - close(s); } diff --git a/rtp.c b/rtp.c index 348ac869..bc3fe8fb 100644 --- a/rtp.c +++ b/rtp.c @@ -290,8 +290,8 @@ void *rtp_control_receiver(void *arg) { obfp += 2; }; *obfp = 0; - - + + // get raw timestamp information // I think that a good way to understand these timestamps is that // (1) the rtlt below is the timestamp of the frame that should be playing at the @@ -302,19 +302,19 @@ void *rtp_control_receiver(void *arg) { // Thus, (3) the latency can be calculated by subtracting the second from the // first. // There must be more to it -- there something missing. - + // In addition, it seems that if the value of the short represented by the second // pair of bytes in the packet is 7 // then an extra time lag is expected to be added, presumably by // the AirPort Express. - + // Best guess is that this delay is 11,025 frames. - + uint32_t rtlt = nctohl(&packet[4]); // raw timestamp less latency uint32_t rt = nctohl(&packet[16]); // raw timestamp - + uint32_t fl = nctohs(&packet[2]); // - + debug(1,"Sync Packet of %d bytes received: \"%s\", flags: %d, timestamps %u and %u, giving a latency of %d frames.",plen,obf,fl,rt,rtlt,rt-rtlt); //debug(1,"Monotonic timestamps are: %" PRId64 " and %" PRId64 " @@ -1452,9 +1452,8 @@ int get_ptp_anchor_local_time_info(rtsp_conn_info *conn, uint32_t *anchorRTP, *anchorRTP = conn->last_anchor_rtptime; if (anchorLocalTime != NULL) *anchorLocalTime = conn->last_anchor_local_time; - - response = clock_ok; } + return response; } diff --git a/rtsp.c b/rtsp.c index f6930dd6..c4e8d831 100644 --- a/rtsp.c +++ b/rtsp.c @@ -536,7 +536,7 @@ void release_play_lock(rtsp_conn_info *conn) { } int get_play_lock(rtsp_conn_info *conn) { - debug(2, "Connection %d: request play lock.", conn->connection_number); + debug(2, "Connection %d: request play lock.", conn->connection_number); // returns -1 if it failed, 0 if it succeeded and 1 if it succeeded but // interrupted an existing session int response = 0; @@ -1176,9 +1176,9 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes ssize_t buflen = 4096; #ifdef CONFIG_METADATA if ((config.metadata_enabled != 0) && (config.get_coverart != 0)) - buflen = 1024 * 256; // big enough for typical picture data, which will be base64 encoded + buflen = 1024 * 256; // big enough for typical picture data, which will be base64 encoded #endif - int release_buffer = 0; // on exit, don't deallocate the buffer if everything was okay + int release_buffer = 0; // on exit, don't deallocate the buffer if everything was okay char *buf = malloc(buflen + 1); // add a NUL at the end if (!buf) { warn("Connection %d: rtsp_read_request: can't get a buffer.", conn->connection_number); @@ -1348,9 +1348,12 @@ int msg_write_response(rtsp_conn_info *conn, rtsp_message *resp) { char *string; }; - struct response_t responses[] = { - {200, "OK"}, {400, "Bad Request"}, {403, "Unauthorized"}, {451, "Unavailable"}, {501, "Not Implemented"}}; - // 451 is really "Unavailable For Legal Reasons"! + struct response_t responses[] = {{200, "OK"}, + {400, "Bad Request"}, + {403, "Unauthorized"}, + {451, "Unavailable"}, + {501, "Not Implemented"}}; + // 451 is really "Unavailable For Legal Reasons"! int found = 0; char *respcode_text = "Unauthorized"; for (i = 0; i < sizeof(responses) / sizeof(struct response_t); i++) { @@ -1470,6 +1473,9 @@ void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) if (conn->player_thread) warn("Connection %d: RECORD: Duplicate RECORD message -- ignored", conn->connection_number); else { + debug(1, "Connection %d: AP1 ALAC Stream, from %s:%u to self at %s:%u.", + conn->connection_number, conn->client_ip_string, conn->client_rtsp_port, + conn->self_ip_string, conn->self_rtsp_port); activity_monitor_signify_activity(1); player_prepare_to_play(conn); player_play(conn); // the thread better be 0 @@ -1845,18 +1851,6 @@ void handle_setrateanchori(rtsp_conn_info *conn, rtsp_message *req, rtsp_message // reset_anchor_info(conn); // needed if the player resumes } pthread_cleanup_pop(1); // unlock the conn->flush_mutex - - if ((rate & 1) != 0) { - // keep this outside the flush_mutex lock - if (conn->ap2_timing_peer_list_message) { - // ptp_send_control_message_string(conn->ap2_timing_peer_list_message); - } else { - debug(1, "Connection %d: No timing peer list!", conn->connection_number); - } - } else { - // player_full_flush(conn); - // ptp_send_control_message_string("T"); // ensure an obsolete clock isn't picked up later. - } } pthread_cleanup_pop(1); // plist_free the messagePlist; } else { @@ -2302,7 +2296,8 @@ void handle_command(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message * uint64_t length = 0; plist_get_data_val(the_item, &buff, &length); // debug(1,"Item %d, length: %" PRId64 " bytes", item_number, length); - if ((buff != NULL) && (length >= strlen("bplist00")) && (strstr(buff, "bplist00") == buff)) { + if ((buff != NULL) && (length >= strlen("bplist00")) && + (strstr(buff, "bplist00") == buff)) { // debug(1,"Contains a plist."); plist_t subsidiary_plist = NULL; plist_from_memory(buff, length, &subsidiary_plist); @@ -2403,15 +2398,7 @@ void handle_setpeers(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp if (ip_address != NULL) free(ip_address); } - - if (conn->ap2_timing_peer_list_message != NULL) - free(conn->ap2_timing_peer_list_message); - conn->ap2_timing_peer_list_message = strdup(timing_list_message); - // if this rtsp thread is playing... - // if (try_to_hold_play_lock(conn) == 0) { - ptp_send_control_message_string(conn->ap2_timing_peer_list_message); - // release_hold_on_play_lock(conn); - // } + ptp_send_control_message_string(timing_list_message); } plist_free(addresses_array); resp->respcode = 200; @@ -2494,10 +2481,6 @@ void teardown_phase_two(rtsp_conn_info *conn) { free(conn->dacp_active_remote); conn->dacp_active_remote = NULL; } - if (conn->ap2_timing_peer_list_message) { - free(conn->ap2_timing_peer_list_message); - conn->ap2_timing_peer_list_message = NULL; - } release_play_lock(conn); } } @@ -2523,6 +2506,7 @@ void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_messag plist_free(streams); debug(2, "Connection %d: TEARDOWN phase one complete", conn->connection_number); } else { + teardown_phase_one(conn); // try to do phase one anyway teardown_phase_two(conn); debug(2, "Connection %d: TEARDOWN phase two complete", conn->connection_number); } @@ -2542,20 +2526,28 @@ void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_messag } #endif +void teardown(rtsp_conn_info *conn) { + player_stop(conn); + activity_monitor_signify_activity(0); // inactive, and should be after command_stop() + if (conn->dacp_active_remote != NULL) { + free(conn->dacp_active_remote); + conn->dacp_active_remote = NULL; + } +} + void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, rtsp_message *resp) { debug_log_rtsp_message(2, "TEARDOWN request", req); debug(2, "Connection %d: TEARDOWN", conn->connection_number); if (have_play_lock(conn)) { - resp->respcode = 200; - msg_add_header(resp, "Connection", "close"); debug( 3, "TEARDOWN: synchronously terminating the player thread of RTSP conversation thread %d (2).", conn->connection_number); - player_stop(conn); + teardown(conn); release_play_lock(conn); - activity_monitor_signify_activity(0); // inactive, and should be after command_stop() + resp->respcode = 200; + msg_add_header(resp, "Connection", "close"); debug(3, "TEARDOWN: successful termination of playing thread of RTSP conversation thread %d.", conn->connection_number); } else { @@ -2563,10 +2555,6 @@ void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message conn->connection_number); resp->respcode = 451; } - if (conn->dacp_active_remote != NULL) { - free(conn->dacp_active_remote); - conn->dacp_active_remote = NULL; - } // debug(1,"Bogus exit for valgrind -- remember to comment it out!."); // exit(EXIT_SUCCESS); } @@ -2614,10 +2602,9 @@ void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { #ifdef CONFIG_AIRPLAY_2 - #ifdef CONFIG_METADATA -static void check_and_send_plist_metadata(plist_t messagePlist, const char *plist_key, uint32_t metadata_code) -{ +static void check_and_send_plist_metadata(plist_t messagePlist, const char *plist_key, + uint32_t metadata_code) { plist_t item = plist_dict_get_item(messagePlist, plist_key); if (item) { char *value; @@ -2654,7 +2641,9 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) plist_get_string_val(timingProtocol, &timingProtocolString); if (timingProtocolString) { if (strcmp(timingProtocolString, "PTP") == 0) { - debug(2, "Connection %d: SETUP: PTP setup detected.", conn->connection_number); + debug(1, "Connection %d: AP2 PTP connection from %s:%u to self at %s:%u.", + conn->connection_number, conn->client_ip_string, conn->client_rtsp_port, + conn->self_ip_string, conn->self_rtsp_port); conn->airplay_stream_category = ptp_stream; conn->timing_type = ts_ptp; get_play_lock(conn); @@ -2674,8 +2663,9 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) uint8_t isRemoteControlOnlyBoolean = 0; plist_get_bool_val(isRemoteControlOnly, &isRemoteControlOnlyBoolean); if (isRemoteControlOnlyBoolean != 0) { - debug(2, "Connection %d: SETUP: Remote Control setup detected.", - conn->connection_number); + debug(1, "Connection %d: Remote Control connection from %s:%u to self at %s:%u.", + conn->connection_number, conn->client_ip_string, conn->client_rtsp_port, + conn->self_ip_string, conn->self_rtsp_port); conn->airplay_stream_category = remote_control_stream; } else { debug(1, @@ -2724,87 +2714,30 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) debug(1, "No groupContainsGroupLeader in SETUP"); } + char timing_list_message[4096]; + timing_list_message[0] = 'T'; + timing_list_message[1] = 0; + plist_t timing_peer_info = plist_dict_get_item(messagePlist, "timingPeerInfo"); if (timing_peer_info) { - // first, get and retain the incoming plist. - plist_t addresses = plist_new_array(); // to hold the device's peer interfaces + // first, get the incoming plist. plist_t addresses_array = plist_dict_get_item(timing_peer_info, "Addresses"); if (addresses_array) { // iterate through the array of items uint32_t items = plist_array_get_size(addresses_array); if (items) { - uint32_t item; - struct ifaddrs *addrs, *iap; - getifaddrs(&addrs); for (item = 0; item < items; item++) { plist_t n = plist_array_get_item(addresses_array, item); char *ip_address = NULL; plist_get_string_val(n, &ip_address); // debug(1, "Timing peer: %s", ip_address); - // find its family and convert it into an IPv4/6 address - - struct sockaddr_storage peer_address; - struct sockaddr_in6 *pa6 = (struct sockaddr_in6 *)&peer_address; - struct sockaddr_in *pa4 = (struct sockaddr_in *)&peer_address; - - if (inet_pton(AF_INET6, ip_address, &pa6->sin6_addr) == - 1) { // is an IPv6 address... - peer_address.ss_family = AF_INET6; - for (iap = addrs; iap != NULL; iap = iap->ifa_next) { - if ((iap->ifa_addr) && (iap->ifa_netmask) && (iap->ifa_flags & IFF_UP) && - ((iap->ifa_flags & IFF_LOOPBACK) == 0) && - (iap->ifa_addr->sa_family == AF_INET6)) { - struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)(iap->ifa_addr); - struct sockaddr_in6 *mask6 = (struct sockaddr_in6 *)(iap->ifa_netmask); - unsigned int i; - int different = 0; - for (i = 0; ((i < 16) && (different == 0)); i++) { - unsigned char host_byte = - (addr6->sin6_addr.s6_addr[i] & mask6->sin6_addr.s6_addr[i]); - unsigned char peer_byte = - (pa6->sin6_addr.s6_addr[i] & mask6->sin6_addr.s6_addr[i]); - if (host_byte != peer_byte) - different = 1; - } - - char buf[32]; - memset(buf, 0, sizeof(buf)); - inet_ntop(AF_INET6, (void *)&addr6->sin6_addr, buf, sizeof(buf)); - // don't insist there are in the same subnet... - //if (!different) { - // debug(1, "%s is in the same subnet as %s.", buf, ip_address); - plist_array_append_item(addresses, plist_new_string(buf)); - //} - } - } - } else if (inet_pton(AF_INET, ip_address, &pa4->sin_addr) == 1) { - peer_address.ss_family = AF_INET; - for (iap = addrs; iap != NULL; iap = iap->ifa_next) { - if ((iap->ifa_addr) && (iap->ifa_netmask) && (iap->ifa_flags & IFF_UP) && - ((iap->ifa_flags & IFF_LOOPBACK) == 0) && - (iap->ifa_addr->sa_family == AF_INET)) { - struct sockaddr_in *addr = (struct sockaddr_in *)(iap->ifa_addr); - // not needed if not checking for same subnet - // struct sockaddr_in *mask = (struct sockaddr_in *)(iap->ifa_netmask); - // if ((addr->sin_addr.s_addr & mask->sin_addr.s_addr) == - // (pa4->sin_addr.s_addr & mask->sin_addr.s_addr)) { - char buf[32]; - memset(buf, 0, sizeof(buf)); - inet_ntop(AF_INET, (void *)&addr->sin_addr, buf, sizeof(buf)); - // no longer insisting they are in the same subnet - // debug(1, "%s is in the same subnet as %s.", buf, ip_address); - plist_array_append_item(addresses, plist_new_string(buf)); - // } - } - } - } else { - debug(1, "SETUP on Connection %d: Don't recognise this as an IP address.", - conn->connection_number); - } + strncat(timing_list_message, " ", + sizeof(timing_list_message) - 1 - strlen(timing_list_message)); + strncat(timing_list_message, ip_address, + sizeof(timing_list_message) - 1 - strlen(timing_list_message)); free(ip_address); } - freeifaddrs(addrs); } else { debug(1, "SETUP on Connection %d: No timingPeerInfo addresses in the array.", conn->connection_number); @@ -2816,6 +2749,48 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) // make up the timing peer info list part of the response... // debug(1,"Create timingPeerInfoPlist"); plist_t timingPeerInfoPlist = plist_new_dict(); + plist_t addresses = plist_new_array(); // to hold the device's interfaces + plist_array_append_item(addresses, plist_new_string(conn->self_ip_string)); + // debug(1,"self ip: \"%s\"", conn->self_ip_string); + + struct ifaddrs *addrs, *iap; + getifaddrs(&addrs); + for (iap = addrs; iap != NULL; iap = iap->ifa_next) { + // debug(1, "Interface index %d, name: \"%s\"",if_nametoindex(iap->ifa_name), + // iap->ifa_name); + if ((iap->ifa_addr) && (iap->ifa_netmask) && (iap->ifa_flags & IFF_UP) && + ((iap->ifa_flags & IFF_LOOPBACK) == 0)) { + char buf[INET6_ADDRSTRLEN + 1]; // +1 for a NUL + memset(buf, 0, sizeof(buf)); + if (iap->ifa_addr->sa_family == AF_INET6) { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)(iap->ifa_addr); + inet_ntop(AF_INET6, (void *)&addr6->sin6_addr, buf, sizeof(buf)); + plist_array_append_item(addresses, plist_new_string(buf)); + // debug(1, "Own address IPv6: %s", buf); + + // strncat(timing_list_message, " ", + // sizeof(timing_list_message) - 1 - strlen(timing_list_message)); + // strncat(timing_list_message, buf, + // sizeof(timing_list_message) - 1 - strlen(timing_list_message)); + + } else { + struct sockaddr_in *addr = (struct sockaddr_in *)(iap->ifa_addr); + inet_ntop(AF_INET, (void *)&addr->sin_addr, buf, sizeof(buf)); + plist_array_append_item(addresses, plist_new_string(buf)); + // debug(1, "Own address IPv4: %s", buf); + + // strncat(timing_list_message, " ", + // sizeof(timing_list_message) - 1 - strlen(timing_list_message)); + // strncat(timing_list_message, buf, + // sizeof(timing_list_message) - 1 - strlen(timing_list_message)); + } + } + } + freeifaddrs(addrs); + + // debug(1,"initial timing peer command: \"%s\".", timing_list_message); + ptp_send_control_message_string(timing_list_message); + plist_dict_set_item(timingPeerInfoPlist, "Addresses", addresses); plist_dict_set_item(timingPeerInfoPlist, "ID", plist_new_string(conn->self_ip_string)); plist_dict_set_item(setupResponsePlist, "timingPeerInfo", timingPeerInfoPlist); @@ -2854,8 +2829,6 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) get_category_string(conn->airplay_stream_category)); mdns_update(NULL, secondary_txt_records); - - resp->respcode = 200; } else { debug(1, "SETUP on Connection %d: PTP setup -- no timingPeerInfo plist.", @@ -2898,8 +2871,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) plist_dict_set_item(setupResponsePlist, "eventPort", plist_new_uint(conn->local_event_port)); - plist_dict_set_item(setupResponsePlist, "timingPort", - plist_new_uint(0)); + plist_dict_set_item(setupResponsePlist, "timingPort", plist_new_uint(0)); cancel_all_RTSP_threads( remote_control_stream, conn->connection_number); // kill all the other remote control listeners @@ -3001,7 +2973,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) switch (item_value) { case 96: { - debug(1, "Connection %d. Realtime Audio Stream Detected.", conn->connection_number); + debug(1, "Connection %d. AP2 Realtime Audio Stream.", conn->connection_number); debug_log_rtsp_message(2, "Realtime Audio Stream SETUP incoming message", req); // get_play_lock(conn); conn->airplay_stream_type = realtime_stream; @@ -3048,11 +3020,6 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) conn->input_bit_depth = conn->stream.fmtp[3]; conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); debug(2, "Realtime Stream Play"); - if (conn->ap2_timing_peer_list_message) { - // ptp_send_control_message_string(conn->ap2_timing_peer_list_message); - } else { - debug(1, "No timing peer list!"); - } activity_monitor_signify_activity(1); player_prepare_to_play(conn); player_play(conn); @@ -3060,8 +3027,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) conn->rtp_running = 1; // hack! } break; case 103: { - debug(1, "SETUP on Connection %d. Buffered Audio Stream Detected.", - conn->connection_number); + debug(1, "Connection %d. AP2 Buffered Audio Stream.", conn->connection_number); debug_log_rtsp_message(2, "Buffered Audio Stream SETUP incoming message", req); // get_play_lock(conn); conn->airplay_stream_type = buffered_stream; @@ -4698,8 +4664,15 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { debug(2, "Connection %d: rtsp_conversation_thread_func_cleanup_function called.", conn->connection_number); #ifdef CONFIG_AIRPLAY_2 + // AP2 teardown_phase_one(conn); teardown_phase_two(conn); +#else + // AP1 + if (have_play_lock(conn)) { + teardown(conn); + release_play_lock(conn); + } #endif debug(3, "Connection %d terminating:Closing timing, control and audio sockets...", @@ -5214,7 +5187,7 @@ void *rtsp_listen_loop(__attribute((unused)) void *arg) { inet_ntop(conn->connection_ip_family, self_addr, conn->self_ip_string, sizeof(conn->self_ip_string)); - 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, conn->client_ip_string, conn->client_rtsp_port, conn->self_ip_string, conn->self_rtsp_port); } else { diff --git a/shairport.c b/shairport.c index 88db7924..86d073c8 100644 --- a/shairport.c +++ b/shairport.c @@ -289,7 +289,8 @@ void usage(char *progname) { #ifdef CONFIG_METADATA printf(" -M, --metadata-enable ask for metadata from the source and process it.\n"); printf(" --metadata-pipename=PIPE send metadata to PIPE, e.g. " - "--metadata-pipename=/tmp/%s-metadata.\n", config.appName); + "--metadata-pipename=/tmp/%s-metadata.\n", + config.appName); printf(" The default is /tmp/%s-metadata.\n", config.appName); printf(" -g, --get-coverart send cover art through the metadata pipe.\n"); #endif @@ -776,32 +777,15 @@ int parse_options(int argc, char **argv) { config.interface = strdup(str); if (config_lookup_string(config.cfg, "general.interface", &str)) { - int specified_interface_found = 0; - struct if_nameindex *if_ni, *i; + config.interface_index = if_nametoindex(config.interface); - if_ni = if_nameindex(); - if (if_ni == NULL) { - debug(1, "Can't get a list of interface names."); - } else { - for (i = if_ni; !(i->if_index == 0 && i->if_name == NULL); i++) { - // printf("%u: %s\n", i->if_index, i->if_name); - if (strcmp(i->if_name, str) == 0) { - config.interface_index = i->if_index; - specified_interface_found = 1; - } - } - } - - if_freenameindex(if_ni); - - if (specified_interface_found == 0) { + if (config.interface_index == 0) { inform( "The mdns service interface \"%s\" was not found, so the setting has been ignored.", config.interface); free(config.interface); config.interface = NULL; - config.interface_index = 0; } } @@ -1262,7 +1246,7 @@ int parse_options(int argc, char **argv) { strcat(temp_metadata_pipe_name, config.appName); strcat(temp_metadata_pipe_name, "-metadata"); config.metadata_pipename = strdup(temp_metadata_pipe_name); - debug(1,"default metadata_pipename is \"%s\".", temp_metadata_pipe_name); + debug(1, "default metadata_pipename is \"%s\".", temp_metadata_pipe_name); } #endif @@ -1320,7 +1304,7 @@ int parse_options(int argc, char **argv) { char temp_pid_dir[4096]; strcpy(temp_pid_dir, "/var/run/"); strcat(temp_pid_dir, config.appName); - debug(1,"default pid filename is \"%s\".", temp_pid_dir); + debug(1, "default pid filename is \"%s\".", temp_pid_dir); char *use_this_pid_dir = temp_pid_dir; #endif // debug(1,"config.piddir \"%s\".",config.piddir); @@ -1445,6 +1429,14 @@ void exit_function() { #ifdef CONFIG_AIRPLAY_2 if (config.regtype2) free(config.regtype2); + if (config.nqptp_shared_memory_interface_name) + free(config.nqptp_shared_memory_interface_name); + if (config.airplay_device_id) + free(config.airplay_device_id); + if (config.airplay_pin) + free(config.airplay_pin); + if (config.airplay_pi) + free(config.airplay_pi); #endif #ifdef CONFIG_LIBDAEMON @@ -1462,6 +1454,7 @@ void exit_function() { config_destroy(config.cfg); if (config.appName) free(config.appName); + // probably should be freeing malloc'ed memory here, including strdup-created strings... #ifdef CONFIG_LIBDAEMON @@ -1833,32 +1826,39 @@ int main(int argc, char **argv) { // use the start of the config.hw_addr and the PID to generate the default airplay_device_id uint64_t apid = nctoh64(config.hw_addr); apid = apid >> 16; // we only use the first 6 bytes but have imported 8. - + int64_t aid; - + // add the airplay_device_id_offset if provided if (config_lookup_int64(config.cfg, "general.airplay_device_id_offset", &aid)) { apid += aid; - } - + } + // replace the airplay_device_id with this, if provided if (config_lookup_int64(config.cfg, "general.airplay_device_id", &aid)) { apid = aid; } - - char apids[6*2+5+1]; // six pairs of digits, 5 colons and a NUL - apids[6*2+5] = 0; // NUL termination + + char shared_memory_interface_name[256] = ""; + snprintf(shared_memory_interface_name, sizeof(shared_memory_interface_name), "/%s-%" PRIx64 "", + config.appName, apid); + // debug(1, "smi name: \"%s\"", shared_memory_interface_name); + + config.nqptp_shared_memory_interface_name = strdup(shared_memory_interface_name); + + char apids[6 * 2 + 5 + 1]; // six pairs of digits, 5 colons and a NUL + apids[6 * 2 + 5] = 0; // NUL termination int i; char hexchar[] = "0123456789abcdef"; for (i = 5; i >= 0; i--) { - apids[i*3+1] = hexchar[apid & 0xF]; + apids[i * 3 + 1] = hexchar[apid & 0xF]; apid = apid >> 4; - apids[i*3] = hexchar[apid & 0xF]; + apids[i * 3] = hexchar[apid & 0xF]; apid = apid >> 4; if (i != 0) - apids[i*3-1] = ':'; + apids[i * 3 - 1] = ':'; } - + config.airplay_device_id = strdup(apids); // now generate a UUID @@ -2017,9 +2017,12 @@ int main(int argc, char **argv) { debug(1, "run_this_after_play_ends action is \"%s\".", strnull(config.cmd_stop)); debug(1, "wait-cmd status is %d.", config.cmd_blocking); debug(1, "run_this_before_play_begins may return output is %d.", config.cmd_start_returns_output); - debug(1, "run_this_if_an_unfixable_error_is_detected action is \"%s\".", strnull(config.cmd_unfixable)); - debug(1, "run_this_before_entering_active_state action is \"%s\".", strnull(config.cmd_active_start)); - debug(1, "run_this_after_exiting_active_state action is \"%s\".", strnull(config.cmd_active_stop)); + debug(1, "run_this_if_an_unfixable_error_is_detected action is \"%s\".", + strnull(config.cmd_unfixable)); + debug(1, "run_this_before_entering_active_state action is \"%s\".", + strnull(config.cmd_active_start)); + debug(1, "run_this_after_exiting_active_state action is \"%s\".", + strnull(config.cmd_active_stop)); debug(1, "active_state_timeout is %f seconds.", config.active_state_timeout); debug(1, "mdns backend \"%s\".", strnull(config.mdns_name)); debug(2, "userSuppliedLatency is %d.", config.userSuppliedLatency);