Merge branch 'mikebrady:development' into development
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user