Merge branch 'mikebrady:development' into development

This commit is contained in:
Charles
2022-01-04 13:47:18 +00:00
committed by GitHub
11 changed files with 266 additions and 231 deletions
+21
View File
@@ -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
+2 -1
View File
@@ -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;
+8 -1
View File
@@ -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);
-1
View File
@@ -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;
}
}
}
+20 -10
View File
@@ -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 <inttypes.h>
#include <pthread.h>
@@ -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
+15 -11
View File
@@ -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
-3
View File
@@ -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
+55 -34
View File
@@ -30,6 +30,7 @@
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#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);
}
+8 -9
View File
@@ -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;
}
+98 -125
View File
@@ -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 {
+39 -36
View File
@@ -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);