Enhancements
Enable the AirPlay 2 build to operate an optional classic-AirPlay-only service or to gracefully degrade to classic AirPlay, as follows:
1. Add a new command-line option '-—service-type=<type>' and an equivalent configuration entry 'service_type = "<type>"' in the 'general' section of the configuration file,
where <type> can be "auto", "classic" or "airplay2":
1. "auto" (default) means that the service will be AirPlay 2 if NQPTP is running. If NQPTP is not running, classic AirPlay service will be provided instead, and in that case,
"(Classic)" will be appended to the default AirPlay service name visible to AirPlay clients like Apple Music, for example "RaspberryPi3B (Classic)".
2. "classic" means the service will be classic AirPlay (aka AirPlay 1).
3. "airplay2" means the service will be the modern AirPlay 2. In this case, as distinct from "auto", if NQPTP is not running, Shairport Sync will log an error and terminate.
2. In the systemd service file, NQPTP is now a "Want" rather than a "Require". If it's present, then it will be launched before Shairport Sync. If it's absent, Shairport Sync will launch anyway.
3. Improve the delivery of input format changes and emit 'sdsc' metadata when changes occur.
4. Emit new format information in the log if statistics is enabled.
Docker Changes
1. Support for linux/arm/v6 has been dropped, as Docker is no longer supported.
2. NQPTP is not started in the AirPlay 2 Docker image if '--service-type=classic' or '--service-type=airplay1' is in the command line options at the end of the docker run command.
The purpose is to ensure that ports 319 and 320 are left alone when the AirPlay 2 image is set to provide Classic service only.
Note that setting the configuration file 'service_type' to 'classic' will not prevent NQPTP from starting up -- you must use the command line option.
3. A new "dev" target has been added. It is a large image containing the custom-built FFmpeg library, NQPTP, Avahi and D-Bus along with the Shairport Sync source and
all necessary development tools. When started, Avahi, D-Bus and NQPTP are all installed and running. The bash shell has also been added and is entered.
Stability Improvements
Reorganise session preemption to fully terminate the existing session before starting a new one.
Don't delay closing the event port to wait for it to be closed at the client end.
Add a safe_socket_close() function to ensure sockets are fully closed. Use -1 to designate closed rather than 0, to prevent attempts to reclose sockets, causing mayhem.
Re-order FFmpeg decommissioning during teardown.
This commit is contained in:
+348
-328
@@ -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 <gcrypt.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <sodium.h>
|
||||
#include <uuid/uuid.h>
|
||||
#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 .<anything>, 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;
|
||||
|
||||
Reference in New Issue
Block a user