@@ -365,11 +393,13 @@
process of adding or removing frames of audio to or from the
stream sent to the output device in order to keep it synchronised
with the player.
- The basic mode is normally almost completely inaudible.
- The alternative mode, soxr, is even less obtrusive but
+ The vernier mode is normally almost completely inaudible and suitable for low-powered devices.
+ An alternative mode, soxr, is less obtrusive but
requires much more processing power. For this mode, support for
libsoxr, the SoX Resampler Library, must be selected when
shairport-sync is built.
+ The basic mode is also suitable for low-powered device,
+ but vernier is better.
The default setting, auto, allows Shairport Sync to choose
soxr mode if the system is powerful enough.
@@ -485,6 +515,10 @@
+
+ The dacquery utility () might be useful for discovering the properties of your system's DACs.
+
+
Mike Brady () developed Shairport Sync from Shairport by James Wah ().
diff --git a/mdns_avahi.c b/mdns_avahi.c
index 10d89d92..2db0f289 100644
--- a/mdns_avahi.c
+++ b/mdns_avahi.c
@@ -91,8 +91,7 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn
const char *name, const char *type, const char *domain,
__attribute__((unused)) const char *host_name,
#if defined(CONFIG_METADATA) || defined(CONFIG_DACP_CLIENT)
- __attribute__((unused)) const AvahiAddress *address,
- uint16_t lport,
+ __attribute__((unused)) const AvahiAddress *address, uint16_t lport,
#else
__attribute__((unused)) const AvahiAddress *address,
__attribute__((unused)) uint16_t lport,
@@ -120,7 +119,7 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn
while (*dacpid == '0')
dacpid++; // skip any leading zeroes
if (strcmp(dacpid, dbs->dacp_id) == 0) {
- debug(1, "resolve_callback: client dacp_id \"%s\" dacp port: %u.", dbs->dacp_id, lport);
+ debug(4, "resolve_callback: client dacp_id \"%s\" dacp port: %u.", dbs->dacp_id, lport);
#ifdef CONFIG_DACP_CLIENT
dacp_monitor_port_update_callback(dacpid, lport);
#endif
@@ -178,7 +177,7 @@ static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, Avah
dacp_monitor_port_update_callback(dbs->dacp_id, 0); // say the port is withdrawn
#endif
#ifdef CONFIG_METADATA
- send_ssnc_metadata('dapo', "0", strlen("0"), 0);
+ send_ssnc_metadata('dapo', "0", strlen("0"), 0);
#endif
}
} else {
diff --git a/metadata/core.c b/metadata/core.c
index d4537cf5..23a803c0 100644
--- a/metadata/core.c
+++ b/metadata/core.c
@@ -25,15 +25,14 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
-
#include "core.h"
#include "pc_queue.h"
-#include
-#include
-#include
-#include
#include
+#include
+#include
+#include
+#include
#ifdef CONFIG_METADATA_PIPE
#include "pipe.h"
@@ -51,7 +50,6 @@
#include "mqtt.h"
#endif
-
// Metadata is not used by shairport-sync.
// Instead we send all metadata to a fifo pipe, so that other apps can listen to
// the pipe and use the metadata.
@@ -167,14 +165,14 @@ void metadata_init(void) {
#ifdef CONFIG_METADATA_PIPE
metadata_pipe_queue_init();
-#endif
+#endif
#ifdef CONFIG_METADATA_MULTICAST
- metadata_multicast_queue_init();
+ metadata_multicast_queue_init();
#endif
#ifdef CONFIG_METADATA_HUB
- metadata_hub_queue_init();
+ metadata_hub_queue_init();
#endif
#ifdef CONFIG_MQTT
@@ -195,14 +193,13 @@ void metadata_stop(void) {
metadata_hub_queue_stop();
#endif
-#ifdef CONFIG_METADATA_MULTICAST
+#ifdef CONFIG_METADATA_MULTICAST
metadata_multicast_queue_stop();
#endif
-#ifdef CONFIG_METADATA_PIPE
+#ifdef CONFIG_METADATA_PIPE
metadata_pipe_queue_stop();
#endif
-
}
}
@@ -259,17 +256,18 @@ int send_metadata_to_queue(pc_queue *queue, const uint32_t type, const uint32_t
if (pack.carrier) {
if (rc == EWOULDBLOCK)
debug(2,
- "metadata queue \"%s\" full, dropping message item: type %x, code %x, data %" PRIxPTR ", "
+ "metadata queue \"%s\" full, dropping message item: type %x, code %x, data %" PRIxPTR
+ ", "
"length %u, message %d.",
queue->name, pack.type, pack.code, (uintptr_t)pack.data, pack.length,
pack.carrier->index_number);
msg_free(&pack.carrier);
} else {
if (rc == EWOULDBLOCK)
- debug(
- 2,
- "metadata queue \"%s\" full, dropping data item: type %x, code %x, data %" PRIxPTR ", length %u.",
- queue->name, pack.type, pack.code, (uintptr_t)pack.data, pack.length);
+ debug(2,
+ "metadata queue \"%s\" full, dropping data item: type %x, code %x, data %" PRIxPTR
+ ", length %u.",
+ queue->name, pack.type, pack.code, (uintptr_t)pack.data, pack.length);
if (pack.data)
free(pack.data);
}
@@ -287,8 +285,7 @@ int send_metadata(const uint32_t type, const uint32_t code, const char *data, co
#endif
#ifdef CONFIG_METADATA_MULTICAST
- rc =
- send_metadata_to_multicast_queue(type, code, data, length, carrier, block);
+ rc = send_metadata_to_multicast_queue(type, code, data, length, carrier, block);
#endif
#ifdef CONFIG_METADATA_HUB
@@ -298,7 +295,6 @@ int send_metadata(const uint32_t type, const uint32_t code, const char *data, co
#ifdef CONFIG_MQTT
rc = send_metadata_to_mqtt_queue(type, code, data, length, carrier, block);
#endif
-
}
return rc;
}
@@ -307,4 +303,3 @@ int send_ssnc_metadata(const uint32_t code, const char *data, const uint32_t len
const int block) {
return send_metadata('ssnc', code, data, length, NULL, block);
}
-
diff --git a/metadata/core.h b/metadata/core.h
index 85ed7646..115245e3 100644
--- a/metadata/core.h
+++ b/metadata/core.h
@@ -1,8 +1,8 @@
#pragma once
+#include "../rtsp.h"
#include "common.h"
#include "config.h"
-#include "../rtsp.h"
#include "pc_queue.h"
#include
@@ -21,7 +21,6 @@ void metadata_pack_cleanup_function(void *arg);
void metadata_init(void);
void metadata_stop(void);
-
int send_metadata_to_queue(pc_queue *queue, const uint32_t type, const uint32_t code,
const char *data, const uint32_t length, rtsp_message *carrier,
int block);
diff --git a/metadata/hub.c b/metadata/hub.c
index fe2c2e14..528fc73d 100644
--- a/metadata/hub.c
+++ b/metadata/hub.c
@@ -43,10 +43,10 @@
#include "config.h"
#include "common.h"
+#include "core.h"
#include "dacp.h"
#include "hub.h"
#include "pc_queue.h"
-#include "core.h"
#ifdef CONFIG_MBEDTLS
#include
@@ -759,15 +759,14 @@ void metadata_hub_queue_init() {
}
void metadata_hub_queue_stop() {
- // debug(2, "metadata stop hub thread.");
- pthread_cancel(metadata_hub_thread);
- pthread_join(metadata_hub_thread, NULL);
- pc_queue_delete(&metadata_hub_queue);
- // debug(2, "metadata stop hub done.");
+ // debug(2, "metadata stop hub thread.");
+ pthread_cancel(metadata_hub_thread);
+ pthread_join(metadata_hub_thread, NULL);
+ pc_queue_delete(&metadata_hub_queue);
+ // debug(2, "metadata stop hub done.");
}
-int send_metadata_to_hub_queue(const uint32_t type, const uint32_t code,
- const char *data, const uint32_t length, rtsp_message *carrier,
- int block) {
- return send_metadata_to_queue(&metadata_hub_queue, type, code, data, length, carrier, block);
+int send_metadata_to_hub_queue(const uint32_t type, const uint32_t code, const char *data,
+ const uint32_t length, rtsp_message *carrier, int block) {
+ return send_metadata_to_queue(&metadata_hub_queue, type, code, data, length, carrier, block);
}
diff --git a/metadata/hub.h b/metadata/hub.h
index 754f9c4a..2d0670b2 100644
--- a/metadata/hub.h
+++ b/metadata/hub.h
@@ -194,7 +194,5 @@ void _metadata_hub_read_epilog(const char *filename, const int linenumber);
// metadata queue stuff
void metadata_hub_queue_init();
void metadata_hub_queue_stop();
-int send_metadata_to_hub_queue(const uint32_t type, const uint32_t code,
- const char *data, const uint32_t length, rtsp_message *carrier,
- int block);
-
+int send_metadata_to_hub_queue(const uint32_t type, const uint32_t code, const char *data,
+ const uint32_t length, rtsp_message *carrier, int block);
diff --git a/metadata/multicast.c b/metadata/multicast.c
index de4930fb..679e02f8 100644
--- a/metadata/multicast.c
+++ b/metadata/multicast.c
@@ -1,16 +1,15 @@
- #include "multicast.h"
- #include "core.h"
- #include "pc_queue.h"
-
- #include
- #include
-
-
+#include "multicast.h"
+#include "core.h"
+#include "pc_queue.h"
+
+#include
+#include
+
// A special sub-protocol is used for sending large data items over UDP
// If the payload exceeded 4 MB, it is chunked using the following format:
// "ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data.
// Notice that the number of items is different to the standard
-
+
#define METADATA_SNDBUF (4 * 1024 * 1024)
static int metadata_sock = -1;
static struct sockaddr_in metadata_sockaddr;
@@ -20,10 +19,10 @@ pc_queue metadata_multicast_queue;
metadata_package metadata_multicast_queue_items[metadata_multicast_queue_size];
pthread_t metadata_multicast_thread;
-int send_metadata_to_multicast_queue(const uint32_t type, const uint32_t code,
- const char *data, const uint32_t length, rtsp_message *carrier,
- int block) {
- return send_metadata_to_queue(&metadata_multicast_queue, type, code, data, length, carrier, block);
+int send_metadata_to_multicast_queue(const uint32_t type, const uint32_t code, const char *data,
+ const uint32_t length, rtsp_message *carrier, int block) {
+ return send_metadata_to_queue(&metadata_multicast_queue, type, code, data, length, carrier,
+ block);
}
void metadata_create_multicast_socket(void) {
@@ -162,12 +161,12 @@ void *metadata_multicast_thread_function(__attribute__((unused)) void *ignore) {
}
void metadata_multicast_queue_init() {
- // create a pc_queue for the metadata_multicast_queue
- pc_queue_init(&metadata_multicast_queue, (char *)&metadata_multicast_queue_items,
- sizeof(metadata_package), metadata_multicast_queue_size, "multicast");
- if (named_pthread_create(&metadata_multicast_thread, NULL, metadata_multicast_thread_function,
- NULL, "metadata mcst") != 0)
- debug(1, "Failed to create metadata multicast thread!");
+ // create a pc_queue for the metadata_multicast_queue
+ pc_queue_init(&metadata_multicast_queue, (char *)&metadata_multicast_queue_items,
+ sizeof(metadata_package), metadata_multicast_queue_size, "multicast");
+ if (named_pthread_create(&metadata_multicast_thread, NULL, metadata_multicast_thread_function,
+ NULL, "metadata mcst") != 0)
+ debug(1, "Failed to create metadata multicast thread!");
}
void metadata_multicast_queue_stop() {
diff --git a/metadata/multicast.h b/metadata/multicast.h
index 0f35b1be..66db8e92 100644
--- a/metadata/multicast.h
+++ b/metadata/multicast.h
@@ -1,10 +1,9 @@
#pragma once
-#include
#include "rtsp.h"
+#include
void metadata_multicast_queue_init();
void metadata_multicast_queue_stop();
-int send_metadata_to_multicast_queue(const uint32_t type, const uint32_t code,
- const char *data, const uint32_t length, rtsp_message *carrier,
- int block);
\ No newline at end of file
+int send_metadata_to_multicast_queue(const uint32_t type, const uint32_t code, const char *data,
+ const uint32_t length, rtsp_message *carrier, int block);
\ No newline at end of file
diff --git a/metadata/pc_queue.c b/metadata/pc_queue.c
index 296ccbdc..86bc9845 100644
--- a/metadata/pc_queue.c
+++ b/metadata/pc_queue.c
@@ -27,9 +27,9 @@
#include "pc_queue.h"
+#include
#include
#include
-#include
void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items,
const char *name) {
@@ -169,4 +169,3 @@ int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) {
}
return 0;
}
-
diff --git a/metadata/pipe.c b/metadata/pipe.c
index 35128861..72a41816 100644
--- a/metadata/pipe.c
+++ b/metadata/pipe.c
@@ -1,14 +1,14 @@
- #include "pipe.h"
- #include "core.h"
- #include "pc_queue.h"
-
- #include
- #include
- #include
- #include
+#include "pipe.h"
+#include "core.h"
+#include "pc_queue.h"
+
+#include
+#include
+#include
+#include
static int fd = -1; // the pipe file descriptor
-
+
pc_queue metadata_queue;
#define metadata_queue_size 500
metadata_package metadata_queue_items[metadata_queue_size];
@@ -152,8 +152,8 @@ void metadata_pipe_queue_init() {
if ((fd == -1) && (errno != ENXIO)) {
char errorstring[1024];
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
- debug(1, "metadata_init -- error %d (\"%s\") opening pipe: \"%s\".", errno,
- (char *)errorstring, path);
+ debug(1, "metadata_init -- error %d (\"%s\") opening pipe: \"%s\".", errno, (char *)errorstring,
+ path);
warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno,
(char *)errorstring, path);
}
@@ -179,8 +179,7 @@ void metadata_pipe_queue_stop() {
}
}
-int send_metadata_to_pipe_queue(const uint32_t type, const uint32_t code,
- const char *data, const uint32_t length, rtsp_message *carrier,
- int block) {
- return send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block);
+int send_metadata_to_pipe_queue(const uint32_t type, const uint32_t code, const char *data,
+ const uint32_t length, rtsp_message *carrier, int block) {
+ return send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block);
}
diff --git a/metadata/pipe.h b/metadata/pipe.h
index 2a242983..f219fece 100644
--- a/metadata/pipe.h
+++ b/metadata/pipe.h
@@ -1,10 +1,9 @@
#pragma once
-#include
#include "rtsp.h"
+#include
void metadata_pipe_queue_init();
void metadata_pipe_queue_stop();
-int send_metadata_to_pipe_queue(const uint32_t type, const uint32_t code,
- const char *data, const uint32_t length, rtsp_message *carrier,
- int block);
\ No newline at end of file
+int send_metadata_to_pipe_queue(const uint32_t type, const uint32_t code, const char *data,
+ const uint32_t length, rtsp_message *carrier, int block);
\ No newline at end of file
diff --git a/mqtt.c b/mqtt.c
index 9035986b..3acb8865 100644
--- a/mqtt.c
+++ b/mqtt.c
@@ -8,14 +8,16 @@
#include "common.h"
#include "player.h"
#include "rtsp.h"
+#include "utilities/string_utilities.h"
#include "rtp.h"
#ifdef CONFIG_DACP_CLIENT
#include "dacp.h"
#endif
-#include "metadata/hub.h"
+
#include "metadata/core.h"
+#include "metadata/hub.h"
#include "metadata/pc_queue.h"
#include "mqtt.h"
@@ -79,7 +81,7 @@ void on_message(__attribute__((unused)) struct mosquitto *mosq,
debug(2, "[MQTT]: Received Recognized Command: %s\n", commands[it]);
if (strcmp(commands[it], "disconnect") == 0) {
debug(2, "[MQTT]: Disconnect Command: %s\n", commands[it]);
- release_play_lock(NULL); // stop any current session and don't replace it
+ stop_play(); // stop any current session and don't replace it
} else {
debug(2, "[MQTT]: DACP Command: %s\n", commands[it]);
#ifdef CONFIG_DACP_CLIENT
@@ -211,10 +213,10 @@ void send_autodiscovery_messages(struct mosquitto *mosq) {
(strcmp(sensors[i], "active") == 0 || strcmp(sensors[i], "playing") == 0);
bool is_volume_sensor = strcmp(sensors[i], "volume") == 0;
- const char* entity_type = is_binary_sensor ? "binary_sensor" : "sensor";
+ const char *entity_type = is_binary_sensor ? "binary_sensor" : "sensor";
- snprintf(topic, sizeof(topic), "%s/%s/%s_%s/%s/config", autodiscovery_prefix,
- entity_type, model, device_id_no_colons, sensors[i]);
+ snprintf(topic, sizeof(topic), "%s/%s/%s_%s/%s/config", autodiscovery_prefix, entity_type,
+ model, device_id_no_colons, sensors[i]);
snprintf(id_string, sizeof(id_string), "%s_%s_%s", model, device_name, sensors[i]);
@@ -232,12 +234,12 @@ void send_autodiscovery_messages(struct mosquitto *mosq) {
"\"default_entity_id\": \"%s.%s\","
"%s%s%s"
"}",
- sensor_names[i], // name
- config.mqtt_topic, sensors[i], //state_topic
- icons[i], // icon
- id_string, // unique_id
- id_string, // object_id
- entity_type, id_string, // default_entity_id
+ sensor_names[i], // name
+ config.mqtt_topic, sensors[i], // state_topic
+ icons[i], // icon
+ id_string, // unique_id
+ id_string, // object_id
+ entity_type, id_string, // default_entity_id
is_binary_sensor ? "\"payload_on\": \"1\",\"payload_off\": \"0\"," : "",
is_volume_sensor
? "\"value_template\": \"{{ ((value | regex_findall_index("
@@ -268,8 +270,7 @@ void mqtt_publish(char *topic, char *data_in, uint32_t length_in) {
int rc;
if ((rc = mosquitto_publish(global_mosq, NULL, fulltopic, length, data, 0,
- config.mqtt_publish_retain)) !=
- MOSQ_ERR_SUCCESS) {
+ config.mqtt_publish_retain)) != MOSQ_ERR_SUCCESS) {
switch (rc) {
case MOSQ_ERR_NO_CONN:
debug(1, "[MQTT]: Publish failed: not connected to broker");
@@ -503,15 +504,13 @@ void metadata_mqtt_queue_init() {
debug(1, "Failed to create metadata mqtt thread!");
}
void metadata_mqtt_queue_stop() {
- // debug(2, "metadata stop mqtt thread.");
- pthread_cancel(metadata_mqtt_thread);
- pthread_join(metadata_mqtt_thread, NULL);
- pc_queue_delete(&metadata_mqtt_queue);
- // debug(2, "metadata stop mqtt done.");
+ // debug(2, "metadata stop mqtt thread.");
+ pthread_cancel(metadata_mqtt_thread);
+ pthread_join(metadata_mqtt_thread, NULL);
+ pc_queue_delete(&metadata_mqtt_queue);
+ // debug(2, "metadata stop mqtt done.");
}
-int send_metadata_to_mqtt_queue(const uint32_t type, const uint32_t code,
- const char *data, const uint32_t length, rtsp_message *carrier,
- int block) {
- return send_metadata_to_queue(&metadata_mqtt_queue, type, code, data, length, carrier, block);
+int send_metadata_to_mqtt_queue(const uint32_t type, const uint32_t code, const char *data,
+ const uint32_t length, rtsp_message *carrier, int block) {
+ return send_metadata_to_queue(&metadata_mqtt_queue, type, code, data, length, carrier, block);
}
-
diff --git a/mqtt.h b/mqtt.h
index f450b416..88fc376b 100644
--- a/mqtt.h
+++ b/mqtt.h
@@ -15,7 +15,6 @@ void _cb_log(struct mosquitto *mosq, void *userdata, int level, const char *str)
void metadata_mqtt_queue_init();
void metadata_mqtt_queue_stop();
-int send_metadata_to_mqtt_queue(const uint32_t type, const uint32_t code,
- const char *data, const uint32_t length, rtsp_message *carrier,
- int block);
+int send_metadata_to_mqtt_queue(const uint32_t type, const uint32_t code, const char *data,
+ const uint32_t length, rtsp_message *carrier, int block);
#endif /* #ifndef MQTT_H */
diff --git a/player.c b/player.c
index b4e505ce..4b149faa 100644
--- a/player.c
+++ b/player.c
@@ -657,17 +657,17 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
// or by using the setting that has been given.
// Upmixing will not be done if the setting is "auto".
-
+
// Similarly, on the "auto" setting, downmixing will be done
// only if the number of output channels available
// is strictly less than the number of input channels.
-
+
// If the number of output channels equals the number of input channels,
// no mixing is done on the "auto" setting.
// To do an upmix or a custom downmix, specify
// the target format, e.g. "7.1" in the mixdown setting.
-
+
// NOTE: upmixing, by default, simply copies the input channels to their
// equivalents in the output channels. All other channels are
// left silent.
@@ -683,9 +683,9 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
AVChannelLayout output_channel_layout;
if (config.mixdown_enable != 0) {
if (config.mixdown_channel_layout == 0) {
- if (CHANNELS_FROM_ENCODED_FORMAT(output_configuration) < (unsigned) input_channel_count) {
+ if (CHANNELS_FROM_ENCODED_FORMAT(output_configuration) < (unsigned)input_channel_count) {
av_channel_layout_default(&output_channel_layout,
- CHANNELS_FROM_ENCODED_FORMAT(output_configuration));
+ CHANNELS_FROM_ENCODED_FORMAT(output_configuration));
} else {
av_channel_layout_from_mask(&output_channel_layout, input_layout);
}
@@ -705,7 +705,8 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
av_opt_set_int(swr, "in_channel_layout", input_layout, 0);
if (config.mixdown_enable != 0) {
if (config.mixdown_channel_layout == 0) {
- if (CHANNELS_FROM_ENCODED_FORMAT(output_configuration) < av_get_channel_layout_nb_channels(input_layout)) {
+ if ((signed)CHANNELS_FROM_ENCODED_FORMAT(output_configuration) <
+ (av_get_channel_layout_nb_channels(input_layout)) {
output_layout =
av_get_default_channel_layout(CHANNELS_FROM_ENCODED_FORMAT(output_configuration));
} // else leave output_layout as it was: the sames as the input_layout.
@@ -778,7 +779,7 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
// get information about the output from the resampler
int64_t resampler_output_format = 0;
int resampler_channels_found = 0;
-
+
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
@@ -891,7 +892,7 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
free(device_channels);
}
}
-
+
pthread_setcancelstate(oldState, NULL);
if (output_channel_map_faulty != 0) {
@@ -1035,17 +1036,26 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
return response; // 0 if everything is okay
}
void prepare_decoding_chain(rtsp_conn_info *conn, ssrc_t ssrc) {
+ if ((ssrc != SSRC_NONE) && (ssrc != conn->incoming_ssrc) && (ssrc_is_recognised(ssrc) != 0)) {
+ // conn->incoming_ssrc will be SSRC_NONE only before the first valid encoding is found
+ if ((config.statistics_requested) && (conn->incoming_ssrc != SSRC_NONE))
+ inform("Connection %d: Incoming Audio Encoding is switching to: \"%s\".",
+ conn->connection_number, get_ssrc_name(ssrc));
+ // conn->incoming_ssrc = payload_ssrc;
+#ifdef CONFIG_METADATA
+ send_ssnc_metadata('sdsc', get_ssrc_name(ssrc), strlen(get_ssrc_name(ssrc)), 1);
+#endif
+ }
+
if ((ssrc_is_recognised(ssrc)) && (ssrc != conn->incoming_ssrc)) {
- if ((config.statistics_requested != 0) && (ssrc != SSRC_NONE) &&
- (conn->incoming_ssrc != SSRC_NONE)) {
- debug(2, "Connection %d: incoming audio switching to \"%s\".", conn->connection_number,
- get_ssrc_name(ssrc));
-#ifdef CONFIG_METADATA
- send_ssnc_metadata('sdsc', get_ssrc_name(ssrc), strlen(get_ssrc_name(ssrc)), 1);
-#endif
- }
-
+ /*
+ if ((config.statistics_requested != 0) && (ssrc != SSRC_NONE) &&
+ (conn->incoming_ssrc != SSRC_NONE)) {
+ debug(2, "Connection %d: incoming audio switching to \"%s\".", conn->connection_number,
+ get_ssrc_name(ssrc));
+ }
+ */
// the ssrc of the incoming packet is different to the ssrc of the decoding chain
// so the decoding chain must be rebuilt
@@ -1071,7 +1081,8 @@ void prepare_decoding_chain(rtsp_conn_info *conn, ssrc_t ssrc) {
conn->codec = avcodec_find_decoder(AV_CODEC_ID_AAC);
break;
default:
- die("Connection %d: can't find a suitable codec for SSRC: %s", conn->connection_number, get_ssrc_name(ssrc));
+ die("Connection %d: can't find a suitable codec for SSRC: %s", conn->connection_number,
+ get_ssrc_name(ssrc));
break;
}
@@ -1080,17 +1091,18 @@ void prepare_decoding_chain(rtsp_conn_info *conn, ssrc_t ssrc) {
// Get a decoder-dependent codec context
conn->codec_context = avcodec_alloc_context3(conn->codec);
- if (conn->codec_context != NULL) {
+ if (conn->codec_context != NULL) {
// push a deallocator -- av_free(codec_context)
// pthread_cleanup_push(avcodec_alloc_context3_cleanup_handler, &conn->codec_context);
-
+
// prepare to open the codec context with that codec
// but first, if it's the ALAC decoder, prepare a magic cookie
if ((ssrc == ALAC_48000_S24_2) || (ssrc == ALAC_44100_S16_2)) {
alac_ffmpeg_magic_cookie *extradata =
malloc(sizeof(alac_ffmpeg_magic_cookie)); // might not use it
if (extradata == NULL)
- die("connection %d: could not allocate memory for a magic cookie.", conn->connection_number);
+ die("connection %d: could not allocate memory for a magic cookie.",
+ conn->connection_number);
// creata a magic cookie preceded by the 12-byte "atom" (?) expected by FFMPEG (?)
memset(extradata, 0, sizeof(alac_ffmpeg_magic_cookie));
extradata->cookie_size = htonl(sizeof(alac_ffmpeg_magic_cookie));
@@ -1115,18 +1127,19 @@ void prepare_decoding_chain(rtsp_conn_info *conn, ssrc_t ssrc) {
}
// pthread_cleanup_push(malloc_cleanup, &conn->codec_context->extradata);
// avcodec_free_context() will free extradata
-
+
if (avcodec_open2(conn->codec_context, conn->codec, NULL) < 0) {
- pthread_setcancelstate(oldState, NULL);
- die("connection %d: could not initialise the codec context", conn->connection_number);
+ pthread_setcancelstate(oldState, NULL);
+ die("connection %d: could not initialise the codec context", conn->connection_number);
}
} else {
die("connection %d: could not allocate a codec context!", conn->connection_number);
- }
+ }
pthread_setcancelstate(oldState, NULL);
conn->input_rate = get_ssrc_rate(ssrc);
- debug(2, "Connection %d: set conn->input_rate: %u.", conn->connection_number, conn->input_rate);
+ debug(2, "Connection %d: set conn->input_rate: %u.", conn->connection_number,
+ conn->input_rate);
if ((ssrc == ALAC_48000_S24_2) || (ssrc == ALAC_44100_S16_2)) {
conn->frames_per_packet = 352;
} else {
@@ -1583,28 +1596,26 @@ uint32_t player_put_packet(uint32_t ssrc, seq_t seqno, uint32_t actual_timestamp
} else if (config.decoder_in_use == 1 << decoder_ffmpeg_alac) {
#ifdef CONFIG_FFMPEG
prepare_decoding_chain(conn, ALAC_44100_S16_2);
- // if (len > 8) {
- abuf->avframe = block_to_avframe(conn, data_to_use, len);
- abuf->ssrc = ALAC_44100_S16_2;
- if (abuf->avframe) {
- input_packets_used = abuf->avframe->nb_samples;
- }
- if (mute) {
- // it's important to have already run it through the decoder before dropping it
- // especially if it an AAC decoder
- debug(2, "ap1 muting frame %u.", actual_timestamp);
- abuf->length = abuf->avframe->nb_samples;
- av_frame_free(&abuf->avframe);
- abuf->avframe = NULL;
- }
- // } else {
- if (len <= 8) {
- debug(1,
- "Using the FFMPEG ALAC_44100_S16_2 decoder, a short audio packet %u, rtptime %u, of length %zu has been decoded but not discarded. Contents follow:", seqno,
- actual_timestamp, len);
- debug_print_buffer(1, data, len);
- // abuf->length = conn->frames_per_packet;
- // abuf->avframe = NULL;
+ abuf->avframe = block_to_avframe(conn, data_to_use, len);
+ abuf->ssrc = ALAC_44100_S16_2;
+ if (abuf->avframe) {
+ input_packets_used = abuf->avframe->nb_samples;
+ }
+ if (mute) {
+ // it's important to have already run it through the decoder before dropping it
+ // especially if it an AAC decoder
+ debug(2, "ap1 muting frame %u.", actual_timestamp);
+ abuf->length = abuf->avframe->nb_samples;
+ av_frame_free(&abuf->avframe);
+ abuf->avframe = NULL;
+ }
+ if (len <= 8) {
+ debug(2,
+ "Connection %d, using FFMPEG on an ALAC_44100_S16_2 stream, a short audio packet "
+ "%u, rtptime %u, of length %zu has been decoded but not discarded. Contents "
+ "follow:",
+ conn->connection_number, seqno, actual_timestamp, len);
+ debug_print_buffer(2, data, len);
}
#else
debug(1, "FFMPEG support has not been built into this version Shairport Sync!");
@@ -1636,27 +1647,25 @@ uint32_t player_put_packet(uint32_t ssrc, seq_t seqno, uint32_t actual_timestamp
prepare_decoding_chain(conn, ssrc); // dynamically set the decoding environment
- // if (len > 8) {
- abuf->avframe = block_to_avframe(conn, data, len);
- abuf->ssrc = ssrc; // tag the avframe with its specific SSRC
- if (abuf->avframe) {
- input_packets_used = abuf->avframe->nb_samples;
- }
- if (mute) {
- // it's important to have already run it through the decoder before dropping it
- debug(2, "ap2 muting frame %u.", actual_timestamp);
- abuf->length = abuf->avframe->nb_samples;
- av_frame_free(&abuf->avframe);
- abuf->avframe = NULL;
- }
- //} else {
+ abuf->avframe = block_to_avframe(conn, data, len);
+ abuf->ssrc = ssrc; // tag the avframe with its specific SSRC
+ if (abuf->avframe) {
+ input_packets_used = abuf->avframe->nb_samples;
+ }
+ if (mute) {
+ // it's important to have already run it through the decoder before dropping it
+ debug(2, "ap2 muting frame %u.", actual_timestamp);
+ abuf->length = abuf->avframe->nb_samples;
+ av_frame_free(&abuf->avframe);
+ abuf->avframe = NULL;
+ }
+
if (len <= 8) {
- debug(1,
- "Using an FFMPEG decoder, a short audio packet %u, rtptime %u, of length %zu has been decoded but not discarded. Contents follow:", seqno,
- actual_timestamp, len);
- debug_print_buffer(1, data, len);
- // abuf->length = 0;
- // abuf->avframe = NULL;
+ debug(2,
+ "Connection %d: using FFMPEG on a %s stream, a short audio packet %u, rtptime %u, "
+ "of length %zu has been decoded but not discarded. Contents follow:",
+ conn->connection_number, get_ssrc_name(ssrc), seqno, actual_timestamp, len);
+ debug_print_buffer(2, data, len);
}
abuf->ready = 1;
abuf->status = 0; // signifying that it was received
@@ -2497,12 +2506,12 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) {
RATE_FROM_ENCODED_FORMAT(config.current_output_configuration)) /
1000000000;
int64_t exact_frame_gap = gross_frame_gap - dac_delay;
-
debug(4,
- "Exact frame gap: %" PRId64
- ". DAC delay: %ld. Total: %" PRId64 ". First packet timestamp: %u. ",
- exact_frame_gap, dac_delay, gross_frame_gap, conn->first_packet_timestamp);
+ "Exact frame gap: %" PRId64 ". DAC delay: %ld. Total: %" PRId64
+ ". First packet timestamp: %u. ",
+ exact_frame_gap, dac_delay, gross_frame_gap,
+ conn->first_packet_timestamp);
// int64_t frames_needed_to_maintain_desired_buffer =
// (int64_t)(config.audio_backend_buffer_desired_length *
// config.current_output_configuration->rate) -
@@ -2720,9 +2729,11 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) {
}
if (wait) {
uint64_t time_to_wait_for_wakeup_ns = 10000000; // default
- if (conn->input_format_is_valid != 0) {
- time_to_wait_for_wakeup_ns = 1000000000 / conn->input_rate; // this is time period of one frame
- time_to_wait_for_wakeup_ns *= 4 * conn->frames_per_packet; // about 4 * 7 mS for 352 frames per second
+ if (conn->input_format_is_valid != 0) {
+ time_to_wait_for_wakeup_ns =
+ 1000000000 / conn->input_rate; // this is time period of one frame
+ time_to_wait_for_wakeup_ns *=
+ 4 * conn->frames_per_packet; // about 4 * 7 mS for 352 frames per second
}
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
@@ -2733,13 +2744,14 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) {
struct timespec time_of_wakeup;
time_of_wakeup.tv_sec = sec;
time_of_wakeup.tv_nsec = nsec;
- // debug(1, "wait for up to %f mS or for the next packet...", time_to_wait_for_wakeup_ns * 1E-6);
+ // debug(1, "wait for up to %f mS or for the next packet...", time_to_wait_for_wakeup_ns *
+ // 1E-6);
int rc = pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex,
&time_of_wakeup); // this is a pthread cancellation point
if ((rc != 0) && (rc != ETIMEDOUT))
// if (rc)
debug(3, "pthread_cond_timedwait returned error code %d.", rc);
- // debug(1, "waited");
+ // debug(1, "waited");
#endif
#ifdef COMPILE_FOR_OSX
uint64_t sec = time_to_wait_for_wakeup_ns / 1000000000;
@@ -2789,8 +2801,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) {
debug(2, "setting up software resampler for %s for the first time.",
get_ssrc_name(curframe->ssrc));
} else {
- debug(1, "Connection %d: queued audio buffers switching to \"%s\".", conn->connection_number,
- get_ssrc_name(curframe->ssrc));
+ debug(2, "Connection %d: queued audio buffers switching to \"%s\".",
+ conn->connection_number, get_ssrc_name(curframe->ssrc));
clear_software_resampler(conn);
// ask the backend if it can give us its best choice for an ffmpeg configuration:
}
@@ -3029,8 +3041,8 @@ static int stuff_buffer_vernier(int32_t *inptr, int length, sps_format_t l_outpu
debug(1,
"Can't see how this could ever happen, but "
"current_input_sample_floor_index %" PRId64
- " has just stepped outside the frame of %d samples, with stuff %d and current_input_sample_index_fp at %" PRId64 ".%05" PRId64
- ".",
+ " has just stepped outside the frame of %d samples, with stuff %d and "
+ "current_input_sample_index_fp at %" PRId64 ".%05" PRId64 ".",
current_input_sample_floor_index, length, stuff, current_input_sample_index_int,
current_input_sample_index_low);
current_input_sample_floor_index = length - 1; // hack
@@ -3052,8 +3064,8 @@ static int stuff_buffer_vernier(int32_t *inptr, int length, sps_format_t l_outpu
debug(1,
"Can't see how this could ever happen, but "
"current_input_sample_ceil_index %" PRId64
- " has just stepped outside the frame of %d samples, with stuff %d and current_input_sample_index_fp at %" PRId64
- ".%05" PRId64 ".",
+ " has just stepped outside the frame of %d samples, with stuff %d and "
+ "current_input_sample_index_fp at %" PRId64 ".%05" PRId64 ".",
current_input_sample_floor_index, length, stuff, current_input_sample_index_int,
current_input_sample_index_low);
}
@@ -3314,17 +3326,10 @@ void player_thread_cleanup_handler(void *arg) {
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
// debug(1, "Connection %d: player_thread_cleanup_handler start.", conn->connection_number);
-#ifdef CONFIG_FFMPEG
- // debug(1, "FFmpeg clearup");
- clear_software_resampler(conn);
- clear_decoding_chain(conn);
- // debug(1, "FFmpeg clearup done");
-#endif
-
if (config.output->stop) {
#ifdef CONFIG_FFMPEG
- if (avflush(conn) > 1)
- debug(3, "ffmpeg flush at stop!");
+ if ((config.decoder_in_use == 1 << decoder_ffmpeg_alac) && (avflush(conn) > 1))
+ debug(1, "ffmpeg flush at stop!");
#endif
debug(2, "Connection %d: player: stop the output backend.", conn->connection_number);
config.output->stop();
@@ -3418,6 +3423,15 @@ void player_thread_cleanup_handler(void *arg) {
ptp_send_control_message_string("E");
#endif
+#ifdef CONFIG_FFMPEG
+ if (config.decoder_in_use == 1 << decoder_ffmpeg_alac) {
+ // debug(1, "FFmpeg clearup");
+ clear_software_resampler(conn);
+ clear_decoding_chain(conn);
+ // debug(1, "FFmpeg clearup done");
+ }
+#endif
+
if (conn->outbuf) {
free(conn->outbuf);
conn->outbuf = NULL;
@@ -3512,11 +3526,17 @@ void *player_thread_func(void *arg) {
init_alac_decoder((int32_t *)&conn->stream.fmtp,
conn); // this sets up incoming rate, bit depth, channels.
// No pthread cancellation point in here
+#ifdef CONFIG_METADATA
+ send_ssnc_metadata('sdsc', "ALAC/44100/S16_LE/2", strlen("ALAC/44100/S16_LE/2"), 1);
+#endif
}
#endif
#ifdef CONFIG_APPLE_ALAC
if (config.decoder_in_use == 1 << decoder_apple_alac) {
apple_alac_init(conn->stream.fmtp); // no pthread cancellation point in here
+#ifdef CONFIG_METADATA
+ send_ssnc_metadata('sdsc', "ALAC/44100/S16_LE/2", strlen("ALAC/44100/S16_LE/2"), 1);
+#endif
}
#endif
}
@@ -4145,7 +4165,7 @@ void *player_thread_func(void *arg) {
send_ssnc_stream_description("Classic", get_ssrc_name(conn->incoming_ssrc));
#endif
if (config.statistics_requested)
- inform("Connection %d: Classic AirPlay (\"AirPlay 1\") Compatible playback. "
+ inform("Connection %d: Classic AirPlay (\"AirPlay 1\") playback. "
"Input format: %s. Output format: %s.",
conn->connection_number, get_ssrc_name(conn->incoming_ssrc),
short_description);
@@ -4369,10 +4389,14 @@ void *player_thread_func(void *arg) {
if (inframe->timestamp_gap < 0) {
gap_to_fix = -inframe->timestamp_gap; // this is frames at the input rate
int64_t gap_to_fix_ns = (gap_to_fix * 1000000000) / conn->input_rate;
- gap_to_fix = (gap_to_fix_ns *
- RATE_FROM_ENCODED_FORMAT(config.current_output_configuration) + 1000000000/2) /
+ gap_to_fix = (gap_to_fix_ns * RATE_FROM_ENCODED_FORMAT(
+ config.current_output_configuration) +
+ 1000000000 / 2) /
1000000000; // this is frames at the output rate
- debug(4, "gap_to_fix: %u frames at input rate, %" PRId64 " frames at output rate.", -inframe->timestamp_gap, gap_to_fix);
+ debug(4,
+ "gap_to_fix: %u frames at input rate, %" PRId64
+ " frames at output rate.",
+ -inframe->timestamp_gap, gap_to_fix);
// debug(3, "due to timstamp gap of %d frames, skip %" PRId64 " output
// frames.", inframe->timestamp_gap, gap_to_fix);
}
@@ -4580,7 +4604,7 @@ void *player_thread_func(void *arg) {
#endif
) {
- float(*fbufs)[1024] = malloc(conn->input_num_channels * sizeof(*fbufs));
+ float (*fbufs)[1024] = malloc(conn->input_num_channels * sizeof(*fbufs));
// debug(1, "size of array allocated is %d bytes.", conn->input_num_channels *
// sizeof(*fbufs));
int32_t *tbuf32 = conn->tbuf;
@@ -4794,15 +4818,14 @@ void *player_thread_func(void *arg) {
frames_to_skip -= play_samples;
} else {
-
-
- size_t bytes_to_skip = frames_to_skip *
+ size_t bytes_to_skip =
+ frames_to_skip *
CHANNELS_FROM_ENCODED_FORMAT(config.current_output_configuration) *
sps_format_sample_size(
FORMAT_FROM_ENCODED_FORMAT(config.current_output_configuration));
char *play_starting_point = conn->outbuf + bytes_to_skip;
-
+
config.output->play(play_starting_point, play_samples - frames_to_skip,
play_samples_are_timed, inframe->timestamp,
should_be_time);
diff --git a/player.h b/player.h
index e2626a06..05c035f6 100644
--- a/player.h
+++ b/player.h
@@ -273,7 +273,8 @@ typedef struct {
// otherwise
int software_mute_enabled; // if we don't have a real mute that we can use
int fd;
- int authorized; // set if a password is required and has been supplied or not required. Also always set in AirPlay 2 mode.
+ int authorized; // set if a password is required and has been supplied or not required. Also
+ // always set in AirPlay 2 mode.
char *auth_nonce; // the session nonce, if needed
stream_cfg stream;
SOCKADDR remote, local;
diff --git a/ptp-utilities.c b/ptp-utilities.c
index 696c3ec6..35dce400 100644
--- a/ptp-utilities.c
+++ b/ptp-utilities.c
@@ -42,6 +42,7 @@
#define __STDC_FORMAT_MACROS
#include "common.h"
#include "ptp-utilities.h"
+#include "utilities/network_utilities.h"
#include
#include
@@ -253,7 +254,7 @@ void ptp_send_control_message_string(const char *msg) {
die("error sending timing_peer_list to NQPTP");
}
/* Deallocate the socket */
- close(s);
+ safe_socket_close(&s);
freeaddrinfo(info);
/* deallocate the message string */
diff --git a/rtp.c b/rtp.c
index 2c8f9915..f00f41db 100644
--- a/rtp.c
+++ b/rtp.c
@@ -29,6 +29,7 @@
#include "common.h"
#include "player.h"
#include "rtsp.h"
+#include "utilities/network_utilities.h"
#include
#include
#include
@@ -311,11 +312,6 @@ void *rtp_audio_receiver(void *arg) {
}
}
- /*
- debug(3, "Audio receiver -- Server RTP thread interrupted. terminating.");
- close(conn->audio_socket);
- */
-
debug(1, "Audio receiver thread \"normal\" exit -- this can't happen. Hah!");
pthread_cleanup_pop(0); // don't execute anything here.
debug(2, "Audio receiver thread exit.");
@@ -981,7 +977,8 @@ void *rtp_timing_receiver(void *arg) {
return_time);
}
} else {
- debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %zd.", packet[1], nread);
+ debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %zd.", packet[1],
+ nread);
}
} else {
debug(3, "Timing Receiver Thread -- dropping incoming packet to simulate a bad network.");
@@ -1387,9 +1384,11 @@ int get_ptp_anchor_local_time_info(rtsp_conn_info *conn, uint32_t *anchorRTP,
if ((previous_offset != 0) && (previous_clock_id == actual_clock_id) &&
((jitter > 3000000) || (jitter < -3000000)))
debug(1,
- "Clock jitter: %.3f mS. Time since sample: %.3f mS. Time since start of mastership: %.3f "
+ "Clock jitter: %.3f mS. Time since sample: %.3f mS. Time since start of "
+ "mastership: %.3f "
"seconds.",
- jitter * 0.000001, time_since_sample * 0.000001, time_since_start_of_mastership * 0.000000001);
+ jitter * 0.000001, time_since_sample * 0.000001,
+ time_since_start_of_mastership * 0.000000001);
previous_offset = actual_offset;
previous_clock_id = actual_clock_id;
@@ -1514,7 +1513,8 @@ int frame_to_ptp_local_time(uint32_t timestamp, uint64_t *time, rtsp_conn_info *
int64_t time_difference = frame_difference;
time_difference = time_difference * 1000000000;
if (conn->input_rate == 0) {
- debug(1,"Connection %d: in a call to frame_to_ptp_local_time, conn->input_rate is zero!", conn->connection_number);
+ debug(1, "Connection %d: in a call to frame_to_ptp_local_time, conn->input_rate is zero!",
+ conn->connection_number);
time_difference = 0;
} else {
time_difference = time_difference / conn->input_rate;
@@ -1550,10 +1550,9 @@ int local_ptp_time_to_frame(uint64_t time, uint32_t *frame, rtsp_conn_info *conn
void rtp_ap2_control_handler_cleanup_handler(void *arg) {
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
debug(2, "Connection %d: AP2 Control Receiver Cleanup.", conn->connection_number);
- close(conn->ap2_control_socket);
+ safe_socket_close(&conn->ap2_control_socket);
debug(2, "Connection %d: UDP control port %u closed.", conn->connection_number,
conn->local_ap2_control_port);
- conn->ap2_control_socket = 0;
conn->ap2_remote_control_socket_addr_length =
0; // indicates to the control receiver thread that the socket address need to be
// recreated (needed for resend requests in the realtime mode)
@@ -1806,9 +1805,9 @@ void *rtp_ap2_control_receiver(void *arg) {
void rtp_realtime_audio_cleanup_handler(__attribute__((unused)) void *arg) {
debug(2, "Realtime Audio Receiver Cleanup Start.");
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
- close(conn->realtime_audio_socket);
- debug(2, "Connection %d: closing realtime audio port %u", conn->connection_number, conn->local_realtime_audio_port);
- conn->realtime_audio_socket = 0;
+ debug(2, "Connection %d: closing realtime audio port %u", conn->connection_number,
+ conn->local_realtime_audio_port);
+ safe_socket_close(&conn->realtime_audio_socket);
debug(2, "Realtime Audio Receiver Cleanup Done.");
}
diff --git a/rtsp.c b/rtsp.c
index 7bc90389..e551974a 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -53,6 +53,7 @@
#include "activity_monitor.h"
#include "config.h"
+#include "utilities/network_utilities.h"
#include "utilities/rtsp_message_utilities.h"
#ifdef CONFIG_OPENSSL
@@ -76,8 +77,8 @@
#include "rtsp.h"
#ifdef CONFIG_METADATA
-#include "metadata/pc_queue.h"
#include "metadata/core.h"
+#include "metadata/pc_queue.h"
#endif
#ifdef CONFIG_METADATA_HUB
@@ -91,12 +92,12 @@
#endif
#ifdef CONFIG_AIRPLAY_2
-#include
#include "ap2_buffered_audio_processor.h"
#include "ap2_event_receiver.h"
#include "pair_ap/pair.h"
#include "plists/get_info_response.h"
#include "ptp-utilities.h"
+#include
#ifdef HAVE_LIBPLIST_GE_2_3_0
#define plist_from_memory(plist_data, length, plist) \
@@ -125,8 +126,8 @@ enum rtsp_read_request_response {
rtsp_read_request_response_error
};
-rtsp_conn_info *principal_conn;
-rtsp_conn_info **conns;
+rtsp_conn_info *principal_conn = NULL;
+rtsp_conn_info **conns = NULL;
// always lock this when accessing the principal conn value
// use a read lock when consulting and holding it
@@ -144,132 +145,6 @@ pthread_mutex_t conns_lock = PTHREAD_MUTEX_INITIALIZER;
int RTSP_connection_index = 1;
-// The principal_conn variable points to the connection that
-// controls the mDNS status and flags and that is potentially
-// in control of the playing subsystem to output audio to a backend
-// the principal_conn variable may be NULL
-
-// the principal_conn is set by an ANNOUNCE message (Classic AirPlay) or
-// by the initial SETUP (of a connection, not of a play session) message (AirPlay 2) and cleared
-// when a session is terminated (AirPlay 2)
-
-// In AirPlay 2, only one PTP connection can be live at any time, and it is the principal_conn.
-// This is because, in AirPlay 2, the principal_conn connection
-// also has control of the mDNS interface, and thus determines the state of the player as seen by
-// other devices.
-
-void release_play_lock(rtsp_conn_info *conn) {
- // no need thread cancellation points in here
- pthread_rwlock_wrlock(&principal_conn_lock);
- if ((principal_conn == conn) || (conn == NULL)) { // if we have the player
- if (principal_conn != NULL) {
-#ifdef CONFIG_AIRPLAY_2
- config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay
- if (principal_conn->airplay_gid) {
- free(principal_conn->airplay_gid);
- principal_conn->airplay_gid = NULL; // stop using the client's GID as our GID.
- }
- build_bonjour_strings(principal_conn);
- mdns_update(NULL, secondary_txt_records);
-#endif
- debug(2, "Connection %d: %s released principal_conn.", conn->connection_number,
- get_category_string(conn->airplay_stream_category));
- }
- principal_conn = NULL; // let it go
- }
- pthread_rwlock_unlock(&principal_conn_lock);
-}
-
-// stop the current principal_conn from playing if necessary and make conn the principal_conn.
-
-play_lock_r get_play_lock(rtsp_conn_info *conn, int allow_session_interruption) {
- play_lock_r response = play_lock_aquisition_failed;
- if (conn != NULL) {
- debug(2, "Connection %d: %s get_play_lock with allow_session_interruption of %d.",
- conn->connection_number, get_category_string(conn->airplay_stream_category),
- allow_session_interruption);
-
- pthread_rwlock_wrlock(&principal_conn_lock);
- pthread_cleanup_push(rwlock_unlock, (void *)&principal_conn_lock);
-
- if (principal_conn == conn) {
- debug(2, "Connection %d: %s already has principal_conn.", principal_conn->connection_number,
- get_category_string(conn->airplay_stream_category));
- } else {
- if (principal_conn != NULL)
- debug(2, "Connection %d: %s is requested to relinquish principal_conn.",
- principal_conn->connection_number,
- get_category_string(conn->airplay_stream_category));
- if (conn != NULL)
- debug(2, "Connection %d: %s request to acquire principal_conn.", conn->connection_number,
- get_category_string(conn->airplay_stream_category));
- }
-
- if (principal_conn == conn) {
- if (conn == NULL)
- response = play_lock_already_released;
- else
- response = play_lock_already_acquired;
- } else if (principal_conn == NULL) {
- // already unlocked, and principal conn not NULL
- principal_conn = conn;
-#ifdef CONFIG_AIRPLAY_2
- config.airplay_statusflags |= (1 << 11); // DeviceSupportsRelay
-#endif
- response = play_lock_acquired_without_breaking_in;
- } else if (allow_session_interruption != 0) { // principal conn not NULL,
- // important -- demote the principal conn before cancelling it
- if (principal_conn->fd > 0) {
- debug(2,
- "Connection %d: %s is acquiring play_lock and is forcing termination of Connection "
- "%d %s. Closing "
- "RTSP connection socket %d: "
- "from %s:%u to self at "
- "%s:%u.",
- conn->connection_number, get_category_string(conn->airplay_stream_category),
- principal_conn->connection_number,
- get_category_string(principal_conn->airplay_stream_category), principal_conn->fd,
- principal_conn->client_ip_string, principal_conn->client_rtsp_port,
- principal_conn->self_ip_string, principal_conn->self_rtsp_port);
- close(principal_conn->fd);
- principal_conn->fd = 0;
- usleep(1000000);
- }
- rtsp_conn_info *previous_principal_conn = principal_conn;
- principal_conn = conn; // make the conn the new principal_conn
- pthread_cancel(previous_principal_conn->thread); // cancel the previous one...
- usleep(1000000);
- if (principal_conn == NULL) {
-#ifdef CONFIG_AIRPLAY_2
- config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay
- if (conn->airplay_gid) {
- free(conn->airplay_gid);
- conn->airplay_gid = NULL; // stop using the client's GID as our GID.
- }
- build_bonjour_strings(conn);
- mdns_update(NULL, secondary_txt_records);
-#endif
- response = play_lock_released;
- } else {
-#ifdef CONFIG_AIRPLAY_2
- config.airplay_statusflags |= (1 << 11); // DeviceSupportsRelay
-#endif
- response = play_lock_acquired_by_breaking_in;
- }
- // usleep(1000000); // don't know why this delay is needed.
- }
- if ((principal_conn != NULL) && (response != play_lock_already_acquired))
- debug(2, "Connection %d: %s has principal_conn.", conn->connection_number,
- get_category_string(conn->airplay_stream_category));
- pthread_cleanup_pop(1); // release the principal_conn lock
-
- } else {
- debug(1, "Connection %d: %s get_play_lock must have a non-NULL conn.", conn->connection_number,
- get_category_string(conn->airplay_stream_category));
- }
- return response;
-}
-
// keep track of the threads we have spawned so we can join() them
static int nconns = 0;
static void track_thread(rtsp_conn_info *conn) {
@@ -369,6 +244,191 @@ void cleanup_threads(void) {
debug(4, "Airplay Volume for new connections is %.6f.", suggested_volume(NULL));
}
+int terminate_conn(int connection_number) {
+ // this will look for a connection by number, cancel it, join it and delete it.
+ int found = 0;
+ debug_mutex_lock(&conns_lock, 1000000, 4);
+ // look for an empty slot first
+ int i = 0;
+ while ((i < nconns) && (found == 0)) {
+ if ((conns[i] != NULL) && (conns[i]->connection_number == connection_number)) {
+ pthread_cancel(conns[i]->thread);
+ pthread_join(conns[i]->thread, NULL);
+ conns[i] = NULL;
+ found = 1;
+ } else
+ i++;
+ }
+ debug_mutex_unlock(&conns_lock, 4);
+ return found;
+}
+
+// The principal_conn variable points to the connection that
+// controls the mDNS status and flags and that is potentially
+// in control of the playing subsystem to output audio to a backend
+// the principal_conn variable may be NULL
+
+// the principal_conn is set by an ANNOUNCE message (Classic AirPlay) or
+// by the initial SETUP (of a connection, not of a play session) message (AirPlay 2) and cleared
+// when a session is terminated (AirPlay 2)
+
+// In AirPlay 2, only one PTP connection can be live at any time, and it is the principal_conn.
+// This is because, in AirPlay 2, the principal_conn connection
+// also has control of the mDNS interface, and thus determines the state of the player as seen by
+// other devices.
+
+// If a conn has play lock, kill the connection completely.
+
+void stop_play() {
+ pthread_rwlock_wrlock(&principal_conn_lock);
+ if (principal_conn != NULL) {
+ debug(1, "Connection %d: stop and close this connection.", principal_conn->connection_number);
+ terminate_conn(principal_conn->connection_number);
+#ifdef CONFIG_AIRPLAY_2
+ config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay
+ if (principal_conn->airplay_gid) {
+ free(principal_conn->airplay_gid);
+ principal_conn->airplay_gid = NULL; // stop using the client's GID as our GID.
+ }
+ build_bonjour_strings(principal_conn);
+ if (config.service_type == APST_airplay2) {
+ mdns_update(NULL, secondary_txt_records);
+ }
+#endif
+ principal_conn = NULL; // let it go
+ debug(1, "Connection successfully closed.");
+ }
+ pthread_rwlock_unlock(&principal_conn_lock);
+}
+
+void release_play_lock(rtsp_conn_info *conn) {
+ // no need thread cancellation points in here
+ pthread_rwlock_wrlock(&principal_conn_lock);
+ if ((principal_conn == conn) || (conn == NULL)) { // if we have the player
+ if (principal_conn != NULL) {
+#ifdef CONFIG_AIRPLAY_2
+ config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay
+ if (principal_conn->airplay_gid) {
+ free(principal_conn->airplay_gid);
+ principal_conn->airplay_gid = NULL; // stop using the client's GID as our GID.
+ }
+ build_bonjour_strings(principal_conn);
+ if (config.service_type == APST_airplay2) {
+ mdns_update(NULL, secondary_txt_records);
+ }
+#endif
+ debug(2, "Connection %d: %s released principal_conn.", conn->connection_number,
+ get_category_string(conn->airplay_stream_category));
+ }
+ principal_conn = NULL; // let it go
+ }
+ pthread_rwlock_unlock(&principal_conn_lock);
+}
+
+// stop the current principal_conn from playing if necessary and make conn the principal_conn.
+
+// result of trying to acquire or release the play lock
+typedef enum {
+ play_lock_released,
+ play_lock_already_released,
+ play_lock_already_acquired,
+ play_lock_acquired_without_breaking_in,
+ play_lock_acquired_by_breaking_in,
+ play_lock_aquisition_failed
+} play_lock_r;
+
+play_lock_r get_play_lock(rtsp_conn_info *conn, int allow_session_interruption) {
+ play_lock_r response = play_lock_aquisition_failed;
+ if (conn != NULL) {
+ debug(2, "Connection %d: %s get_play_lock with allow_session_interruption of %d.",
+ conn->connection_number, get_category_string(conn->airplay_stream_category),
+ allow_session_interruption);
+
+ pthread_rwlock_wrlock(&principal_conn_lock);
+ pthread_cleanup_push(rwlock_unlock, (void *)&principal_conn_lock);
+
+ if (principal_conn == conn) {
+ debug(2, "Connection %d: %s already has principal_conn.", principal_conn->connection_number,
+ get_category_string(conn->airplay_stream_category));
+ } else {
+ if (principal_conn != NULL)
+ debug(2, "Connection %d: %s is requested to relinquish principal_conn.",
+ principal_conn->connection_number,
+ get_category_string(conn->airplay_stream_category));
+ if (conn != NULL)
+ debug(2, "Connection %d: %s request to acquire principal_conn.", conn->connection_number,
+ get_category_string(conn->airplay_stream_category));
+ }
+
+ if (principal_conn == conn) {
+ if (conn == NULL)
+ response = play_lock_already_released;
+ else
+ response = play_lock_already_acquired;
+ } else if (principal_conn == NULL) {
+ // already unlocked, and principal conn not NULL
+ principal_conn = conn;
+#ifdef CONFIG_AIRPLAY_2
+ config.airplay_statusflags |= (1 << 11); // DeviceSupportsRelay
+#endif
+ response = play_lock_acquired_without_breaking_in;
+ } else if (allow_session_interruption != 0) { // principal conn not NULL,
+ // important -- demote the principal conn before cancelling it
+ /*
+ if (principal_conn->fd > 0) {
+ debug(2,
+ "Connection %d: %s is acquiring play_lock and is forcing termination of Connection "
+ "%d %s. Closing "
+ "RTSP connection socket %d: "
+ "from %s:%u to self at "
+ "%s:%u.",
+ conn->connection_number, get_category_string(conn->airplay_stream_category),
+ principal_conn->connection_number,
+ get_category_string(principal_conn->airplay_stream_category), principal_conn->fd,
+ principal_conn->client_ip_string, principal_conn->client_rtsp_port,
+ principal_conn->self_ip_string, principal_conn->self_rtsp_port);
+ safe_socket_close(&principal_conn->fd);
+ usleep(1000000);
+ }
+ */
+ debug(4, "Connection %d: about to be terminated.", principal_conn->connection_number);
+ rtsp_conn_info *previous_principal_conn = principal_conn;
+ principal_conn = conn; // make the conn the new principal_conn
+ terminate_conn(previous_principal_conn->connection_number);
+ debug(4, "Connection successfully terminated.");
+ if (principal_conn == NULL) {
+#ifdef CONFIG_AIRPLAY_2
+ config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay
+ if (conn->airplay_gid) {
+ free(conn->airplay_gid);
+ conn->airplay_gid = NULL; // stop using the client's GID as our GID.
+ }
+ build_bonjour_strings(conn);
+ if (config.service_type == APST_airplay2) {
+ mdns_update(NULL, secondary_txt_records);
+ }
+#endif
+ response = play_lock_released;
+ } else {
+#ifdef CONFIG_AIRPLAY_2
+ config.airplay_statusflags |= (1 << 11); // DeviceSupportsRelay
+#endif
+ response = play_lock_acquired_by_breaking_in;
+ }
+ // usleep(1000000); // don't know why this delay is needed.
+ }
+ if ((principal_conn != NULL) && (response != play_lock_already_acquired))
+ debug(2, "Connection %d: %s has principal_conn.", conn->connection_number,
+ get_category_string(conn->airplay_stream_category));
+ pthread_cleanup_pop(1); // release the principal_conn lock
+
+ } else {
+ debug(1, "Connection %d: %s get_play_lock must have a non-NULL conn.", conn->connection_number,
+ get_category_string(conn->airplay_stream_category));
+ }
+ return response;
+}
+
// park a null at the line ending, and return the next line pointer
// accept \r, \n, or \r\n
static char *nextline(char *in, int inbuf) {
@@ -578,8 +638,7 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
debug(2, "Connection %d RTSP port closed by client with error %d: \"%s\".",
conn->connection_number, errno, (char *)errorstring);
}
- close(conn->fd); // close it from our end too...
- conn->fd = 0;
+ safe_socket_close(&conn->fd); // close it from our end too...
reply = rtsp_read_request_response_channel_closed;
} else {
char errorstring[1024];
@@ -952,20 +1011,18 @@ void generateTxtDataValueInfo(rtsp_conn_info *conn, void **response, size_t *res
&qualifier_response_data, &qualifier_response_data_length) == 0)
debug(1, "Problem");
-/*
- uint64_t features_hi = 0x0001C340445D0A00L;
- features_hi = (features_hi >> 32) & 0xffffffff;
- uint64_t features_lo = 0x0001C340445D0A00L;
- features_lo = features_lo & 0xffffffff;
-*/
-
+ /*
+ uint64_t features_hi = 0x0001C340445D0A00L;
+ features_hi = (features_hi >> 32) & 0xffffffff;
+ uint64_t features_lo = 0x0001C340445D0A00L;
+ features_lo = features_lo & 0xffffffff;
+ */
uint64_t features_hi = config.airplay_features;
features_hi = (features_hi >> 32) & 0xffffffff;
uint64_t features_lo = config.airplay_features;
features_lo = features_lo & 0xffffffff;
-
if (add_pstring_to_malloc(bnprintf(localString, sizeof(localString),
"features=0x%" PRIX64 ",0x%" PRIX64 "", features_lo,
features_hi),
@@ -1292,7 +1349,8 @@ void handle_flushbuffered(rtsp_conn_info *conn, rtsp_message *req, rtsp_message
conn->ap2_immediate_flush_until_sequence_number = flushUntilSeq & 0x7fffff;
conn->ap2_immediate_flush_until_rtp_timestamp = flushUntilTS;
debug(2,
- "Connection %d: immediate flush request created: flushUntilTS: %" PRIu64 ", flushUntilSeq: %" PRIu64 ".",
+ "Connection %d: immediate flush request created: flushUntilTS: %" PRIu64
+ ", flushUntilSeq: %" PRIu64 ".",
conn->connection_number, flushUntilTS, flushUntilSeq & 0x7fffff);
conn->ap2_play_enabled = 0; // stop trying to play audio
ptp_send_control_message_string(
@@ -1316,7 +1374,8 @@ void handle_flushbuffered(rtsp_conn_info *conn, rtsp_message *req, rtsp_message
conn->ap2_deferred_flush_requests[i].flushUntilSeq = flushUntilSeq & 0x7fffff;
conn->ap2_deferred_flush_requests[i].flushUntilTS = flushUntilTS;
debug(2,
- "Connection %d: deferred flush request created: flushFromSeq: %" PRIu64 ", flushUntilSeq: %" PRIu64 ".",
+ "Connection %d: deferred flush request created: flushFromSeq: %" PRIu64
+ ", flushUntilSeq: %" PRIu64 ".",
conn->connection_number, flushFromSeq, flushUntilSeq);
} else {
debug(1, "Connection %d: no more room for deferred flush request records",
@@ -1473,7 +1532,7 @@ struct pairings {
uint8_t public_key[32];
struct pairings *next;
-} * pairings;
+} *pairings;
static struct pairings *pairing_find(const char *device_id) {
for (struct pairings *pairing = pairings; pairing; pairing = pairing->next) {
@@ -1546,14 +1605,15 @@ static void pairing_list_cb(pair_cb enum_cb, void *enum_cb_arg,
void handle_pair_add(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req,
rtsp_message *resp) {
-
+
char *hdr = msg_get_header(req, "X-Apple-Client-Name");
if (hdr) {
if (conn->ap2_client_name)
free(conn->ap2_client_name);
conn->ap2_client_name = strdup(hdr);
- }
- debug(1, "Connection %d from \"%s\": handle_pair_add", conn->connection_number, conn->ap2_client_name);
+ }
+ debug(1, "Connection %d from \"%s\": handle_pair_add", conn->connection_number,
+ conn->ap2_client_name);
debug_log_rtsp_message_conn(conn, 1, "pair-add request", req);
uint8_t *body = NULL;
size_t body_len = 0;
@@ -1577,8 +1637,9 @@ void handle_pair_list(rtsp_conn_info *conn __attribute__((unused)), rtsp_message
if (conn->ap2_client_name)
free(conn->ap2_client_name);
conn->ap2_client_name = strdup(hdr);
- }
- debug(1, "Connection %d from \"%s\": handle_pair_list", conn->connection_number, conn->ap2_client_name);
+ }
+ debug(1, "Connection %d from \"%s\": handle_pair_list", conn->connection_number,
+ conn->ap2_client_name);
uint8_t *body = NULL;
size_t body_len = 0;
int ret = pair_list(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_list_cb, NULL,
@@ -1596,14 +1657,15 @@ void handle_pair_list(rtsp_conn_info *conn __attribute__((unused)), rtsp_message
void handle_pair_remove(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req,
rtsp_message *resp) {
-
+
char *hdr = msg_get_header(req, "X-Apple-Client-Name");
if (hdr) {
if (conn->ap2_client_name)
free(conn->ap2_client_name);
conn->ap2_client_name = strdup(hdr);
- }
- debug(1, "Connection %d from \"%s\": handle_pair_remove", conn->connection_number, conn->ap2_client_name);
+ }
+ debug(1, "Connection %d from \"%s\": handle_pair_remove", conn->connection_number,
+ conn->ap2_client_name);
uint8_t *body = NULL;
size_t body_len = 0;
int ret = pair_remove(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_remove_cb, NULL,
@@ -1625,15 +1687,16 @@ void handle_pair_verify(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *r
if (conn->ap2_client_name)
free(conn->ap2_client_name);
conn->ap2_client_name = strdup(hdr);
- }
+ }
// try to pick up the stages
uint8_t *b = (uint8_t *)req->content;
char mstage = '-';
if ((req->contentlength >= 3) && (b[0] == 6) && (b[1] == 1) && (b[1] <= 9)) {
mstage = '0' + b[2];
}
-
- debug(1, "Connection %d from \"%s\": handle_pair_verify, stage M%c, Content-Length %d", conn->connection_number, conn->ap2_client_name, mstage, req->contentlength);
+
+ debug(1, "Connection %d from \"%s\": handle_pair_verify, stage M%c, Content-Length %d",
+ conn->connection_number, conn->ap2_client_name, mstage, req->contentlength);
debug_log_rtsp_message_conn(conn, 2, "pair-verify request", req);
int ret;
uint8_t *body = NULL;
@@ -1692,9 +1755,8 @@ void handle_pair_pin_start(rtsp_conn_info *conn, rtsp_message *req, rtsp_message
free(conn->ap2_client_name);
conn->ap2_client_name = strdup(hdr);
}
- debug(4, "Connection %d from \"%s\": handle_pair_pin_start, Content-Length %d", conn->connection_number,
- conn->ap2_client_name,
- req->contentlength);
+ debug(4, "Connection %d from \"%s\": handle_pair_pin_start, Content-Length %d",
+ conn->connection_number, conn->ap2_client_name, req->contentlength);
debug_log_rtsp_message_conn(conn, 4, "handle_pair_pin_start", req);
uint8_t *body = NULL;
@@ -1715,9 +1777,8 @@ void handle_pair_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *re
free(conn->ap2_client_name);
conn->ap2_client_name = strdup(hdr);
}
- debug(2, "Connection %d from \"%s\": handle_pair_setup, Content-Length %d", conn->connection_number,
- conn->ap2_client_name,
- req->contentlength);
+ debug(2, "Connection %d from \"%s\": handle_pair_setup, Content-Length %d",
+ conn->connection_number, conn->ap2_client_name, req->contentlength);
debug_log_rtsp_message_conn(conn, 2, "pair-setup request", req);
int ret;
@@ -1728,8 +1789,8 @@ void handle_pair_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *re
debug_log_rtsp_message(3, "handle_pair_setup", req);
if (!conn->ap2_pairing_context.setup_ctx) {
- conn->ap2_pairing_context.setup_ctx = pair_setup_new(PAIR_SERVER_HOMEKIT, config.password,
- NULL, NULL, config.airplay_device_id);
+ conn->ap2_pairing_context.setup_ctx =
+ pair_setup_new(PAIR_SERVER_HOMEKIT, config.password, NULL, NULL, config.airplay_device_id);
if (!conn->ap2_pairing_context.setup_ctx) {
debug(1, "Error creating setup context");
resp->respcode = 500; // Internal Server Error
@@ -1751,16 +1812,18 @@ void handle_pair_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *re
// don't activate yet, the response to this request is still plaintext
conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx =
pair_cipher_new(PAIR_SERVER_HOMEKIT, 3, conn->pair_setup_result->shared_secret,
- conn->pair_setup_result->shared_secret_len, ""); // last argument is the (possible) dynamic salt suffix
+ conn->pair_setup_result->shared_secret_len,
+ ""); // last argument is the (possible) dynamic salt suffix
if (!conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx) {
debug(1, "Error setting up rtsp control channel ciphering");
goto out;
}
conn->ap2_pairing_context.control_cipher_bundle.description = strdup("Control Stream");
-
+
conn->ap2_pairing_context.event_cipher_bundle.cipher_ctx =
pair_cipher_new(PAIR_SERVER_HOMEKIT, 4, conn->pair_setup_result->shared_secret,
- conn->pair_setup_result->shared_secret_len, ""); // last argument is the (possible) dynamic salt suffix
+ conn->pair_setup_result->shared_secret_len,
+ ""); // last argument is the (possible) dynamic salt suffix
if (!conn->ap2_pairing_context.event_cipher_bundle.cipher_ctx) {
debug(1, "Error setting up rtsp event channel ciphering");
goto out;
@@ -1789,7 +1852,6 @@ void handle_fp_setup(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message
conn->ap2_client_name);
debug_log_rtsp_message_conn(conn, 2, "fp-setup request", req);
-
/* Fairplay magic */
static uint8_t server_fp_reply1[] =
"\x46\x50\x4c\x59\x03\x01\x02\x00\x00\x00\x00\x82\x02\x00\x0f\x9f\x3f\x9e\x0a"
@@ -1907,9 +1969,9 @@ void handle_configure(rtsp_conn_info *conn __attribute__((unused)),
rtsp_message *req __attribute__((unused)), rtsp_message *resp) {
debug_log_rtsp_message_conn(conn, 1, "POST /configure req:", req);
-
+
int existingEnable_HK_Access_Control = config.enable_HK_Access_Control;
-
+
plist_t response_plist = plist_new_dict();
// look for a configuration dictionary
@@ -1919,43 +1981,46 @@ void handle_configure(rtsp_conn_info *conn __attribute__((unused)),
plist_t configurationDict = plist_dict_get_item(messagePlist, "ConfigurationDictionary");
if (configurationDict != NULL) {
uint8_t enable_HK_Access_Control = 0;
- plist_t enableItem = plist_dict_get_item(configurationDict,"Enable_HK_Access_Control");
+ plist_t enableItem = plist_dict_get_item(configurationDict, "Enable_HK_Access_Control");
if (enableItem != NULL) {
plist_get_bool_val(enableItem, &enable_HK_Access_Control);
-
+
if (enable_HK_Access_Control != 0) {
config.enable_HK_Access_Control = 1;
plist_dict_set_item(response_plist, "Identifier", plist_new_string(config.airplay_pi));
plist_dict_set_item(response_plist, "Enable_HK_Access_Control", plist_new_bool(1));
- plist_dict_set_item(response_plist, "PublicKey",
- plist_new_data((const char *)config.airplay_pk, sizeof(config.airplay_pk)));
+ plist_dict_set_item(
+ response_plist, "PublicKey",
+ plist_new_data((const char *)config.airplay_pk, sizeof(config.airplay_pk)));
plist_dict_set_item(response_plist, "Device_Name", plist_new_string(config.service_name));
- plist_dict_set_item(response_plist, "Access_Control_Level", plist_new_uint(0));
+ plist_dict_set_item(response_plist, "Access_Control_Level", plist_new_uint(0));
} else {
- config.enable_HK_Access_Control = 0;
+ config.enable_HK_Access_Control = 0;
// leave the response dict empty
}
} else {
debug(1, "no Enable_HK_Access_Control item in POST /configure ConfigurationDictionary");
}
- debug(1, "enable_HK_Access_Control is %u.", enable_HK_Access_Control);
+ debug(1, "enable_HK_Access_Control is %u.", enable_HK_Access_Control);
} else {
- debug(1, "no ConfigurationDictionary in POST /configure plist");
+ debug(1, "no ConfigurationDictionary in POST /configure plist");
}
plist_free(messagePlist);
} else {
debug(1, "no plist in POST /configure request");
}
-
+
if (config.enable_HK_Access_Control != 0) {
config.airplay_statusflags |= (1 << 10); // DeviceWasSetupForHKAccessControl
} else {
config.airplay_statusflags &= (0xffffffff - (1 << 10));
}
-
+
if (config.enable_HK_Access_Control != existingEnable_HK_Access_Control) {
build_bonjour_strings(principal_conn);
- mdns_update(NULL, secondary_txt_records);
+ if (config.service_type == APST_airplay2) {
+ mdns_update(NULL, secondary_txt_records);
+ }
}
plist_to_bin(response_plist, &resp->content, &resp->contentlength);
plist_free(response_plist);
@@ -2024,9 +2089,10 @@ void handle_command(rtsp_conn_info *conn, rtsp_message *req,
// to incur the memory and processing cost. So we'll just send it to the
// metadata handling code and it can be dealt with there.
#ifdef CONFIG_METADATA
- send_metadata('ssnc', 'copl', req->content, req->contentlength, req, 1); // COmmand PList (and release 'req' afterwards)
+ send_metadata('ssnc', 'copl', req->content, req->contentlength, req,
+ 1); // COmmand PList (and release 'req' afterwards)
#endif
- /*
+ /*
plist_t command_dict = NULL;
plist_from_memory(req->content, req->contentlength, &command_dict);
if (command_dict != NULL) {
@@ -2062,10 +2128,8 @@ void handle_command(rtsp_conn_info *conn, rtsp_message *req,
if (subsidiary_plist) {
char *printable_plist = plist_as_xml_text(subsidiary_plist);
if (printable_plist) {
- debug(4, "Connection %d:\n==\n%s\n==", conn->connection_number, printable_plist);
- free(printable_plist);
- } else {
- debug(1, "Can't print the plist!");
+ debug(4, "Connection %d:\n==\n%s\n==", conn->connection_number,
+ printable_plist); free(printable_plist); } else { debug(1, "Can't print the plist!");
}
plist_free(subsidiary_plist);
} else {
@@ -2219,7 +2283,7 @@ void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message
#ifdef CONFIG_AIRPLAY_2
void handle_options_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req,
rtsp_message *resp) {
- debug_log_rtsp_message_conn(conn, 1, "OPTIONS request", req);
+ debug_log_rtsp_message_conn(conn, 3, "OPTIONS request", req);
debug(3, "Connection %d: OPTIONS", conn->connection_number);
resp->respcode = 200;
msg_add_header(resp, "Public",
@@ -2234,10 +2298,9 @@ void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_messag
rtsp_message *resp) {
debug(2, "Connection %d from \"%s\": TEARDOWN 2 %s.", conn->connection_number,
- conn->ap2_client_name,
- get_category_string(conn->airplay_stream_category));
+ conn->ap2_client_name, get_category_string(conn->airplay_stream_category));
debug_log_rtsp_message(2, "TEARDOWN 2: ", req);
-
+
if (conn->player_thread) {
debug(2, "TEARDOWN 2 is stopping a player thread before exiting...");
player_stop(conn); // this nulls the player_thread and cancels the threads...
@@ -2308,7 +2371,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
int err;
plist_t messagePlist = plist_from_rtsp_content(req);
-
+
if (messagePlist != NULL) {
// if (conn->sessionPlist)
// plist_free(conn->sessionPlist);
@@ -2390,11 +2453,13 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
conn->connection_number);
}
}
-
- debug(2, "Connection %d from \"%s\": Initial (i.e. no streams array) SETUP (AirPlay 2) on %s", conn->connection_number,
- conn->ap2_client_name,
- get_category_string(conn->airplay_stream_category));
- debug_log_rtsp_message_conn(conn, 2, "Initial (i.e. no streams array) SETUP (AirPlay 2) incoming message", req);
+
+ debug(2,
+ "Connection %d from \"%s\": Initial (i.e. no streams array) SETUP (AirPlay 2) on %s",
+ conn->connection_number, conn->ap2_client_name,
+ get_category_string(conn->airplay_stream_category));
+ debug_log_rtsp_message_conn(
+ conn, 2, "Initial (i.e. no streams array) SETUP (AirPlay 2) incoming message", req);
// here, we know it's an initial setup and we know the kind of setup being requested
// if it's a full service PTP stream, we get groupUUID, groupContainsGroupLeader and
@@ -2556,7 +2621,6 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
debug(2, "Connection %d: TCP PTP event port opened: %u.", conn->connection_number,
conn->local_event_port);
-
if (conn->rtp_event_thread != NULL)
debug(1, "previous rtp_event_thread allocation not freed, it seems.");
conn->ap2_event_receiver_exited = 0;
@@ -2581,7 +2645,9 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
build_bonjour_strings(conn);
debug(2, "Connection %d: SETUP mdns_update on %s.", conn->connection_number,
get_category_string(conn->airplay_stream_category));
- mdns_update(NULL, secondary_txt_records);
+ if (config.service_type == APST_airplay2) {
+ mdns_update(NULL, secondary_txt_records);
+ }
#ifdef CONFIG_METADATA
check_and_send_plist_metadata(messagePlist, "name", 'snam');
@@ -2659,11 +2725,13 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
warn("Unrecognised SETUP incoming message -- ignored.");
}
} else {
-
- debug(2, "Connection %d from \"%s\": Subsequent (i.e. with streams array) SETUP (AirPlay 2) on %s", conn->connection_number,
- conn->ap2_client_name,
- get_category_string(conn->airplay_stream_category));
- debug_log_rtsp_message_conn(conn, 2, "Subsequent (i.e. with streams array) SETUP (AirPlay 2) incoming message", req);
+
+ debug(2,
+ "Connection %d from \"%s\": Subsequent (i.e. with streams array) SETUP (AirPlay 2) on %s",
+ conn->connection_number, conn->ap2_client_name,
+ get_category_string(conn->airplay_stream_category));
+ debug_log_rtsp_message_conn(
+ conn, 2, "Subsequent (i.e. with streams array) SETUP (AirPlay 2) incoming message", req);
if (conn->airplay_stream_category == ptp_stream) {
@@ -2789,7 +2857,8 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
conn->input_rate = item_value;
debug(4, "Set conn->input_rate: %u.", conn->input_rate);
} else {
- debug(1, "Connection %d. No sample rate (sr) property found in setup.", conn->connection_number);
+ debug(1, "Connection %d. No sample rate (sr) property found in setup.",
+ conn->connection_number);
}
item = plist_dict_get_item(stream0, "spf"); // samples per frame
@@ -2798,7 +2867,8 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
conn->frames_per_packet = item_value;
debug(4, "Set conn->frames_per_packet: %u.", conn->frames_per_packet);
} else {
- debug(1, "Connection %d. No samples per frame (spf) property found in setup.", conn->connection_number);
+ debug(1, "Connection %d. No samples per frame (spf) property found in setup.",
+ conn->connection_number);
}
// bind a new UDP port and get a socket
@@ -2813,7 +2883,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
conn->local_realtime_audio_port);
named_pthread_create(&conn->rtp_realtime_audio_thread, NULL, &rtp_realtime_audio_receiver,
- (void *)conn, "ap2_rat_%d", conn->connection_number);
+ (void *)conn, "ap2_ra_%d", conn->connection_number);
plist_dict_set_item(stream0dict, "type", plist_new_uint(96));
plist_dict_set_item(stream0dict, "dataPort",
plist_new_uint(conn->local_realtime_audio_port));
@@ -2859,8 +2929,8 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
// debug(1, "Connection %d: create rtp_buffered_audio_thread", conn->connection_number);
named_pthread_create_with_priority(&conn->rtp_buffered_audio_thread, 2,
- &rtp_buffered_audio_processor, (void *)conn,
- "ap2_bat_%d", conn->connection_number);
+ &rtp_buffered_audio_processor, (void *)conn, "ap2_ba_%d",
+ conn->connection_number);
plist_dict_set_item(stream0dict, "type", plist_new_uint(103));
plist_dict_set_item(stream0dict, "dataPort",
@@ -2912,7 +2982,8 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
snprintf(salt_suffix, sizeof(salt_suffix), "%" PRIu64 "", seed);
conn->ap2_pairing_context.data_cipher_bundle.cipher_ctx =
pair_cipher_new(PAIR_SERVER_HOMEKIT, 5, conn->pair_setup_result->shared_secret,
- conn->pair_setup_result->shared_secret_len, salt_suffix); // last argument is the (possible) dynamic salt suffix
+ conn->pair_setup_result->shared_secret_len,
+ salt_suffix); // last argument is the (possible) dynamic salt suffix
if (conn->ap2_pairing_context.data_cipher_bundle.cipher_ctx != NULL) {
conn->ap2_pairing_context.data_cipher_bundle.description = strdup("DataStream");
// get a port to use as an data port
@@ -3137,34 +3208,34 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req,
debug(3, "Connection %d: request to set AirPlay Volume to: %f.", conn->connection_number,
volume);
// if we are playing, go ahead and change the volume
-// #ifdef CONFIG_DBUS_INTERFACE
-// if (dbus_service_is_running()) {
-// shairport_sync_set_volume(shairportSyncSkeleton, volume);
-// } else {
-// #endif
- pthread_rwlock_rdlock(&principal_conn_lock); // don't let the principal_conn be changed
- pthread_cleanup_push(rwlock_unlock, (void *)&principal_conn_lock);
- if (principal_conn == conn) {
- debug(3, "Connection %d: set player volume to %.3f.", conn->connection_number, volume);
- player_volume(volume, conn);
- debug(3, "Connection %d: set player volume to %.3f success.", conn->connection_number,
- volume);
- } else {
- if (principal_conn != NULL)
- debug(1, "Connection %d: fail to set player volume to %.3f. Principal conn is %d.",
- conn->connection_number, volume, principal_conn->connection_number);
- else
- debug(1, "Connection %d: fail to set player volume to %.3f. Principal conn is NULL.",
- conn->connection_number, volume);
- }
- if (conn != NULL) {
- conn->own_airplay_volume = volume;
- conn->own_airplay_volume_set = 1;
- }
- pthread_cleanup_pop(1); // release the principal_conn lock
-// #ifdef CONFIG_DBUS_INTERFACE
-// }
-// #endif
+ // #ifdef CONFIG_DBUS_INTERFACE
+ // if (dbus_service_is_running()) {
+ // shairport_sync_set_volume(shairportSyncSkeleton, volume);
+ // } else {
+ // #endif
+ pthread_rwlock_rdlock(&principal_conn_lock); // don't let the principal_conn be changed
+ pthread_cleanup_push(rwlock_unlock, (void *)&principal_conn_lock);
+ if (principal_conn == conn) {
+ debug(3, "Connection %d: set player volume to %.3f.", conn->connection_number, volume);
+ player_volume(volume, conn);
+ debug(3, "Connection %d: set player volume to %.3f success.", conn->connection_number,
+ volume);
+ } else {
+ if (principal_conn != NULL)
+ debug(1, "Connection %d: fail to set player volume to %.3f. Principal conn is %d.",
+ conn->connection_number, volume, principal_conn->connection_number);
+ else
+ debug(1, "Connection %d: fail to set player volume to %.3f. Principal conn is NULL.",
+ conn->connection_number, volume);
+ }
+ if (conn != NULL) {
+ conn->own_airplay_volume = volume;
+ conn->own_airplay_volume_set = 1;
+ }
+ pthread_cleanup_pop(1); // release the principal_conn lock
+ // #ifdef CONFIG_DBUS_INTERFACE
+ // }
+ // #endif
} else if (strncmp(cp, "progress: ", strlen("progress: ")) ==
0) { // this can be sent even when metadata is not solicited
@@ -3692,7 +3763,7 @@ static char *make_nonce(void) {
// int ignore =
if (read(lfd, random, sizeof(random)) != sizeof(random))
debug(1, "Error reading /dev/urandom");
- close(lfd);
+ safe_socket_close(&lfd);
return base64_enc(random, 8); // returns a pointer to malloc'ed memory
}
@@ -3911,8 +3982,7 @@ void rtsp_conversation_thread_cleanup_function(void *arg) {
"%s:%u.",
conn->connection_number, conn->fd, conn->client_ip_string, conn->client_rtsp_port,
conn->self_ip_string, conn->self_rtsp_port);
- close(conn->fd);
- conn->fd = 0;
+ safe_socket_close(&conn->fd);
}
#ifdef CONFIG_AIRPLAY_2
@@ -3923,19 +3993,7 @@ void rtsp_conversation_thread_cleanup_function(void *arg) {
// give the event receiver a chance to exit normally, if it exists
if (conn->rtp_event_thread != NULL) {
- uint64_t event_receiver_start_wait_time = get_absolute_time_in_ns();
- int64_t event_receiver_wait_time = 0;
- do {
- if (conn->ap2_event_receiver_exited == 0)
- usleep(50000);
- event_receiver_wait_time = get_absolute_time_in_ns() - event_receiver_start_wait_time;
- } while ((conn->ap2_event_receiver_exited == 0) && (event_receiver_wait_time < 2000000000L));
-
- if (conn->ap2_event_receiver_exited == 0) {
- debug(2, "Connection %d: %s event receiver has not exited, so cancelling it.",
- conn->connection_number, get_category_string(conn->airplay_stream_category));
- pthread_cancel(*conn->rtp_event_thread);
- }
+ pthread_cancel(*conn->rtp_event_thread);
pthread_join(*conn->rtp_event_thread, NULL);
free(conn->rtp_event_thread);
conn->rtp_event_thread = NULL;
@@ -3950,20 +4008,17 @@ void rtsp_conversation_thread_cleanup_function(void *arg) {
if (conn->control_socket) {
debug(3, "Connection %d: terminating -- closing control_socket %d.", conn->connection_number,
conn->control_socket);
- close(conn->control_socket);
- conn->control_socket = 0;
+ safe_socket_close(&conn->control_socket);
}
if (conn->timing_socket) {
debug(3, "Connection %d: terminating -- closing timing_socket %d.", conn->connection_number,
conn->timing_socket);
- close(conn->timing_socket);
- conn->timing_socket = 0;
+ safe_socket_close(&conn->timing_socket);
}
if (conn->audio_socket) {
debug(3, "Connection %d: terminating -- closing audio_socket %d.", conn->connection_number,
conn->audio_socket);
- close(conn->audio_socket);
- conn->audio_socket = 0;
+ safe_socket_close(&conn->audio_socket);
}
if (conn->auth_nonce) {
free(conn->auth_nonce);
@@ -4038,7 +4093,7 @@ void rtsp_conversation_thread_cleanup_function(void *arg) {
#endif
debug(3, "Connection %d: Closed.", conn->connection_number);
conn->running = 0; // for the garbage collector
- release_play_lock(conn);
+ // release_play_lock(conn);
pthread_setcancelstate(oldState, NULL);
}
}
@@ -4072,11 +4127,11 @@ static void *rtsp_conversation_thread_func(void *pconn) {
die("Connection %d: error %d initialising player_create_delete_mutex.", conn->connection_number,
rc);
- #ifdef CONFIG_AIRPLAY_2
- rc = pthread_mutex_init(&conn->event_sender_mutex, NULL);
+#ifdef CONFIG_AIRPLAY_2
+ rc = pthread_mutex_init(&conn->event_sender_mutex, NULL);
if (rc)
die("Connection %d: error %d initialising event_sender_mutex.", conn->connection_number, rc);
- #endif
+#endif
// nothing before this is cancellable
pthread_cleanup_push(rtsp_conversation_thread_cleanup_function, (void *)conn);
@@ -4128,7 +4183,7 @@ static void *rtsp_conversation_thread_func(void *pconn) {
hdr = msg_get_header(req, "CSeq");
if (hdr)
msg_add_header(resp, "CSeq", hdr);
- // msg_add_header(resp, "Audio-Jack-Status", "connected; type=analog");
+ // msg_add_header(resp, "Audio-Jack-Status", "connected; type=analog");
#ifdef CONFIG_AIRPLAY_2
char server_string[128];
snprintf(server_string, sizeof(server_string), "AirTunes/%s", config.srcvers);
@@ -4137,18 +4192,17 @@ static void *rtsp_conversation_thread_func(void *pconn) {
msg_add_header(resp, "Server", "AirTunes/105.1");
#endif
+ // we are only concerned with classic_airplay_authorized if we are running in classic AirPlay
+ // mode either because the build is for classic AirPlay only or, if the build is for AirPlay
+ // 2, we happen to be running in classic AirPlay compatibility mode
-
-// we are only concerned with classic_airplay_authorized if we are running in classic AirPlay mode
-// either because the build is for classic AirPlay only or, if the build is for AirPlay 2,
-// we happen to be running in classic AirPlay compatibility mode
-
- if ((conn->authorized == 1) ||
+ if ((conn->authorized == 1) ||
#ifdef CONFIG_AIRPLAY_2
- (conn->airplay_type != ap_1) ||
-#endif
- (rtsp_classic_airplay_auth(&conn->auth_nonce, req, resp) == 0)) {
- conn->authorized = 1; // it must have been classic airplay and authorized or didn't need a password, or not classic AirPlay at all
+ (conn->airplay_type != ap_1) ||
+#endif
+ (rtsp_classic_airplay_auth(&conn->auth_nonce, req, resp) == 0)) {
+ conn->authorized = 1; // it must have been classic airplay and authorized or didn't need a
+ // password, or not classic AirPlay at all
struct method_handler *mh;
int method_selected = 0;
for (mh = method_handlers; mh->method; mh++) {
@@ -4208,8 +4262,10 @@ static void *rtsp_conversation_thread_func(void *pconn) {
so_linger.l_linger = 0;
err = setsockopt(conn->fd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger);
if (err)
- debug(1, "Connection %d: Could not set the RTSP socket to abort due to a write error on closing.",
- conn->connection_number);
+ debug(1,
+ "Connection %d: Could not set the RTSP socket to abort due to a write error on "
+ "closing.",
+ conn->connection_number);
conn->stop = 1;
// if (debuglev >= 1)
// debuglev = 3; // see what happens next
@@ -4237,12 +4293,14 @@ static void *rtsp_conversation_thread_func(void *pconn) {
debug(1, "rtsp_read_request_response_bad_packet write response error %d: \"%s\".", errno,
(char *)errorstring);
} else if (lreply != (ssize_t)strlen(response_text)) {
- debug(1, "rtsp_read_request_response_bad_packet write %zd bytes requested but %d written.",
+ debug(1,
+ "rtsp_read_request_response_bad_packet write %zd bytes requested but %d written.",
strlen(response_text), reply);
}
}
}
}
+ release_play_lock(conn);
pthread_cleanup_pop(1);
debug(2, "Connection %d: exit.", conn->connection_number);
pthread_exit(NULL);
@@ -4271,24 +4329,20 @@ void rtsp_listen_loop_cleanup_handler(__attribute__((unused)) void *arg) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
debug(2, "rtsp_listen_loop_cleanup_handler called.");
- cancel_all_RTSP_threads(unspecified_stream_category, 0); // kill all RTSP listeners
+ if (conns) {
+ cancel_all_RTSP_threads(unspecified_stream_category, 0); // kill all RTSP listeners
+ free(conns);
+ conns = NULL;
+ }
int *sockfd = (int *)arg;
if (sockfd) {
int i;
for (i = 1; i <= sockfd[0]; i++) {
- debug(2, "closing socket %d.", sockfd[i]);
- close(sockfd[i]);
+ safe_socket_close(&sockfd[i]);
}
free(sockfd);
}
pthread_setcancelstate(oldState, NULL);
- int i;
- for (i = 0; i < nconns; i++) {
- if (conns[i] != NULL) {
- free(conns[i]);
- conns[i] = NULL;
- }
- }
}
void *rtsp_listen_loop(__attribute((unused)) void *arg) {
@@ -4390,16 +4444,16 @@ void *rtsp_listen_loop(__attribute((unused)) void *arg) {
char **t1 = txt_records; // ap1 text records
char **t2 = NULL; // possibly two text records
#ifdef CONFIG_AIRPLAY_2
- // make up a secondary set of text records
- t2 = secondary_txt_records; // second set of text records in AirPlay 2 only
+ if (config.service_type == APST_airplay2) {
+ // make up a secondary set of text records
+ t2 = secondary_txt_records; // second set of text records in AirPlay 2 only
+ }
#endif
build_bonjour_strings(NULL); // no conn yet
// if a thread is created, e.g. Avahi, it'll inherit the name from this thread
mdns_register(t1, t2); // note that the dacp thread could still be using the mdns stuff after
// all player threads have been terminated, so mdns_unregister can't be
// in the rtsp_listen_loop cleanup.
- pthread_t tid = pthread_self();
- do_pthread_setname(&tid, "listener");
pthread_setcancelstate(oldState, NULL);
int acceptfd;
struct timeval tv;
@@ -4465,7 +4519,7 @@ void *rtsp_listen_loop(__attribute((unused)) void *arg) {
} else if ((principal_conn != NULL) && (config.allow_session_interruption == 0)) {
debug(1, "Connection %d: %s session interruption not allowed", conn->connection_number,
get_category_string(conn->airplay_stream_category));
- close(conn->fd);
+ safe_socket_close(&conn->fd);
#endif
} else {
diff --git a/rtsp.h b/rtsp.h
index b873425f..54acc368 100644
--- a/rtsp.h
+++ b/rtsp.h
@@ -24,30 +24,13 @@ typedef struct {
void msg_retain(rtsp_message *msg);
void msg_free(rtsp_message **msgh);
-extern pthread_rwlock_t principal_conn_lock;
-extern rtsp_conn_info *principal_conn;
-extern rtsp_conn_info **conns;
-
void *rtsp_listen_loop(__attribute((unused)) void *arg);
-void lock_player();
-void unlock_player();
-
-// result of trying to acquire or release the play lock
-typedef enum {
- play_lock_released,
- play_lock_already_released,
- play_lock_already_acquired,
- play_lock_acquired_without_breaking_in,
- play_lock_acquired_by_breaking_in,
- play_lock_aquisition_failed
-} play_lock_r;
-
// this can be used to [try to] forcibly stop a play session
-play_lock_r get_play_lock(rtsp_conn_info *conn, int allow_session_interruption);
+// play_lock_r get_play_lock(rtsp_conn_info *conn, int allow_session_interruption);
// this will release the play lock only if the conn has it or if the conn is NULL
-void release_play_lock(rtsp_conn_info *conn);
+void stop_play(); // stop and drop a playing connection
#ifdef CONFIG_AIRPLAY_2
ssize_t read_encrypted(int fd, pair_cipher_bundle *ctx, void *buf, size_t count);
diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf
index 35f1d7a5..86009806 100644
--- a/scripts/shairport-sync.conf
+++ b/scripts/shairport-sync.conf
@@ -14,11 +14,16 @@ general =
// %v for the version number, e.g. 3.0 and
// %V for the full version string, e.g. 3.3-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc
// Overall length can not exceed 50 characters. Example: "Shairport Sync %v on %H".
+//
// password = "secret"; // leave this commented out if you don't want to require a password
+// About the "service_type" setting below: On an AirPlay-2-capable Shairport Sync, if you wish to offer only the older "classic" AirPlay (sometimes called AirPlay 1), set service_type to "classic".
+// A potential use of this would be for Apple Music on Windows, which is not compatible with the AirPlay 2 service offered by Shairport Sync, but which is compatible with the classic service.
+// The default is "auto", which means that the AirPlay 2 service will be provided if NQPTP is present and "classic" AirPlay will be provided otherwise, with "(Classic)" appended to the service name (see above).
+// service_type = "auto"; // This can be "auto", "airplay2" or "classic".
// The interpolation setting below controls how Shairport Sync adds or removes frames of audio to keep in sync.
// "auto" (default) measures the processor's floating point speed and chooses "soxr" if available and it is fast enough. Otherwise, "vernier" is selected.
-// "soxr" uses the SoX library to recode a packet of frames to a new packet containing more or fewer frames. This needs a processor with fast floating point capability.
// "vernier" recodes a packet of frames to a new packet containing more or fewer frames. This is recommended for low powered devices.
+// "soxr" uses the SoX library to recode a packet of frames to a new packet containing more or fewer frames. This needs a processor with fast floating point capability.
// "basic" causes the simple removal or insertion of frames in a packet of frames. Not recommended.
// interpolation = "auto"; // aka "stuffing". Default is "auto". Alternatives are "vernier", "basic" or "soxr". Choose "soxr" only if you have a reasonably fast processor and Shairport Sync has been built with "soxr" support.
// output_backend = "alsa"; // Run "shairport-sync -h" to get a list of all output_backends, e.g. "alsa", "pipe", "stdout". The default is the first one.
diff --git a/scripts/shairport-sync.service.in b/scripts/shairport-sync.service.in
index 6314b27d..190fc9de 100644
--- a/scripts/shairport-sync.service.in
+++ b/scripts/shairport-sync.service.in
@@ -4,6 +4,7 @@ After=sound.target
Wants=network-online.target
After=network.target network-online.target
@SYSTEMD_AFTER_ARGS@
+@SYSTEMD_WANTS_ARGS@
@SYSTEMD_REQUIRES_ARGS@
StartLimitIntervalSec=300
StartLimitBurst=5
diff --git a/shairport.c b/shairport.c
index 17c1ce0a..f4164e16 100644
--- a/shairport.c
+++ b/shairport.c
@@ -2,7 +2,7 @@
* Shairport, an Apple Airplay receiver
* Copyright (c) James Laird 2013
* All rights reserved.
- * Modifications and additions (c) Mike Brady 2014--2025
+ * Modifications and additions (c) Mike Brady 2014--2026
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -50,12 +50,12 @@
#ifdef CONFIG_AIRPLAY_2
#include "ptp-utilities.h"
+#include "utilities/generate_device_uuid.h"
+#include "utilities/generate_random_uuid.h"
#include
#include
#include
#include
-#include "utilities/generate_random_uuid.h"
-#include "utilities/generate_device_uuid.h"
#endif
@@ -82,6 +82,7 @@
#include "common.h"
#include "rtp.h"
#include "rtsp.h"
+#include "utilities/string_utilities.h"
#if defined(CONFIG_DACP_CLIENT)
#include "dacp.h"
@@ -265,7 +266,7 @@ void *soxr_time_check(__attribute__((unused)) void *arg) {
outbuffer, buffer_length - 1, &odone, // Output.
&io_spec, // Input, output and transfer spec.
NULL, NULL); // Default configuration.
-
+
pthread_setcancelstate(oldState, NULL);
}
@@ -321,6 +322,7 @@ void usage(char *progname) {
printf("Options:\n");
printf(" -h, --help Show this help.\n");
printf(" -V, --version Show version information -- the version string.\n");
+ printf(" --service-type Select the type of AirPlay service: \"auto\", \"airplay2\" or \"classic\". (You can use \"airplay1\" in place of \"classic\".)\n");
printf(" -X, --displayConfig Output OS information, version string, command line, configuration file and active settings to the log.\n");
printf(" --statistics Print some interesting statistics. More will be printed if -v / -vv / -vvv are also chosen.\n");
printf(" -v, --verbose Print debug information; -v some; -vv more; -vvv lots -- generally too much.\n");
@@ -382,6 +384,7 @@ void usage(char *progname) {
int parse_options(int argc, char **argv) {
// there are potential memory leaks here -- it's called a second time, previously allocated
// strings will dangle.
+ char *cli_service_type_string = NULL;
char *raw_service_name = NULL; /* Used to pick up the service name before possibly expanding it */
char *stuffing = NULL; /* used for picking up the stuffing option */
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
@@ -394,40 +397,40 @@ int parse_options(int argc, char **argv) {
int tolerance_in_frames = 0;
poptContext optCon; /* context for parsing command-line options */
struct poptOption optionsTable[] = {
- {"verbose", 'v', POPT_ARG_NONE, NULL, 'v', NULL, NULL},
- {"kill", 'k', POPT_ARG_NONE, &killOption, 0, NULL, NULL},
- {"daemon", 'd', POPT_ARG_NONE, &daemonisewith, 0, NULL, NULL},
- {"justDaemoniseNoPIDFile", 'j', POPT_ARG_NONE, &daemonisewithout, 0, NULL, NULL},
- {"configfile", 'c', POPT_ARG_STRING, &config.configfile, 0, NULL, NULL},
- {"statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL, NULL},
- {"logOutputLevel", 0, POPT_ARG_NONE, &config.logOutputLevel, 0, NULL, NULL},
- {"version", 'V', POPT_ARG_NONE, NULL, 0, NULL, NULL},
- {"displayConfig", 'X', POPT_ARG_NONE, &display_config_selected, 0, NULL, NULL},
- {"port", 'p', POPT_ARG_INT, &config.port, 0, NULL, NULL},
- {"name", 'a', POPT_ARG_STRING, &raw_service_name, 0, NULL, NULL},
- {"output", 'o', POPT_ARG_STRING, &config.output_name, 0, NULL, NULL},
- {"on-start", 'B', POPT_ARG_STRING, &config.cmd_start, 0, NULL, NULL},
- {"on-stop", 'E', POPT_ARG_STRING, &config.cmd_stop, 0, NULL, NULL},
- {"wait-cmd", 'w', POPT_ARG_NONE, &config.cmd_blocking, 0, NULL, NULL},
- {"mdns", 'm', POPT_ARG_STRING, &config.mdns_name, 0, NULL, NULL},
- {"latency", 'L', POPT_ARG_INT, &config.userSuppliedLatency, 0, NULL, NULL},
- {"stuffing", 'S', POPT_ARG_STRING, &stuffing, 'S', NULL, NULL},
- {"resync", 'r', POPT_ARG_INT, &resync_threshold_in_frames, 'r', NULL, NULL},
- {"timeout", 't', POPT_ARG_INT, &config.timeout, 't', NULL, NULL},
- {"password", 0, POPT_ARG_STRING, &config.password, 0, NULL, NULL},
+ {"verbose", 'v', POPT_ARG_NONE, NULL, 'v', NULL, NULL},
+ {"kill", 'k', POPT_ARG_NONE, &killOption, 0, NULL, NULL},
+ {"daemon", 'd', POPT_ARG_NONE, &daemonisewith, 0, NULL, NULL},
+ {"justDaemoniseNoPIDFile", 'j', POPT_ARG_NONE, &daemonisewithout, 0, NULL, NULL},
+ {"configfile", 'c', POPT_ARG_STRING, &config.configfile, 0, NULL, NULL},
+ {"statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL, NULL},
+ {"logOutputLevel", 0, POPT_ARG_NONE, &config.logOutputLevel, 0, NULL, NULL},
+ {"version", 'V', POPT_ARG_NONE, NULL, 0, NULL, NULL},
+ {"displayConfig", 'X', POPT_ARG_NONE, &display_config_selected, 0, NULL, NULL},
+ {"port", 'p', POPT_ARG_INT, &config.port, 0, NULL, NULL},
+ {"name", 'a', POPT_ARG_STRING, &raw_service_name, 0, NULL, NULL},
+ {"output", 'o', POPT_ARG_STRING, &config.output_name, 0, NULL, NULL},
+ {"on-start", 'B', POPT_ARG_STRING, &config.cmd_start, 0, NULL, NULL},
+ {"on-stop", 'E', POPT_ARG_STRING, &config.cmd_stop, 0, NULL, NULL},
+ {"wait-cmd", 'w', POPT_ARG_NONE, &config.cmd_blocking, 0, NULL, NULL},
+ {"mdns", 'm', POPT_ARG_STRING, &config.mdns_name, 0, NULL, NULL},
+ {"latency", 'L', POPT_ARG_INT, &config.userSuppliedLatency, 0, NULL, NULL},
+ {"stuffing", 'S', POPT_ARG_STRING, &stuffing, 'S', NULL, NULL},
+ {"resync", 'r', POPT_ARG_INT, &resync_threshold_in_frames, 'r', NULL, NULL},
+ {"timeout", 't', POPT_ARG_INT, &config.timeout, 't', NULL, NULL},
+ {"password", 0, POPT_ARG_STRING, &config.password, 0, NULL, NULL},
+ {"service-type", 0, POPT_ARG_STRING, &cli_service_type_string, 0, NULL, NULL},
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
- {"dbus-default-message-bus", 0, POPT_ARG_STRING, &dbus_default_message_bus, 0, NULL, NULL},
+ {"dbus-default-message-bus", 0, POPT_ARG_STRING, &dbus_default_message_bus, 0, NULL, NULL},
#endif
- {"tolerance", 'z', POPT_ARG_INT, &tolerance_in_frames, 'z', NULL, NULL},
- {"use-stderr", 'u', POPT_ARG_NONE, NULL, 'u', NULL, NULL},
- {"log-to-syslog", 0, POPT_ARG_NONE, &log_to_syslog_selected, 0, NULL, NULL},
+ {"tolerance", 'z', POPT_ARG_INT, &tolerance_in_frames, 'z', NULL, NULL},
+ {"use-stderr", 'u', POPT_ARG_NONE, NULL, 'u', NULL, NULL},
+ {"log-to-syslog", 0, POPT_ARG_NONE, &log_to_syslog_selected, 0, NULL, NULL},
#ifdef CONFIG_METADATA
- {"metadata-enable", 'M', POPT_ARG_NONE, &config.metadata_enabled, 'M', NULL, NULL},
- {"metadata-pipename", 0, POPT_ARG_STRING, &config.metadata_pipename, 0, NULL, NULL},
- {"get-coverart", 'g', POPT_ARG_NONE, &config.get_coverart, 'g', NULL, NULL},
+ {"metadata-enable", 'M', POPT_ARG_NONE, &config.metadata_enabled, 'M', NULL, NULL},
+ {"metadata-pipename", 0, POPT_ARG_STRING, &config.metadata_pipename, 0, NULL, NULL},
+ {"get-coverart", 'g', POPT_ARG_NONE, &config.get_coverart, 'g', NULL, NULL},
#endif
- POPT_AUTOHELP{NULL, 0, 0, NULL, 0, NULL, NULL}
- };
+ POPT_AUTOHELP{NULL, 0, 0, NULL, 0, NULL, NULL}};
// we have to parse the command line arguments to look for a config file
int optind;
@@ -552,8 +555,8 @@ int parse_options(int argc, char **argv) {
// for unexpected circumstances
config.model = strdup("ShairportSync");
- //config.model = strdup("AirPort10,115");
- // config.model = strdup("AudioAccessory5,1");
+ // config.model = strdup("AirPort10,115");
+ // config.model = strdup("AudioAccessory5,1");
// config.srcvers = strdup(PACKAGE_VERSION);
// config.srcvers = strdup("760.13.1");
@@ -612,8 +615,8 @@ int parse_options(int argc, char **argv) {
// use the MAC address placed in config.hw_addr to generate the default airplay_device_id
uint64_t temporary_airplay_id = nctoh64(config.hw_addr);
temporary_airplay_id =
- temporary_airplay_id >> 16; // we only use the first 6 bytes but have imported 8.
-
+ temporary_airplay_id >> 16; // we only use the first 6 bytes but have imported 8.
+
config_init(&config_file_stuff);
config_file_real_path = realpath(config.configfile, NULL);
@@ -628,6 +631,10 @@ int parse_options(int argc, char **argv) {
// make config.cfg point to it
config.cfg = &config_file_stuff;
+ /* See if a specific service type has been requested */
+ if (config_lookup_non_empty_string(config.cfg, "general.service_type", &str)) {
+ config.service_type = string_to_service_type(str, "general service_type");
+ }
/* Get the Service Name. */
if (config_lookup_non_empty_string(config.cfg, "general.name", &str)) {
raw_service_name = (char *)str;
@@ -680,8 +687,7 @@ int parse_options(int argc, char **argv) {
* starting at the port base. Only three ports are needed. */
if (config_lookup_int(config.cfg, "general.udp_port_range", &value)) {
if ((value < 3) || (value > 65535))
- die("Invalid port range \"%d\". It should be between 3 and 65535, default is 10",
- value);
+ die("Invalid port range \"%d\". It should be between 3 and 65535, default is 10", value);
else
config.udp_port_range = value;
}
@@ -1297,6 +1303,132 @@ int parse_options(int argc, char **argv) {
"controlled by a hardware mixer. "
"You must not use a hardware mixer when using the loudness filter.");
+#if defined(CONFIG_DBUS_INTERFACE)
+ /* Get the dbus service sbus setting. */
+ if (config_lookup_string(config.cfg, "general.dbus_service_bus", &str)) {
+ if (strcasecmp(str, "system") == 0)
+ config.dbus_service_bus_type = DBT_system;
+ else if (strcasecmp(str, "session") == 0)
+ config.dbus_service_bus_type = DBT_session;
+ else
+ die("Invalid dbus_service_bus option choice \"%s\". It should be \"system\" (default) or "
+ "\"session\"",
+ str);
+ }
+#endif
+
+#if defined(CONFIG_MPRIS_INTERFACE)
+ /* Get the mpris service sbus setting. */
+ if (config_lookup_string(config.cfg, "general.mpris_service_bus", &str)) {
+ if (strcasecmp(str, "system") == 0)
+ config.mpris_service_bus_type = DBT_system;
+ else if (strcasecmp(str, "session") == 0)
+ config.mpris_service_bus_type = DBT_session;
+ else
+ die("Invalid mpris_service_bus option choice \"%s\". It should be \"system\" (default) "
+ "or "
+ "\"session\"",
+ str);
+ }
+#endif
+
+#ifdef CONFIG_MQTT
+ config_set_lookup_bool(config.cfg, "mqtt.enabled", &config.mqtt_enabled);
+ if (config.mqtt_enabled && !config.metadata_enabled) {
+ die("You need to have metadata enabled in order to use mqtt");
+ }
+ if (config_lookup_non_empty_string(config.cfg, "mqtt.hostname", &str)) {
+ config.mqtt_hostname = (char *)str;
+ // TODO: Document that, if this is false, whole mqtt func is disabled
+ }
+ config.mqtt_port = 1883;
+ if (config_lookup_int(config.cfg, "mqtt.port", &value)) {
+ if ((value < 0) || (value > 65535))
+ die("Invalid mqtt port number \"%d\". It should be between 0 and 65535, default is 1883",
+ value);
+ else
+ config.mqtt_port = value;
+ }
+
+ if (config_lookup_non_empty_string(config.cfg, "mqtt.username", &str)) {
+ config.mqtt_username = (char *)str;
+ }
+ if (config_lookup_non_empty_string(config.cfg, "mqtt.password", &str)) {
+ config.mqtt_password = (char *)str;
+ }
+ int capath = 0;
+ if (config_lookup_non_empty_string(config.cfg, "mqtt.capath", &str)) {
+ config.mqtt_capath = (char *)str;
+ capath = 1;
+ }
+ if (config_lookup_non_empty_string(config.cfg, "mqtt.cafile", &str)) {
+ if (capath)
+ die("Supply either mqtt cafile or mqtt capath -- you have supplied both!");
+ config.mqtt_cafile = (char *)str;
+ }
+ int certkeynum = 0;
+ if (config_lookup_non_empty_string(config.cfg, "mqtt.certfile", &str)) {
+ config.mqtt_certfile = (char *)str;
+ certkeynum++;
+ }
+ if (config_lookup_non_empty_string(config.cfg, "mqtt.keyfile", &str)) {
+ config.mqtt_keyfile = (char *)str;
+ certkeynum++;
+ }
+ if (certkeynum != 0 && certkeynum != 2) {
+ die("If you want to use TLS Client Authentication, you have to specify "
+ "mqtt.certfile AND mqtt.keyfile.\nYou have supplied only one of them.\n"
+ "If you do not want to use TLS Client Authentication, leave both empty.");
+ }
+
+ if (config_lookup_non_empty_string(config.cfg, "mqtt.topic", &str)) {
+ config.mqtt_topic = (char *)str;
+ }
+ config_set_lookup_bool(config.cfg, "mqtt.publish_raw", &config.mqtt_publish_raw);
+ config_set_lookup_bool(config.cfg, "mqtt.publish_parsed", &config.mqtt_publish_parsed);
+ config_set_lookup_bool(config.cfg, "mqtt.publish_cover", &config.mqtt_publish_cover);
+ config_set_lookup_bool(config.cfg, "mqtt.publish_retain", &config.mqtt_publish_retain);
+ if (config.mqtt_publish_cover && !config.get_coverart) {
+ die("You need to have metadata.include_cover_art enabled in order to use "
+ "mqtt.publish_cover");
+ }
+ config_set_lookup_bool(config.cfg, "mqtt.enable_autodiscovery",
+ &config.mqtt_enable_autodiscovery);
+ if (config_lookup_non_empty_string(config.cfg, "mqtt.autodiscovery_prefix", &str)) {
+ config.mqtt_autodiscovery_prefix = (char *)str;
+ }
+ config_set_lookup_bool(config.cfg, "mqtt.enable_remote", &config.mqtt_enable_remote);
+ if (config_lookup_non_empty_string(config.cfg, "mqtt.empty_payload_substitute", &str)) {
+ if (strlen(str) == 0)
+ config.mqtt_empty_payload_substitute = NULL;
+ else
+ config.mqtt_empty_payload_substitute = strdup(str);
+ } else {
+ config.mqtt_empty_payload_substitute = strdup("--");
+ }
+#ifndef CONFIG_AVAHI
+ if (config.mqtt_enable_remote) {
+ die("You have enabled MQTT remote control which requires shairport-sync to be built with "
+ "Avahi, but your installation is not using avahi. Please reinstall/recompile with "
+ "avahi enabled, or disable remote control.");
+ }
+#endif
+#endif
+#ifdef CONFIG_AIRPLAY_2
+ long long aid;
+
+ // replace the airplay_device_id with this, if provided
+ if (config_lookup_int64(config.cfg, "general.airplay_device_id", &aid)) {
+ temporary_airplay_id = aid;
+ }
+
+ // add the airplay_device_id_offset if provided
+ if (config_lookup_int64(config.cfg, "general.airplay_device_id_offset", &aid)) {
+ temporary_airplay_id += aid;
+ }
+
+#endif
+
} else {
if (config_error_type(&config_file_stuff) == CONFIG_ERR_FILE_IO)
die("Error reading configuration file \"%s\": \"%s\".", config_file_real_path,
@@ -1306,131 +1438,6 @@ int parse_options(int argc, char **argv) {
config_error_file(&config_file_stuff), config_error_text(&config_file_stuff));
}
}
-#if defined(CONFIG_DBUS_INTERFACE)
- /* Get the dbus service sbus setting. */
- if (config_lookup_string(config.cfg, "general.dbus_service_bus", &str)) {
- if (strcasecmp(str, "system") == 0)
- config.dbus_service_bus_type = DBT_system;
- else if (strcasecmp(str, "session") == 0)
- config.dbus_service_bus_type = DBT_session;
- else
- die("Invalid dbus_service_bus option choice \"%s\". It should be \"system\" (default) or "
- "\"session\"",
- str);
- }
-#endif
-
-#if defined(CONFIG_MPRIS_INTERFACE)
- /* Get the mpris service sbus setting. */
- if (config_lookup_string(config.cfg, "general.mpris_service_bus", &str)) {
- if (strcasecmp(str, "system") == 0)
- config.mpris_service_bus_type = DBT_system;
- else if (strcasecmp(str, "session") == 0)
- config.mpris_service_bus_type = DBT_session;
- else
- die("Invalid mpris_service_bus option choice \"%s\". It should be \"system\" (default) or "
- "\"session\"",
- str);
- }
-#endif
-
-#ifdef CONFIG_MQTT
- config_set_lookup_bool(config.cfg, "mqtt.enabled", &config.mqtt_enabled);
- if (config.mqtt_enabled && !config.metadata_enabled) {
- die("You need to have metadata enabled in order to use mqtt");
- }
- if (config_lookup_non_empty_string(config.cfg, "mqtt.hostname", &str)) {
- config.mqtt_hostname = (char *)str;
- // TODO: Document that, if this is false, whole mqtt func is disabled
- }
- config.mqtt_port = 1883;
- if (config_lookup_int(config.cfg, "mqtt.port", &value)) {
- if ((value < 0) || (value > 65535))
- die("Invalid mqtt port number \"%d\". It should be between 0 and 65535, default is 1883",
- value);
- else
- config.mqtt_port = value;
- }
-
- if (config_lookup_non_empty_string(config.cfg, "mqtt.username", &str)) {
- config.mqtt_username = (char *)str;
- }
- if (config_lookup_non_empty_string(config.cfg, "mqtt.password", &str)) {
- config.mqtt_password = (char *)str;
- }
- int capath = 0;
- if (config_lookup_non_empty_string(config.cfg, "mqtt.capath", &str)) {
- config.mqtt_capath = (char *)str;
- capath = 1;
- }
- if (config_lookup_non_empty_string(config.cfg, "mqtt.cafile", &str)) {
- if (capath)
- die("Supply either mqtt cafile or mqtt capath -- you have supplied both!");
- config.mqtt_cafile = (char *)str;
- }
- int certkeynum = 0;
- if (config_lookup_non_empty_string(config.cfg, "mqtt.certfile", &str)) {
- config.mqtt_certfile = (char *)str;
- certkeynum++;
- }
- if (config_lookup_non_empty_string(config.cfg, "mqtt.keyfile", &str)) {
- config.mqtt_keyfile = (char *)str;
- certkeynum++;
- }
- if (certkeynum != 0 && certkeynum != 2) {
- die("If you want to use TLS Client Authentication, you have to specify "
- "mqtt.certfile AND mqtt.keyfile.\nYou have supplied only one of them.\n"
- "If you do not want to use TLS Client Authentication, leave both empty.");
- }
-
- if (config_lookup_non_empty_string(config.cfg, "mqtt.topic", &str)) {
- config.mqtt_topic = (char *)str;
- }
- config_set_lookup_bool(config.cfg, "mqtt.publish_raw", &config.mqtt_publish_raw);
- config_set_lookup_bool(config.cfg, "mqtt.publish_parsed", &config.mqtt_publish_parsed);
- config_set_lookup_bool(config.cfg, "mqtt.publish_cover", &config.mqtt_publish_cover);
- config_set_lookup_bool(config.cfg, "mqtt.publish_retain", &config.mqtt_publish_retain);
- if (config.mqtt_publish_cover && !config.get_coverart) {
- die("You need to have metadata.include_cover_art enabled in order to use mqtt.publish_cover");
- }
- config_set_lookup_bool(config.cfg, "mqtt.enable_autodiscovery",
- &config.mqtt_enable_autodiscovery);
- if (config_lookup_non_empty_string(config.cfg, "mqtt.autodiscovery_prefix", &str)) {
- config.mqtt_autodiscovery_prefix = (char *)str;
- }
- config_set_lookup_bool(config.cfg, "mqtt.enable_remote", &config.mqtt_enable_remote);
- if (config_lookup_non_empty_string(config.cfg, "mqtt.empty_payload_substitute", &str)) {
- if (strlen(str) == 0)
- config.mqtt_empty_payload_substitute = NULL;
- else
- config.mqtt_empty_payload_substitute = strdup(str);
- } else {
- config.mqtt_empty_payload_substitute = strdup("--");
- }
-#ifndef CONFIG_AVAHI
- if (config.mqtt_enable_remote) {
- die("You have enabled MQTT remote control which requires shairport-sync to be built with "
- "Avahi, but your installation is not using avahi. Please reinstall/recompile with "
- "avahi enabled, or disable remote control.");
- }
-#endif
-
-#ifdef CONFIG_AIRPLAY_2
- long long aid;
-
- // replace the airplay_device_id with this, if provided
- if (config_lookup_int64(config.cfg, "general.airplay_device_id", &aid)) {
- temporary_airplay_id = aid;
- }
-
- // add the airplay_device_id_offset if provided
- if (config_lookup_int64(config.cfg, "general.airplay_device_id_offset", &aid)) {
- temporary_airplay_id += aid;
- }
-
-#endif
-#endif
-
}
// now, do the command line options again, but this time do them fully -- it's a unix convention
@@ -1497,6 +1504,10 @@ int parse_options(int argc, char **argv) {
die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c));
}
+ if (cli_service_type_string != NULL)
+ config.service_type = string_to_service_type(cli_service_type_string,
+ "command line option \"--service-type\" argument");
+
poptFreeContext(optCon);
#if defined(CONFIG_DBUS_INTERFACE) || (CONFIG_MPRIS_INTERFACE)
@@ -1527,6 +1538,7 @@ int parse_options(int argc, char **argv) {
config.nqptp_shared_memory_interface_name = strdup(NQPTP_INTERFACE_NAME);
+ // create the config.ap1_prefix[i]
char apids[6 * 2 + 5 + 1]; // six pairs of digits, 5 colons and a NUL
apids[6 * 2 + 5] = 0; // NUL termination
int i;
@@ -1555,15 +1567,13 @@ int parse_options(int argc, char **argv) {
else
memcpy(result, config.ap1_prefix, sizeof(result));
- // OpenSSL is mandatory for AirPlay 2 anyway
-#ifdef CONFIG_OPENSSL
+ // OpenSSL is mandatory for AirPlay 2
EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
EVP_DigestInit_ex(mdctx, EVP_md5(), NULL);
EVP_DigestUpdate(mdctx, config.ap1_prefix, sizeof(config.ap1_prefix));
unsigned int md5_digest_len = EVP_MD_size(EVP_md5());
EVP_DigestFinal_ex(mdctx, result, &md5_digest_len);
EVP_MD_CTX_free(mdctx);
-#endif
// now, convert it into a type 4 UUID
// see https://stackoverflow.com/questions/10867405/generating-v5-uuid-what-is-name-and-namespace
@@ -1580,7 +1590,7 @@ int parse_options(int argc, char **argv) {
// Produces a UUID string at uuid consisting of lower-case letters
uuid_unparse_lower(result, psi_uuid);
config.airplay_psi = psi_uuid;
-
+
debug(3, "size of pk is %zu.", sizeof(config.airplay_pk));
pair_public_key_get(PAIR_SERVER_HOMEKIT, config.airplay_pk, config.airplay_device_id);
char buf[128];
@@ -1590,24 +1600,24 @@ int parse_options(int argc, char **argv) {
ptr += sprintf(ptr, "%02x", config.airplay_pk[pk_index]);
*ptr = '\0';
config.pk_string = strdup(buf);
-
+
// the features code is a 64-bit number, but in the mDNS advertisement, the least significant 32
// bit are given first for example, if the features number is 0x1C340405F4A00, it will be given as
// features=0x405F4A00,0x1C340 in the mDNS string, and in a signed decimal number in the plist:
// 496155702020608 this setting here is the source of both the plist features response and the
// mDNS string.
- config.airplay_features =
- 0x00018340405C4A00; // no AP2 metadata (b50), no AP1 text (b17), no AP1 progress (b16), no AP1 artwork (b15)
- // 0x0001C340405C4A00; // no AP2 metadata (b50), no AP1 text (b17), no AP1 progress (b16), no AP1 artwork (b15)
- // 0x0001C340445D0A00;
+ config.airplay_features = 0x00018340405C4A00; // no AP2 metadata (b50), no AP1 text (b17), no AP1
+ // progress (b16), no AP1 artwork (b15)
+ // 0x0001C340405C4A00; // no AP2 metadata (b50), no AP1 text (b17), no AP1 progress (b16), no
+ // AP1 artwork (b15) 0x0001C340445D0A00;
// config.airplay_features |= (1 << 26); // 0x0x4000000
- // features=0x0001C340445D0A00 -- AirPort Express
+ // features=0x0001C340445D0A00 -- AirPort Express
#ifdef CONFIG_METADATA
// If we are asking for metadata, turn on the relevant bits
-
+
// If bit 50 is set, metadata is sent via plists in POST /command payloads.
// The data consists of textual information about what is playing and cover art
// It does not seem possible to turn off the cover art.
@@ -1615,13 +1625,15 @@ int parse_options(int argc, char **argv) {
// While bit 50 is set, no data comes through the "classic" way. That is
// no progress, text or picture data comes through in the way that
// it comes through in Classic AirPlay (aka AirPlay 1).
-
+
// Although it is less flexible about what metadata is sent, bit 50 being set
// provides much more information, so should be the default for AirPlay 2
-
+
if (config.metadata_enabled != 0) {
- config.airplay_features |= (uint64_t)1 << 50; // metadata in a binary plist, including more state information
- // config.airplay_features |= ((uint64_t)1 << 15) | ((uint64_t)1 << 16) | ((uint64_t)1 << 17); // older metadata flags artwork, progress and text respectively
+ config.airplay_features |=
+ (uint64_t)1 << 50; // metadata in a binary plist, including more state information
+ // config.airplay_features |= ((uint64_t)1 << 15) | ((uint64_t)1 << 16) | ((uint64_t)1 << 17);
+ // // older metadata flags artwork, progress and text respectively
}
#endif
@@ -1642,11 +1654,11 @@ int parse_options(int argc, char **argv) {
if (padding)
*padding = 0;
debug(2, "airplay_fex is \"%s\"", config.airplay_fex);
-
+
// now the status flags
// Advertised with mDNS and returned with GET /info, see
// https://openairplay.github.io/airplay-spec/status_flags.html
-
+
config.airplay_statusflags = 0;
config.airplay_statusflags |= 1 << 2; // Audio cable is attached
if (config.password != NULL) {
@@ -1708,34 +1720,88 @@ int parse_options(int argc, char **argv) {
config.airplay_volume =
config.default_airplay_volume; // if no volume is ever set or requested, default to initial
// default value if nothing else comes in first.
- // now, do the substitutions in the service name
- char hostname[100];
- gethostname(hostname, 100);
- // strip off a terminating ., e.g. .local from the hostname
- char *last_dot = strrchr(hostname, '.');
- if (last_dot != NULL)
- *last_dot = '\0';
+ // let's see if we have AirPlay 2 and NQPTP is installed or not and set service_type accordingly
+ // or exit. the outcome will be APST_airplay2, APST_classic or APST_forced_classic, and APST_auto
+ // will be gone.
- char *i0;
- if (raw_service_name == NULL)
- i0 = strdup("%H"); // this is the default it the Service Name wasn't specified
- else
- i0 = strdup(raw_service_name);
+ char service_type_string[32];
+ service_type_to_string(config.service_type, service_type_string);
+ debug(1, "config.service_type is: \"%s\".", service_type_string);
- // here, do the substitutions for %h, %H, %v and %V
- char *i1 = str_replace(i0, "%h", hostname);
- if ((hostname[0] >= 'a') && (hostname[0] <= 'z'))
- hostname[0] = hostname[0] - 0x20; // convert a lowercase first letter into a capital letter
- char *i2 = str_replace(i1, "%H", hostname);
- char *i3 = str_replace(i2, "%v", PACKAGE_VERSION);
- char *vs = get_version_string();
- config.service_name = str_replace(i3, "%V", vs); // service name complete
- free(i0);
- free(i1);
- free(i2);
- free(i3);
- free(vs);
+#ifdef CONFIG_AIRPLAY_2
+ // don't bother checking for NQPTP if we are providing a classic service
+ if (config.service_type != APST_classic) {
+ ptp_send_control_message_string(
+ "T"); // send this message to get nqptp to create the named shm interface
+ int response = 0;
+ /*
+ uint64_t nqptp_start_waiting_time = get_absolute_time_in_ns();
+ int continue_waiting = 0;
+ int64_t time_spent_waiting = 0;
+ do {
+ continue_waiting = 0;
+ response = ptp_shm_interface_open();
+ if ((response == -1) && (errno == ENOENT)) {
+ time_spent_waiting = get_absolute_time_in_ns() - nqptp_start_waiting_time;
+ if (time_spent_waiting < 10000000000L) {
+ continue_waiting = 1;
+ usleep(50000);
+ }
+ }
+ } while (continue_waiting != 0);
+ */
+
+ response = ptp_shm_interface_open(); // look for NQPTP service
+
+ if ((response == -1) && (errno == ENOENT)) {
+ debug(1, "NQPTP service not found.");
+ // change auto to forced classic
+ if (config.service_type == APST_auto) {
+ config.service_type = APST_forced_classic;
+ } else if (config.service_type == APST_airplay2) {
+ die("The NQPTP service can not be found. NQPTP must be installed and running to provide "
+ "AirPlay 2 service.");
+ }
+ } else if ((response == -1) && (errno == EACCES)) {
+ die("Shairport Sync must have read access to the NQPTP shared memory file in /dev/shm/.");
+ } else if (response != 0) {
+ die("an error occurred accessing the NQPTP service.");
+ }
+
+ if (response == 0) {
+ // change "auto" to "airplay2"
+ if (config.service_type == APST_auto) {
+ config.service_type = APST_airplay2;
+ }
+ // check that the version of Shairport Sync and NQPTP match...
+ debug(1, "NQPTP service found.");
+ if (config.service_type == APST_airplay2) {
+ // now that we are using AirPlay 2, check NQPTP and SPS match...
+ int ptp_clock_version = ptp_get_clock_version();
+ if (ptp_clock_version == 0) {
+ die("The NQPTP service on this system, which is required for Shairport Sync to operate, "
+ "does "
+ "not seem to be initialised.");
+ } else if (ptp_clock_version < NQPTP_SHM_STRUCTURES_VERSION) {
+ die("The NQPTP service (SMI Version %d) on this system is too old for this version of "
+ "Shairport Sync, which requires SMI Version %d. Please update.",
+ ptp_clock_version, NQPTP_SHM_STRUCTURES_VERSION);
+ } else if (ptp_clock_version > NQPTP_SHM_STRUCTURES_VERSION) {
+ die("This version of Shairport Sync (SMI Version %d) is too old for the version of NQPTP "
+ "(SMI "
+ "Version %d) on this system. Please update.",
+ NQPTP_SHM_STRUCTURES_VERSION, ptp_clock_version);
+ }
+ }
+ }
+ }
+#else
+ // if we don't have AirPLay 2, the service type must be classic
+ config.service_type = APST_classic;
+#endif
+
+ config.service_name = service_name(raw_service_name);
#ifdef CONFIG_MQTT
// mqtt topic was not set. As we have the service name just now, set it
@@ -1905,9 +1971,6 @@ void exit_function() {
#endif
- if (conns)
- free(conns); // make sure the connections have been deleted first
-
if (config.service_name)
free(config.service_name);
@@ -1971,7 +2034,7 @@ void exit_function() {
if (config.appName)
free(config.appName);
- // probably should be freeing malloc'ed memory here, including strdup-created strings...
+ // probably should be freeing malloc'ed memory here, including strdup-created strings...
#ifdef CONFIG_LIBDAEMON
if (this_is_the_daemon_process) { // this is the daemon that is exiting
@@ -2016,12 +2079,12 @@ void termHandler(__attribute__((unused)) int k) {
void _display_config(const char *filename, const int linenumber, __attribute__((unused)) int argc,
__attribute__((unused)) char **argv) {
-
+
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-zero-length"
-#endif
-
+#endif
+
_inform(filename, linenumber, ">> Display Config Start.");
// see the man entry on popen
@@ -2200,11 +2263,11 @@ void _display_config(const char *filename, const int linenumber, __attribute__((
}
}
_inform(filename, linenumber, "");
- _inform(filename, linenumber, ">> Display Config End.");
-
- #if defined(__GNUC__) || defined(__clang__)
- #pragma GCC diagnostic pop
- #endif
+ _inform(filename, linenumber, ">> Display Config End.");
+
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
}
#define display_config(argc, argv) _display_config(__FILE__, __LINE__, argc, argv)
@@ -2328,7 +2391,6 @@ int main(int argc, char **argv) {
pid = getpid();
config.log_fd = -1;
- conns = NULL; // no connections active
#ifdef CONFIG_LIBDAEMON
daemon_set_verbosity(LOG_DEBUG);
@@ -2340,6 +2402,8 @@ int main(int argc, char **argv) {
debug(1, "adding the exit function");
atexit(exit_function);
+ config.service_type = APST_auto; // this may be changed by the settings...
+
// get a device id -- the first non-local MAC address
get_device_id((uint8_t *)&config.hw_addr, 6);
@@ -2389,12 +2453,6 @@ int main(int argc, char **argv) {
config.resync_threshold = 0.050; // default
config.tolerance = 0.002;
-#ifdef CONFIG_AIRPLAY_2
- config.port = 7000;
-#else
- config.port = 5000;
-#endif
-
#ifdef CONFIG_SOXR
config.packet_stuffing = ST_auto; // use soxr interpolation by default if support has been
// included and if the CPU is fast enough
@@ -2402,10 +2460,6 @@ int main(int argc, char **argv) {
config.packet_stuffing = ST_vernier; // you need to explicitly ask for "basic" (ST_basic)
#endif
- // char hostname[100];
- // gethostname(hostname, 100);
- // config.service_name = malloc(20 + 100);
- // snprintf(config.service_name, 20 + 100, "Shairport Sync on %s", hostname);
set_requested_connection_state_to_output(
1); // we expect to be able to connect to the output device
config.audio_backend_buffer_desired_length = 0.15; // seconds
@@ -2465,13 +2519,6 @@ int main(int argc, char **argv) {
// parse arguments into config -- needed to locate pid_dir
int audio_arg = parse_options(argc, argv);
- // mDNS supports maximum of 63-character names (we append 13).
- if (strlen(config.service_name) > 50) {
- warn("The service name \"%s\" is too long (max 50 characters) and has been truncated.",
- config.service_name);
- config.service_name[50] = '\0'; // truncate it and carry on...
- }
-
if (display_config_selected != 0) {
display_config(argc, argv);
if (argc == 2) {
@@ -2601,21 +2648,36 @@ int main(int argc, char **argv) {
#endif
#ifdef CONFIG_AIRPLAY_2
-
- if (has_fltp_capable_aac_decoder() == 0) {
- die("Shairport Sync can not run on this system. Run \"shairport-sync -h\" for more "
- "information.");
+ if (config.service_type == APST_airplay2) {
+ config.port = 7000;
+ } else {
+ config.port = 5000;
}
+#else
+ config.port = 5000;
+#endif
+#ifdef CONFIG_AIRPLAY_2
+ if (config.service_type == APST_airplay2) {
+ if (has_fltp_capable_aac_decoder() == 0) {
+ die("Shairport Sync can not run on this system. Run \"shairport-sync -h\" for more "
+ "information.");
+ }
+ }
uint64_t apf = config.airplay_features;
uint64_t apfh = config.airplay_features;
apfh = apfh >> 32;
uint32_t apf32 = apf;
uint32_t apfh32 = apfh;
- debug(1, "Startup in AirPlay 2 mode, with features 0x%" PRIx32 ",0x%" PRIx32 " on device \"%s\".",
- apf32, apfh32, config.airplay_device_id);
+ if (config.service_type == APST_airplay2) {
+ debug(1,
+ "Startup in AirPlay 2 mode, with features 0x%" PRIx32 ",0x%" PRIx32 " on device \"%s\".",
+ apf32, apfh32, config.airplay_device_id);
+ } else {
+ debug(1, "Startup in Classic AirPlay (aka \"AirPlay 1\") mode. (AirPlay2 build.)");
+ }
#else
- debug(1, "Startup in classic Airplay (aka \"AirPlay 1\") mode.");
+ debug(1, "Startup in Classic AirPlay (aka \"AirPlay 1\") mode.");
#endif
// control-c (SIGINT) cleanly
@@ -2949,9 +3011,9 @@ int main(int argc, char **argv) {
config.output_channel_map[i] = strdup(channel_id);
debug(2, "output channel %d is \"%s\".", i, config.output_channel_map[i]);
} else {
-
-
- warn("during channel mapping, \"%s\" was not recognised as a channel name -- as a result, output channel %d will be silent.",
+
+ warn("during channel mapping, \"%s\" was not recognised as a channel name -- as a "
+ "result, output channel %d will be silent.",
channel_id, i);
config.output_channel_map[i] = strdup("--");
}
@@ -3155,12 +3217,11 @@ int main(int argc, char **argv) {
if (soxr_time_check_thread != NULL) {
named_pthread_create(soxr_time_check_thread, NULL, &soxr_time_check, NULL, "soxr_checker");
} else {
- debug(1,"couldn't get memory to start the soxr_checker");
+ debug(1, "couldn't get memory to start the soxr_checker");
}
pthread_setcancelstate(oldState, NULL); // make this un-cancellable
-
}
-
+
#endif
#ifdef CONFIG_FFMPEG
@@ -3208,47 +3269,53 @@ int main(int argc, char **argv) {
#endif
// In AirPlay 2 mode, the AP1 prefix is the same as the device ID less the colons
+ // and has already been calculated.
+
// In AirPlay 1 mode, the AP1 prefix is calculated by hashing the service name.
-#ifndef CONFIG_AIRPLAY_2
- uint8_t ap_md5[16];
-
- // debug(1, "size of hw_addr is %u.", sizeof(config.hw_addr));
+#ifdef CONFIG_AIRPLAY_2
+ if (config.service_type != APST_airplay2) {
+#endif
+ uint8_t ap_md5[16];
+ // debug(1, "size of hw_addr is %u.", sizeof(config.hw_addr));
#ifdef CONFIG_OPENSSL
- EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
- EVP_DigestInit_ex(mdctx, EVP_md5(), NULL);
- EVP_DigestUpdate(mdctx, config.service_name, strlen(config.service_name));
- EVP_DigestUpdate(mdctx, config.hw_addr, sizeof(config.hw_addr));
- unsigned int md5_digest_len = EVP_MD_size(EVP_md5());
- EVP_DigestFinal_ex(mdctx, ap_md5, &md5_digest_len);
- EVP_MD_CTX_free(mdctx);
+ EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
+ EVP_DigestInit_ex(mdctx, EVP_md5(), NULL);
+ EVP_DigestUpdate(mdctx, config.service_name, strlen(config.service_name));
+ EVP_DigestUpdate(mdctx, config.hw_addr, sizeof(config.hw_addr));
+ unsigned int md5_digest_len = EVP_MD_size(EVP_md5());
+ EVP_DigestFinal_ex(mdctx, ap_md5, &md5_digest_len);
+ EVP_MD_CTX_free(mdctx);
#endif
#ifdef CONFIG_MBEDTLS
#if MBEDTLS_VERSION_MINOR >= 7
- mbedtls_md5_context tctx;
- mbedtls_md5_starts_ret(&tctx);
- mbedtls_md5_update_ret(&tctx, (unsigned char *)config.service_name, strlen(config.service_name));
- mbedtls_md5_update_ret(&tctx, (unsigned char *)config.hw_addr, sizeof(config.hw_addr));
- mbedtls_md5_finish_ret(&tctx, ap_md5);
+ mbedtls_md5_context tctx;
+ mbedtls_md5_starts_ret(&tctx);
+ mbedtls_md5_update_ret(&tctx, (unsigned char *)config.service_name,
+ strlen(config.service_name));
+ mbedtls_md5_update_ret(&tctx, (unsigned char *)config.hw_addr, sizeof(config.hw_addr));
+ mbedtls_md5_finish_ret(&tctx, ap_md5);
#else
- mbedtls_md5_context tctx;
- mbedtls_md5_starts(&tctx);
- mbedtls_md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name));
- mbedtls_md5_update(&tctx, (unsigned char *)config.hw_addr, sizeof(config.hw_addr));
- mbedtls_md5_finish(&tctx, ap_md5);
+ mbedtls_md5_context tctx;
+ mbedtls_md5_starts(&tctx);
+ mbedtls_md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name));
+ mbedtls_md5_update(&tctx, (unsigned char *)config.hw_addr, sizeof(config.hw_addr));
+ mbedtls_md5_finish(&tctx, ap_md5);
#endif
#endif
#ifdef CONFIG_POLARSSL
- md5_context tctx;
- md5_starts(&tctx);
- md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name));
- md5_update(&tctx, (unsigned char *)config.hw_addr, sizeof(config.hw_addr));
- md5_finish(&tctx, ap_md5);
+ md5_context tctx;
+ md5_starts(&tctx);
+ md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name));
+ md5_update(&tctx, (unsigned char *)config.hw_addr, sizeof(config.hw_addr));
+ md5_finish(&tctx, ap_md5);
#endif
+ memcpy(config.ap1_prefix, ap_md5, sizeof(config.ap1_prefix));
- memcpy(config.ap1_prefix, ap_md5, sizeof(config.ap1_prefix));
+#ifdef CONFIG_AIRPLAY_2
+ }
#endif
#ifdef CONFIG_METADATA
@@ -3275,55 +3342,6 @@ int main(int argc, char **argv) {
glib_worker_thread = g_thread_new("glib worker", glib_worker_thread_function, NULL);
#endif
-#ifdef CONFIG_AIRPLAY_2
-
- ptp_send_control_message_string(
- "T"); // send this message to get nqptp to create the named shm interface
- uint64_t nqptp_start_waiting_time = get_absolute_time_in_ns();
- int continue_waiting = 0;
- int response = 0;
- int64_t time_spent_waiting = 0;
- do {
- continue_waiting = 0;
- response = ptp_shm_interface_open();
- if ((response == -1) && (errno == ENOENT)) {
- time_spent_waiting = get_absolute_time_in_ns() - nqptp_start_waiting_time;
- if (time_spent_waiting < 10000000000L) {
- continue_waiting = 1;
- usleep(50000);
- }
- }
- } while (continue_waiting != 0);
-
- if ((response == -1) && (errno == ENOENT)) {
- die("Shairport Sync can not find the nqptp service on this system. Is nqptp installed and "
- "running?");
- } else if ((response == -1) && (errno == EACCES)) {
- die("Shairport Sync must have read access to the nqptp shared memory file in /dev/shm/.");
- } else if (response != 0) {
- die("an error occurred accessing the nqptp service.");
- }
-
- int ptp_clock_version = ptp_get_clock_version();
- if (ptp_clock_version == 0) {
- die("The nqptp service on this system, which is required for Shairport Sync to operate, does "
- "not seem to be initialised.");
- } else if (ptp_clock_version < NQPTP_SHM_STRUCTURES_VERSION) {
- die("The nqptp service (SMI Version %d) on this system is too old for this version of "
- "Shairport Sync, which requires SMI Version %d. Please update.",
- ptp_clock_version, NQPTP_SHM_STRUCTURES_VERSION);
- } else if (ptp_clock_version > NQPTP_SHM_STRUCTURES_VERSION) {
- die("This version of Shairport Sync (SMI Version %d) is too old for the version of nqptp (SMI "
- "Version %d) on this system. Please update.",
- NQPTP_SHM_STRUCTURES_VERSION, ptp_clock_version);
- }
-
- if (time_spent_waiting == 0)
- debug(1, "NQPTP is online.");
- else
- debug(1, "NQPTP came online after %.3f milliseconds.", 0.000001 * time_spent_waiting);
-#endif
-
#ifdef CONFIG_METADATA
send_ssnc_metadata('svna', config.service_name, strlen(config.service_name), 1);
#endif
@@ -3332,8 +3350,10 @@ int main(int argc, char **argv) {
convolver_pool_init(config.convolution_threads, 8); // 8 channels
#endif
activity_monitor_start();
- debug(3, "create an RTSP listener");
- named_pthread_create(&rtsp_listener_thread, NULL, &rtsp_listen_loop, NULL, "bonjour");
+ debug(4, "create an RTSP listener");
+ // note: the Avahi Threaded Poll thread will be named after whatever name you use here too, so
+ // you'll see two threads named "listener" or whatever...
+ named_pthread_create(&rtsp_listener_thread, NULL, &rtsp_listen_loop, NULL, "listener");
atexit(exit_rtsp_listener);
pthread_join(rtsp_listener_thread, NULL);
return 0;
diff --git a/tinysvcmdns.c b/tinysvcmdns.c
index aa2df5fd..7b01bd25 100644
--- a/tinysvcmdns.c
+++ b/tinysvcmdns.c
@@ -34,6 +34,7 @@
#include "tinysvcmdns.h"
#include "common.h"
+#include "utilities/network_utilities.h"
#define DEBUG_PRINTF(...) debug(3, __VA_ARGS__)
#define log_message(level, ...) \
@@ -1457,11 +1458,11 @@ int write_pipe(int s, char *buf, int len) {
#endif
}
-int close_pipe(int s) {
+int close_pipe(int *s) {
#ifdef _WIN32
- return closesocket(s);
+ return closesocket(*s);
#else
- return close(s);
+ return safe_socket_close(s);
#endif
}
@@ -1566,7 +1567,7 @@ void *main_loop(struct mdnsd *svr) {
free(pkt_buffer);
- close_pipe(svr->sockfd);
+ close_pipe(&svr->sockfd);
svr->stop_flag = 2;
return NULL;
@@ -1749,8 +1750,8 @@ void mdnsd_stop(struct mdnsd *s) {
while (s->stop_flag != 2)
select(0, NULL, NULL, NULL, &tv);
- close_pipe(s->notify_pipe[0]);
- close_pipe(s->notify_pipe[1]);
+ close_pipe(&s->notify_pipe[0]);
+ close_pipe(&s->notify_pipe[1]);
pthread_mutex_destroy(&s->data_lock);
rr_group_destroy(s->group);
diff --git a/utilities/buffered_read.c b/utilities/buffered_read.c
index 8e195914..ed1eb7b7 100644
--- a/utilities/buffered_read.c
+++ b/utilities/buffered_read.c
@@ -26,10 +26,11 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
-
- #include
- #include "buffered_read.h"
- #include "common.h"
+
+#include "buffered_read.h"
+#include "common.h"
+#include
+#include
ssize_t buffered_read(buffered_tcp_desc *descriptor, void *buf, size_t count,
size_t *bytes_remaining) {
@@ -110,8 +111,8 @@ void *buffered_tcp_reader(void *arg) {
int finished = 0;
int fd = accept(descriptor->sock_fd, (struct sockaddr *)&remote_addr, &addr_size);
// debug(1, "buffered_tcp_reader: the client has opened a buffered audio link.");
- intptr_t pfd = fd;
- pthread_cleanup_push(socket_cleanup, (void *)pfd);
+ // intptr_t pfd = fd;
+ pthread_cleanup_push(socket_cleanup, (void *)&fd);
do {
int have_time_to_sleep = 0;
@@ -188,7 +189,7 @@ void *buffered_tcp_reader(void *arg) {
// this will read a block of the size specified to the buffer
// and will return either with the block or on error
ssize_t read_sized_block(buffered_tcp_desc *descriptor, void *buf, size_t count,
- size_t *bytes_remaining) {
+ size_t *bytes_remaining) {
ssize_t response, nread;
size_t inbuf = 0; // bytes already in the buffer
int keep_trying = 1;
diff --git a/utilities/buffered_read.h b/utilities/buffered_read.h
index e0863159..5207898e 100644
--- a/utilities/buffered_read.h
+++ b/utilities/buffered_read.h
@@ -1,5 +1,7 @@
-#ifndef _BUFFERED_READ_H
-#define _BUFFERED_READ_H
+#pragma once
+
+#include
+#include
typedef struct {
int closed;
@@ -19,6 +21,4 @@ void *buffered_tcp_reader(void *arg);
// read the number of bytes specified by "count".
ssize_t read_sized_block(buffered_tcp_desc *descriptor, void *buf, size_t count,
- size_t *bytes_remaining);
-
-#endif // _BUFFERED_READ_H
+ size_t *bytes_remaining);
diff --git a/utilities/debug.c b/utilities/debug.c
index e99cf80c..50ffd37c 100644
--- a/utilities/debug.c
+++ b/utilities/debug.c
@@ -228,7 +228,6 @@ void _inform(const char *filename, const int linenumber, const char *format, ...
pthread_setcancelstate(oldState, NULL);
}
-
void _debug_print_buffer(const char *thefilename, const int linenumber, int level, void *ibuf,
size_t buf_len) {
@@ -238,78 +237,77 @@ void _debug_print_buffer(const char *thefilename, const int linenumber, int leve
char *vbuf = (char *)ibuf; // this to stop the compiler complaining about indexing through a void
unsigned int i;
unsigned int hexdump_cols = 16;
-
+
// 0x123456: \n
- const size_t buffer_size = 2 + 6 + 2 + hexdump_cols * 3 + hexdump_cols + 1;
+ const size_t buffer_size = 2 + 6 + 2 + hexdump_cols * 3 + hexdump_cols + 1;
char *buf = malloc(buffer_size);
if (buf) {
- //char *bufp = buf;
- // *buf = '\0';
+ // char *bufp = buf;
+ // *buf = '\0';
- // if (msg)
- // printf("%s", msg);
-
- #define ADDR_PREFIX_LEN 10 // "0x000000: "
+ // if (msg)
+ // printf("%s", msg);
- // each complete row
- for (i = 0; i < (buf_len / hexdump_cols); i++) {
- char *bufp = buf;
- *buf = '\0';
- snprintf(bufp, ADDR_PREFIX_LEN + 1,"0x%06x ", (i * hexdump_cols) & 0xFFFFFF);
- bufp += ADDR_PREFIX_LEN;
-
- unsigned int j;
- for (j = 0; j < hexdump_cols; j++) {
- snprintf(bufp, strlen("12 ") + 1, "%02x ", 0xFF & vbuf[i * hexdump_cols + j]);
- bufp += strlen("12 ");
- }
-
- for (j = 0; j < hexdump_cols; j++) {
- if (isprint(vbuf[i * hexdump_cols + j])) {
+#define ADDR_PREFIX_LEN 10 // "0x000000: "
+
+ // each complete row
+ for (i = 0; i < (buf_len / hexdump_cols); i++) {
+ char *bufp = buf;
+ *buf = '\0';
+ snprintf(bufp, ADDR_PREFIX_LEN + 1, "0x%06x ", (i * hexdump_cols) & 0xFFFFFF);
+ bufp += ADDR_PREFIX_LEN;
+
+ unsigned int j;
+ for (j = 0; j < hexdump_cols; j++) {
+ snprintf(bufp, strlen("12 ") + 1, "%02x ", 0xFF & vbuf[i * hexdump_cols + j]);
+ bufp += strlen("12 ");
+ }
+
+ for (j = 0; j < hexdump_cols; j++) {
+ if (isprint(vbuf[i * hexdump_cols + j])) {
*bufp = 0xFF & vbuf[i * hexdump_cols + j];
- } else {
+ } else {
*bufp = '.';
+ }
+ bufp++;
}
- bufp++;
- }
- *bufp = '\0';
+ *bufp = '\0';
_debug(thefilename, linenumber, level, "%s", buf);
- }
-
- size_t remaining = buf_len % hexdump_cols;
- if (remaining != 0) {
- size_t starting_offset = buf_len - remaining;
- char *bufp = buf;
- *buf = '\0';
- snprintf(bufp, ADDR_PREFIX_LEN + 1,"0x%06zx ", starting_offset & 0xFFFFFF);
- bufp += ADDR_PREFIX_LEN;
-
- unsigned int j;
- for (j = 0; j < hexdump_cols; j++) {
- if (j < remaining) {
- snprintf(bufp, strlen("12 ") + 1, "%02x ", 0xFF & vbuf[starting_offset + j]);
- } else {
- snprintf(bufp, strlen(" ") + 1, " "); // fille with blanks
- }
- bufp += strlen("12 ");
}
-
- for (j = 0; j < remaining; j++) {
- if (isprint(vbuf[i * hexdump_cols + j])) {
+
+ size_t remaining = buf_len % hexdump_cols;
+ if (remaining != 0) {
+ size_t starting_offset = buf_len - remaining;
+ char *bufp = buf;
+ *buf = '\0';
+ snprintf(bufp, ADDR_PREFIX_LEN + 1, "0x%06zx ", starting_offset & 0xFFFFFF);
+ bufp += ADDR_PREFIX_LEN;
+
+ unsigned int j;
+ for (j = 0; j < hexdump_cols; j++) {
+ if (j < remaining) {
+ snprintf(bufp, strlen("12 ") + 1, "%02x ", 0xFF & vbuf[starting_offset + j]);
+ } else {
+ snprintf(bufp, strlen(" ") + 1, " "); // fille with blanks
+ }
+ bufp += strlen("12 ");
+ }
+
+ for (j = 0; j < remaining; j++) {
+ if (isprint(vbuf[i * hexdump_cols + j])) {
*bufp = 0xFF & vbuf[starting_offset + j];
- } else {
+ } else {
*bufp = '.';
+ }
+ bufp++;
}
- bufp++;
- }
- *bufp = '\0';
+ *bufp = '\0';
_debug(thefilename, linenumber, level, "%s", buf);
- }
- free(buf);
+ }
+ free(buf);
}
}
-
/*
void _debug_print_buffer(const char *thefilename, const int linenumber, int level, void *vbuf,
size_t buf_len) {
diff --git a/utilities/debug.h b/utilities/debug.h
index e29e8066..8491ed74 100644
--- a/utilities/debug.h
+++ b/utilities/debug.h
@@ -58,12 +58,17 @@ EXTERNC void set_show_file_and_line(int setting);
#endif
// Function declarations with printf-style format checking
-EXTERNC void _die(const char *filename, const int linenumber, const char *format, ...) PRINTF_LIKE(3,4);
-EXTERNC void _warn(const char *filename, const int linenumber, const char *format, ...) PRINTF_LIKE(3,4);
-EXTERNC void _inform(const char *filename, const int linenumber, const char *format, ...) PRINTF_LIKE(3,4);
-EXTERNC void _debug(const char *filename, const int linenumber, int level, const char *format, ...) PRINTF_LIKE(4,5);
-EXTERNC void _debug_print_buffer(const char *thefilename, const int linenumber, int level, void *buf,
- size_t buf_len); // not printf-style, no change needed
+EXTERNC void _die(const char *filename, const int linenumber, const char *format, ...)
+ PRINTF_LIKE(3, 4);
+EXTERNC void _warn(const char *filename, const int linenumber, const char *format, ...)
+ PRINTF_LIKE(3, 4);
+EXTERNC void _inform(const char *filename, const int linenumber, const char *format, ...)
+ PRINTF_LIKE(3, 4);
+EXTERNC void _debug(const char *filename, const int linenumber, int level, const char *format, ...)
+ PRINTF_LIKE(4, 5);
+EXTERNC void _debug_print_buffer(const char *thefilename, const int linenumber, int level,
+ void *buf,
+ size_t buf_len); // not printf-style, no change needed
#define die(...) _die(__FILE__, __LINE__, __VA_ARGS__)
#define debug(...) _debug(__FILE__, __LINE__, __VA_ARGS__)
#define warn(...) _warn(__FILE__, __LINE__, __VA_ARGS__)
diff --git a/utilities/generate_device_uuid.c b/utilities/generate_device_uuid.c
index 51dcef4e..7ce6ec05 100644
--- a/utilities/generate_device_uuid.c
+++ b/utilities/generate_device_uuid.c
@@ -1,9 +1,6 @@
/*
* Generate a device-related UUID. This file is part of Shairport Sync
* Copyright (c) Mike Brady 2026
-
- * Modifications, including those associated with audio synchronization, multithreading and
- * metadata handling copyright (c) Mike Brady 2014--2025
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -26,23 +23,23 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
-
+
#include
#include
#include
-
-#include "generate_device_uuid.h"
+
#include "definitions.h"
+#include "generate_device_uuid.h"
// user is responsible for deallocating returned string
char *generate_device_uuid(const char *device_id) {
- uuid_t namespace_uuid;
- uuid_t derived_uuid;
+ uuid_t namespace_uuid;
+ uuid_t derived_uuid;
- uuid_parse(SHAIRPORT_SYNC_DEVICE_NAMESPACE, namespace_uuid);
- uuid_generate_sha1(derived_uuid, namespace_uuid, device_id, strlen(device_id));
+ uuid_parse(SHAIRPORT_SYNC_DEVICE_NAMESPACE, namespace_uuid);
+ uuid_generate_sha1(derived_uuid, namespace_uuid, device_id, strlen(device_id));
- char *uuid = malloc(UUID_STR_LEN + 1);
- uuid_unparse_lower(derived_uuid, uuid);
- return uuid;
+ char *uuid = malloc(UUID_STR_LEN + 1);
+ uuid_unparse_lower(derived_uuid, uuid);
+ return uuid;
}
\ No newline at end of file
diff --git a/utilities/generate_random_uuid.c b/utilities/generate_random_uuid.c
index 848345ff..82e73252 100644
--- a/utilities/generate_random_uuid.c
+++ b/utilities/generate_random_uuid.c
@@ -1,9 +1,6 @@
/*
* Generate a random UUID. This file is part of Shairport Sync
* Copyright (c) Mike Brady 2026
-
- * Modifications, including those associated with audio synchronization, multithreading and
- * metadata handling copyright (c) Mike Brady 2014--2025
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -26,10 +23,10 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
-
+
#include
#include
-
+
#include "generate_random_uuid.h"
// user is responsible for deallocating returned string
diff --git a/utilities/mod23.c b/utilities/mod23.c
index d439ddaa..7d720fcc 100644
--- a/utilities/mod23.c
+++ b/utilities/mod23.c
@@ -1,9 +1,6 @@
/*
* 23-bit modular arithmetic. This file is part of Shairport Sync
* Copyright (c) Mike Brady 2025
-
- * Modifications, including those associated with audio synchronization, multithreading and
- * metadata handling copyright (c) Mike Brady 2014--2025
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -26,9 +23,9 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
-
- #include
- #include "mod23.h"
+
+#include "mod23.h"
+#include
#define MOD_23BIT 0x7FFFFF // 2^23 - 1
diff --git a/utilities/mod23.h b/utilities/mod23.h
index ecdd9606..7998070a 100644
--- a/utilities/mod23.h
+++ b/utilities/mod23.h
@@ -1,6 +1,5 @@
-#ifndef _MOD23_H
-#define _MOD23_H
+#pragma once
+
+#include
int32_t a_minus_b_mod23(uint32_t a, uint32_t b);
-
-#endif // _MOD23_H
diff --git a/utilities/network_utilities.c b/utilities/network_utilities.c
index edb0741f..096c31f9 100644
--- a/utilities/network_utilities.c
+++ b/utilities/network_utilities.c
@@ -1,6 +1,6 @@
/*
* Network Utilities. This file is part of Shairport Sync.
- * Copyright (c) Mike Brady 2014--2025
+ * Copyright (c) Mike Brady 2026
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -24,24 +24,47 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
-#include
-#include
#include "network_utilities.h"
+#include "common.h"
+#include
+#include
+#include
-int eintr_checked_accept(int sockfd, struct sockaddr *addr,
- socklen_t *addrlen) {
+int eintr_checked_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
int response;
do {
- response = accept(sockfd, addr, addrlen);
-
+ response = accept(sockfd, addr, addrlen);
+
if (response == -1) {
char errorstring[1024];
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
- debug(1,
- "error %d accept()ing a socket %d: \"%s\". (Note: error %d will be ignored.)",
- errno, sockfd, errorstring, EINTR);
+ debug(1, "error %d accept()ing a socket %d: \"%s\". (Note: error %d will be ignored.)", errno,
+ sockfd, errorstring, EINTR);
}
-
- } while((response == -1) && (errno == EINTR));
+
+ } while ((response == -1) && (errno == EINTR));
return response;
+}
+
+pthread_mutex_t safe_socket_lock = PTHREAD_MUTEX_INITIALIZER;
+
+int _safe_socket_close(const char *filename, const int linenumber, int *sockfd) {
+ int result = 0;
+ int oldstate;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
+ debug_mutex_lock(&safe_socket_lock, 1000000, 1);
+ if (*sockfd == 0) {
+ _debug(filename, linenumber, 1, "_safe_socket_close: socket is zero!");
+ }
+ if ((*sockfd != -1) && (*sockfd != 0)) {
+ _debug(filename, linenumber, 4, "_safe_socket_close: closing socket %d.", *sockfd);
+ result = close(*sockfd);
+ if (result == 0)
+ *sockfd = -1;
+ } else {
+ _debug(filename, linenumber, 1, "_safe_socket_close: socket already closed!");
+ }
+ debug_mutex_unlock(&safe_socket_lock, 4);
+ pthread_setcancelstate(oldstate, NULL);
+ return result;
}
\ No newline at end of file
diff --git a/utilities/network_utilities.h b/utilities/network_utilities.h
index 001a1c95..70580830 100644
--- a/utilities/network_utilities.h
+++ b/utilities/network_utilities.h
@@ -1,10 +1,10 @@
-#ifndef _NETWORK_UTILITIES_H
-#define _NETWORK_UTILITIES_H
+#pragma once
#include
#define restrict
+int eintr_checked_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
-int eintr_checked_accept(int sockfd, struct sockaddr *addr,
- socklen_t *addrlen);
-#endif // _NETWORK_UTILITIES_H
+int _safe_socket_close(const char *filename, const int linenumber, int *sockfd);
+
+#define safe_socket_close(sockfd) _safe_socket_close(__FILE__, __LINE__, sockfd)
diff --git a/utilities/rtsp_message_utilities.c b/utilities/rtsp_message_utilities.c
index b3415fc4..015376fe 100644
--- a/utilities/rtsp_message_utilities.c
+++ b/utilities/rtsp_message_utilities.c
@@ -1,9 +1,35 @@
+/*
+ * RTSP Message Utilities. This file is part of Shairport Sync.
+ * Copyright (c) Mike Brady 2026
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "rtsp_message_utilities.h"
+#include "../common.h"
+#include "../rtsp.h"
#include
#include
#include
-#include "rtsp_message_utilities.h"
-#include "../rtsp.h"
-#include "../common.h"
#ifdef CONFIG_AIRPLAY_2
#include
@@ -80,15 +106,16 @@ char *msg_get_header(rtsp_message *msg, char *name) {
return NULL;
}
-void _debug_print_msg_headers(rtsp_conn_info *conn, const char *filename, const int linenumber, int level,
- rtsp_message *msg) {
+void _debug_print_msg_headers(rtsp_conn_info *conn, const char *filename, const int linenumber,
+ int level, rtsp_message *msg) {
unsigned int i;
if (conn != NULL) {
if (msg->respcode != 0)
- _debug(filename, linenumber, level, "Connection: %d, Response Code: %d.", conn->connection_number, msg->respcode);
+ _debug(filename, linenumber, level, "Connection: %d, Response Code: %d.",
+ conn->connection_number, msg->respcode);
for (i = 0; i < msg->nheaders; i++) {
- _debug(filename, linenumber, level, "Connection: %d, Type: \"%s\", content: \"%s\"", conn->connection_number, msg->name[i],
- msg->value[i]);
+ _debug(filename, linenumber, level, "Connection: %d, Type: \"%s\", content: \"%s\"",
+ conn->connection_number, msg->name[i], msg->value[i]);
}
} else {
if (msg->respcode != 0)
@@ -243,8 +270,8 @@ char *rtsp_plist_content(rtsp_message *message) {
#endif
-void _debug_log_rtsp_message(rtsp_conn_info *conn, const char *filename, const int linenumber, int level, char *prompt,
- rtsp_message *message) {
+void _debug_log_rtsp_message(rtsp_conn_info *conn, const char *filename, const int linenumber,
+ int level, char *prompt, rtsp_message *message) {
if (level > debug_level())
return;
if ((prompt) && (*prompt != '\0')) // okay to pass NULL or an empty list...
@@ -253,13 +280,13 @@ void _debug_log_rtsp_message(rtsp_conn_info *conn, const char *filename, const i
#ifdef CONFIG_AIRPLAY_2
char *plist_content = rtsp_plist_content(message);
if (plist_content) {
- _debug(filename, linenumber, level, " Content length: %u. Content Plist (as XML):\n--\n%s--", message->contentlength, plist_content);
+ _debug(filename, linenumber, level, " Content length: %u. Content Plist (as XML):\n--\n%s--",
+ message->contentlength, plist_content);
free(plist_content);
} else
#endif
{
- _debug(filename, linenumber, level, " Content length: %u.",
- message->contentlength);
+ _debug(filename, linenumber, level, " Content length: %u.", message->contentlength);
if (message->contentlength > 0) {
_debug_print_buffer(filename, linenumber, level, message->content, message->contentlength);
}
diff --git a/utilities/rtsp_message_utilities.h b/utilities/rtsp_message_utilities.h
index 3a42c031..ce858440 100644
--- a/utilities/rtsp_message_utilities.h
+++ b/utilities/rtsp_message_utilities.h
@@ -8,8 +8,8 @@ int msg_handle_line(rtsp_message **pmsg, char *line);
int msg_add_header(rtsp_message *msg, char *name, char *value);
char *msg_get_header(rtsp_message *msg, char *name);
-void _debug_log_rtsp_message(rtsp_conn_info *conn, const char *filename, const int linenumber, int level, char *prompt,
- rtsp_message *message);
+void _debug_log_rtsp_message(rtsp_conn_info *conn, const char *filename, const int linenumber,
+ int level, char *prompt, rtsp_message *message);
#define debug_log_rtsp_message_conn(conn, level, prompt, message) \
_debug_log_rtsp_message(conn, __FILE__, __LINE__, level, prompt, message)
@@ -17,16 +17,14 @@ void _debug_log_rtsp_message(rtsp_conn_info *conn, const char *filename, const i
#define debug_log_rtsp_message(level, prompt, message) \
_debug_log_rtsp_message(NULL, __FILE__, __LINE__, level, prompt, message)
-
-void _debug_print_msg_headers(rtsp_conn_info *conn, const char *filename, const int linenumber, int level,
- rtsp_message *msg);
+void _debug_print_msg_headers(rtsp_conn_info *conn, const char *filename, const int linenumber,
+ int level, rtsp_message *msg);
#define debug_print_msg_headers(level, message) \
_debug_print_msg_headers(NULL, __FILE__, __LINE__, level, message)
#define debug_print_msg_headers_conn(level, message) \
_debug_print_msg_headers(conn, __FILE__, __LINE__, level, message)
-
#ifdef CONFIG_AIRPLAY_2
int rtsp_message_contains_plist(rtsp_message *message);
plist_t plist_from_rtsp_content(rtsp_message *message);
diff --git a/utilities/string_utilities.c b/utilities/string_utilities.c
new file mode 100644
index 00000000..b9183ac8
--- /dev/null
+++ b/utilities/string_utilities.c
@@ -0,0 +1,194 @@
+/*
+ * Some character string utilities. This file is part of Shairport Sync
+ * Copyright (c) Mike Brady 2026
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include
+#include
+#include
+#include
+
+#include "common.h"
+#include "string_utilities.h"
+
+// mDNS supports maximum of 63-character names (we prepend 13, so only 50 left).
+#define MAX_AIRPLAY_SERVICE_NAME_LENGTH 50
+
+/* from
+ * http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722
+ * with thanks.
+ */
+
+char *str_replace(const char *string, const char *substr, const char *replacement) {
+ char *tok = NULL;
+ char *newstr = NULL;
+ char *oldstr = NULL;
+ char *head = NULL;
+
+ /* if either substr or replacement is NULL, duplicate string a let caller handle it */
+ if (substr == NULL || replacement == NULL)
+ return strdup(string);
+ newstr = strdup(string);
+ head = newstr;
+ if (head) {
+ while ((tok = strstr(head, substr))) {
+ oldstr = newstr;
+ newstr = malloc(strlen(oldstr) - strlen(substr) + strlen(replacement) + 1);
+ /*failed to alloc mem, free old string and return NULL */
+ if (newstr == NULL) {
+ free(oldstr);
+ return NULL;
+ }
+ memcpy(newstr, oldstr, tok - oldstr);
+ memcpy(newstr + (tok - oldstr), replacement, strlen(replacement));
+ memcpy(newstr + (tok - oldstr) + strlen(replacement), tok + strlen(substr),
+ strlen(oldstr) - strlen(substr) - (tok - oldstr));
+ memset(newstr + strlen(oldstr) - strlen(substr) + strlen(replacement), 0, 1);
+ /* move back head right after the last replacement */
+ head = newstr + (tok - oldstr) + strlen(replacement);
+ free(oldstr);
+ }
+ } else {
+ die("failed to allocate memory in str_replace.");
+ }
+ return newstr;
+}
+
+/*
+ * append_truncated - Append `suffix` to `base`, returning a newly malloc'd
+ * string. If the combined result would exceed `limit` bytes (excluding the
+ * NUL terminator), the base is truncated so that the output is:
+ *
+ * ...
+ *
+ * The returned string is always NUL-terminated and never exceeds `limit`
+ * bytes of content. The caller is responsible for free()'ing it.
+ *
+ * Parameters:
+ * base - the primary string
+ * suffix - the string to append
+ * limit - maximum byte count of the resulting string (excl. NUL)
+ *
+ * Returns:
+ * A newly malloc'd string on success, or NULL on failure (limit too small
+ * to fit "..." + suffix, or malloc failure).
+ */
+char *append_truncated(const char *base, const char *suffix, size_t limit) {
+
+ const char *ellipsis = "...";
+ size_t ellipsis_len = strlen(ellipsis);
+ size_t suffix_len = strlen(suffix);
+ size_t base_len = strlen(base);
+ size_t combined_len = base_len + suffix_len;
+ size_t result_len;
+ size_t head_len;
+ char *out;
+
+ if (combined_len <= limit) {
+ /* Fits without truncation. */
+ result_len = combined_len;
+ out = malloc(result_len + 1);
+ if (!out)
+ return NULL;
+ memcpy(out, base, base_len);
+ memcpy(out + base_len, suffix, suffix_len + 1); /* +1 for NUL */
+ return out;
+ }
+
+ /* Not enough room to fit even "..." + suffix. */
+ if (limit < ellipsis_len + suffix_len)
+ return NULL;
+
+ head_len = limit - ellipsis_len - suffix_len;
+
+ /* Respect UTF-8: don't cut in the middle of a multibyte sequence.
+ * Walk back from head_len until we're at a character boundary
+ * (i.e. a byte that is not a UTF-8 continuation byte 0x80–0xBF). */
+ while (head_len > 0 && (base[head_len] & 0xC0) == 0x80)
+ head_len--;
+
+ result_len = head_len + ellipsis_len + suffix_len;
+ out = malloc(result_len + 1);
+ if (!out)
+ return NULL;
+
+ char *p = out;
+ memcpy(p, base, head_len);
+ p += head_len;
+ memcpy(p, ellipsis, ellipsis_len);
+ p += ellipsis_len;
+ memcpy(p, suffix, suffix_len);
+ p += suffix_len;
+ *p = '\0';
+
+ return out;
+}
+
+char *service_name(const char *raw_service_name) {
+ char *response = NULL;
+ // now, do the substitutions in the service name
+ char hostname[256];
+ gethostname(hostname, sizeof(hostname));
+ // strip off a terminating ., e.g. .local from the hostname
+ char *last_dot = strrchr(hostname, '.');
+ if (last_dot != NULL)
+ *last_dot = '\0';
+
+ char *i0;
+ if (raw_service_name == NULL)
+ i0 = strdup("%H"); // default
+ else
+ i0 = strdup(raw_service_name); // this is the string provided in the configuration or on the
+ // command line.
+ // here, do the substitutions for %h, %H, %v and %V
+ char *i1 = str_replace(i0, "%h", hostname);
+ if ((hostname[0] >= 'a') && (hostname[0] <= 'z'))
+ hostname[0] = hostname[0] - 0x20; // convert a lowercase first letter into a capital letter
+ char *i2 = str_replace(i1, "%H", hostname);
+ char *i3 = str_replace(i2, "%v", PACKAGE_VERSION);
+ char *vs = get_version_string();
+ char *i4 = str_replace(i3, "%V", vs); // service name complete
+ // now, we may need to add "(Classic)" and/or truncate it to MAX_AIRPLAY_SERVICE_NAME_LENGTH
+ // characters.
+#ifdef CONFIG_AIRPLAY_2
+ if ((raw_service_name == NULL) && (config.service_type == APST_forced_classic)) {
+ response = append_truncated(
+ i4, " (Classic)", MAX_AIRPLAY_SERVICE_NAME_LENGTH); // append "(Classic)" to default service
+ // name if forced to Classic
+ } else {
+#endif
+ response = append_truncated(
+ i4, "",
+ MAX_AIRPLAY_SERVICE_NAME_LENGTH); // make sure it doesn't exceed the max length: 63 - 13
+#ifdef CONFIG_AIRPLAY_2
+ }
+#endif
+ free(i0);
+ free(i1);
+ free(i2);
+ free(i3);
+ free(i4);
+ free(vs);
+ return response;
+}
\ No newline at end of file
diff --git a/utilities/string_utilities.h b/utilities/string_utilities.h
new file mode 100644
index 00000000..094da579
--- /dev/null
+++ b/utilities/string_utilities.h
@@ -0,0 +1,8 @@
+#pragma once
+
+/* from
+ * http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722
+ */
+char *str_replace(const char *string, const char *substr, const char *replacement);
+
+char *service_name(const char *raw_service_name);
\ No newline at end of file
diff --git a/utilities/structured_buffer.c b/utilities/structured_buffer.c
index 668fdf40..2d69ad81 100644
--- a/utilities/structured_buffer.c
+++ b/utilities/structured_buffer.c
@@ -1,9 +1,6 @@
/*
* Structured Buffer. This file is part of Shairport Sync
* Copyright (c) Mike Brady 2025
-
- * Modifications, including those associated with audio synchronization, multithreading and
- * metadata handling copyright (c) Mike Brady 2014--2025
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -27,12 +24,11 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
+#include
+#include
+#include
#include
#include
-#include
-#include
-#include
-
typedef struct {
char *buf;