]>
Commit | Line | Data |
---|---|---|
cf8401db JL |
1 | /* |
2 | * Shairport, an Apple Airplay receiver | |
3 | * Copyright (c) James Laird 2013 | |
4 | * All rights reserved. | |
28f54af2 | 5 | * Modifications and additions (c) Mike Brady 2014--2023 |
cf8401db JL |
6 | * |
7 | * Permission is hereby granted, free of charge, to any person | |
8 | * obtaining a copy of this software and associated documentation | |
9 | * files (the "Software"), to deal in the Software without | |
10 | * restriction, including without limitation the rights to use, | |
11 | * copy, modify, merge, publish, distribute, sublicense, and/or | |
12 | * sell copies of the Software, and to permit persons to whom the | |
13 | * Software is furnished to do so, subject to the following conditions: | |
14 | * | |
15 | * The above copyright notice and this permission notice shall be | |
16 | * included in all copies or substantial portions of the Software. | |
17 | * | |
18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
19 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | |
20 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
21 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |
22 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
23 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
24 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
25 | * OTHER DEALINGS IN THE SOFTWARE. | |
26 | */ | |
27 | ||
d89e516c | 28 | #include <errno.h> |
5249a6e1 | 29 | #include <fcntl.h> |
b70505fd | 30 | #include <getopt.h> |
064bd293 MB |
31 | #include <libconfig.h> |
32 | #include <libgen.h> | |
33 | #include <memory.h> | |
c8b0be30 | 34 | #include <net/if.h> |
064bd293 | 35 | #include <popt.h> |
90e97bc1 MB |
36 | #include <stdio.h> |
37 | #include <stdlib.h> | |
ccc4fe09 | 38 | #include <sys/socket.h> |
064bd293 | 39 | #include <sys/stat.h> |
ccc4fe09 | 40 | #include <sys/types.h> |
064bd293 MB |
41 | #include <sys/wait.h> |
42 | #include <unistd.h> | |
770b81af | 43 | |
90e97bc1 MB |
44 | #include "config.h" |
45 | ||
3a02b79a | 46 | #ifdef CONFIG_AIRPLAY_2 |
a382ac66 | 47 | #include "ptp-utilities.h" |
0cc97d50 | 48 | #include <gcrypt.h> |
0dc34a46 | 49 | #include <libavcodec/avcodec.h> |
3a02b79a MB |
50 | #include <sodium.h> |
51 | #include <uuid/uuid.h> | |
52 | #endif | |
53 | ||
c9b3d2a2 | 54 | #ifdef CONFIG_MBEDTLS |
62aaf074 | 55 | #include <mbedtls/md5.h> |
3d1ee2e0 | 56 | #include <mbedtls/version.h> |
62aaf074 MB |
57 | #endif |
58 | ||
c9b3d2a2 | 59 | #ifdef CONFIG_POLARSSL |
90e97bc1 MB |
60 | #include <polarssl/md5.h> |
61 | #endif | |
62 | ||
c9b3d2a2 | 63 | #ifdef CONFIG_OPENSSL |
78dbfe6a | 64 | #include <openssl/evp.h> |
dabfee5b | 65 | #include <openssl/md5.h> |
90e97bc1 MB |
66 | #endif |
67 | ||
c9b3d2a2 | 68 | #if defined(CONFIG_DBUS_INTERFACE) |
1e07e1e0 MB |
69 | #include <glib.h> |
70 | #endif | |
71 | ||
2e442853 | 72 | #include "activity_monitor.h" |
ca562872 | 73 | #include "audio.h" |
c8c70b60 | 74 | #include "common.h" |
c8c70b60 MB |
75 | #include "rtp.h" |
76 | #include "rtsp.h" | |
77 | ||
69642bb7 | 78 | #if defined(CONFIG_DACP_CLIENT) |
88c55066 MB |
79 | #include "dacp.h" |
80 | #endif | |
81 | ||
69642bb7 | 82 | #if defined(CONFIG_METADATA_HUB) |
0801290a | 83 | #include "metadata_hub.h" |
88c55066 MB |
84 | #endif |
85 | ||
c9b3d2a2 | 86 | #ifdef CONFIG_DBUS_INTERFACE |
df7c48f0 | 87 | #include "dbus-service.h" |
1e07e1e0 MB |
88 | #endif |
89 | ||
c9b3d2a2 | 90 | #ifdef CONFIG_MQTT |
02694948 TZ |
91 | #include "mqtt.h" |
92 | #endif | |
93 | ||
c9b3d2a2 | 94 | #ifdef CONFIG_MPRIS_INTERFACE |
df7c48f0 | 95 | #include "mpris-service.h" |
74f41a17 MB |
96 | #endif |
97 | ||
e76cfa69 | 98 | #ifdef CONFIG_LIBDAEMON |
064bd293 | 99 | #include <libdaemon/dexec.h> |
d89e516c | 100 | #include <libdaemon/dfork.h> |
d89e516c MB |
101 | #include <libdaemon/dlog.h> |
102 | #include <libdaemon/dpid.h> | |
064bd293 | 103 | #include <libdaemon/dsignal.h> |
e76cfa69 MB |
104 | #else |
105 | #include <syslog.h> | |
106 | #endif | |
d89e516c | 107 | |
ca8acb4a | 108 | #ifdef CONFIG_SOXR |
ca8acb4a | 109 | #include <math.h> |
c8b0be30 | 110 | #include <soxr.h> |
ca8acb4a MB |
111 | #endif |
112 | ||
7b9cd28e YP |
113 | #ifdef CONFIG_CONVOLUTION |
114 | #include <FFTConvolver/convolver.h> | |
115 | #endif | |
116 | ||
593c507d | 117 | pid_t pid; |
b413a29d | 118 | #ifdef CONFIG_LIBDAEMON |
185000d0 | 119 | int this_is_the_daemon_process = 0; |
593c507d MB |
120 | #endif |
121 | ||
e925e196 MB |
122 | #ifndef UUID_STR_LEN |
123 | #define UUID_STR_LEN 36 | |
124 | #endif | |
125 | ||
3b91065c AL |
126 | #define strnull(s) ((s) ? (s) : "(null)") |
127 | ||
3a02b79a MB |
128 | pthread_t rtsp_listener_thread; |
129 | ||
6d088ac4 MB |
130 | int killOption = 0; |
131 | int daemonisewith = 0; | |
132 | int daemonisewithout = 0; | |
0dc34a46 | 133 | int log_to_syslog_selected = 0; |
63e0dfda MB |
134 | #ifdef CONFIG_LIBDAEMON |
135 | int log_to_default = 1; // needed if libdaemon used | |
136 | #endif | |
3cbf7739 | 137 | int display_config_selected = 0; |
0dc34a46 | 138 | int log_to_syslog_select_is_first_command_line_argument = 0; |
6d088ac4 | 139 | |
064bd293 | 140 | char configuration_file_path[4096 + 1]; |
3cbf7739 | 141 | char *config_file_real_path = NULL; |
28b4e6dd | 142 | |
ca562872 MB |
143 | char first_backend_name[256]; |
144 | ||
90e97bc1 | 145 | void print_version(void) { |
064bd293 | 146 | char *version_string = get_version_string(); |
6f93f55d MB |
147 | if (version_string) { |
148 | printf("%s\n", version_string); | |
149 | free(version_string); | |
150 | } else { | |
064bd293 | 151 | debug(1, "Can't print version string!"); |
6f93f55d | 152 | } |
90e97bc1 | 153 | } |
ed629037 MB |
154 | |
155 | #ifdef CONFIG_AIRPLAY_2 | |
1e2a933e | 156 | int has_fltp_capable_aac_decoder(void) { |
38c43f07 | 157 | |
1e2a933e | 158 | // return 1 if the AAC decoder advertises fltp decoding capability, which |
ed629037 MB |
159 | // is needed for decoding Buffered Audio streams |
160 | int has_capability = 0; | |
161 | const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_AAC); | |
162 | if (codec != NULL) { | |
4705be9b MB |
163 | const enum AVSampleFormat *p = codec->sample_fmts; |
164 | if (p != NULL) { | |
165 | while ((has_capability == 0) && (*p != AV_SAMPLE_FMT_NONE)) { | |
fd880056 | 166 | if (*p == AV_SAMPLE_FMT_FLTP) |
69cd95e9 | 167 | has_capability = 1; |
4705be9b | 168 | p++; |
69cd95e9 | 169 | } |
6088c244 | 170 | } |
ed629037 | 171 | } |
69cd95e9 | 172 | return has_capability; |
ed629037 MB |
173 | } |
174 | #endif | |
175 | ||
ca8acb4a | 176 | #ifdef CONFIG_SOXR |
c8b0be30 | 177 | pthread_t soxr_time_check_thread; |
62fca43f | 178 | int soxr_time_check_thread_started = 0; |
c8b0be30 | 179 | void *soxr_time_check(__attribute__((unused)) void *arg) { |
ca8acb4a | 180 | const int buffer_length = 352; |
c8b0be30 MB |
181 | int32_t inbuffer[buffer_length * 2]; |
182 | int32_t outbuffer[(buffer_length + 1) * 2]; | |
183 | ||
184 | // int32_t *outbuffer = (int32_t*)malloc((buffer_length+1)*2*sizeof(int32_t)); | |
185 | // int32_t *inbuffer = (int32_t*)malloc((buffer_length)*2*sizeof(int32_t)); | |
186 | ||
ca8acb4a | 187 | // generate a sample signal |
c8b0be30 MB |
188 | const double frequency = 440; // |
189 | ||
ca8acb4a | 190 | int i; |
c8b0be30 | 191 | |
a69aedf2 | 192 | int number_of_iterations = 0; |
67e9b1b6 | 193 | uint64_t soxr_start_time = get_absolute_time_in_ns(); |
c8b0be30 | 194 | uint64_t loop_until_time = |
54d761ff MB |
195 | (uint64_t)1500000000 + soxr_start_time; // loop for a second and a half, max -- no need to be |
196 | // able to cancel it, do _don't even try_! | |
67e9b1b6 | 197 | while (get_absolute_time_in_ns() < loop_until_time) { |
c8b0be30 | 198 | |
a69aedf2 | 199 | number_of_iterations++; |
c8b0be30 MB |
200 | for (i = 0; i < buffer_length; i++) { |
201 | double w = sin(i * (frequency + number_of_iterations * 2) * 2 * M_PI / 44100); | |
ca8acb4a MB |
202 | int32_t wint = (int32_t)(w * INT32_MAX); |
203 | inbuffer[i * 2] = wint; | |
204 | inbuffer[i * 2 + 1] = wint; | |
205 | } | |
c8b0be30 | 206 | |
ca8acb4a MB |
207 | soxr_io_spec_t io_spec; |
208 | io_spec.itype = SOXR_INT32_I; | |
209 | io_spec.otype = SOXR_INT32_I; | |
210 | io_spec.scale = 1.0; // this seems to crash if not = 1.0 | |
211 | io_spec.e = NULL; | |
212 | io_spec.flags = 0; | |
213 | ||
214 | size_t odone; | |
215 | ||
c8b0be30 MB |
216 | soxr_oneshot(buffer_length, buffer_length + 1, 2, // Rates and # of chans. |
217 | inbuffer, buffer_length, NULL, // Input. | |
218 | outbuffer, buffer_length + 1, &odone, // Output. | |
219 | &io_spec, // Input, output and transfer spec. | |
220 | NULL, NULL); // Default configuration. | |
53715857 | 221 | |
ca8acb4a MB |
222 | io_spec.itype = SOXR_INT32_I; |
223 | io_spec.otype = SOXR_INT32_I; | |
224 | io_spec.scale = 1.0; // this seems to crash if not = 1.0 | |
225 | io_spec.e = NULL; | |
226 | io_spec.flags = 0; | |
227 | ||
c8b0be30 MB |
228 | soxr_oneshot(buffer_length, buffer_length - 1, 2, // Rates and # of chans. |
229 | inbuffer, buffer_length, NULL, // Input. | |
230 | outbuffer, buffer_length - 1, &odone, // Output. | |
231 | &io_spec, // Input, output and transfer spec. | |
232 | NULL, NULL); // Default configuration. | |
ca8acb4a MB |
233 | } |
234 | ||
38c43f07 MB |
235 | int64_t soxr_execution_time = |
236 | get_absolute_time_in_ns() - soxr_start_time; // this must be zero or positive | |
237 | int soxr_execution_time_int = soxr_execution_time; // must be in or around 1500000000 | |
238 | ||
9aa8f91c MB |
239 | // free(outbuffer); |
240 | // free(inbuffer); | |
38c43f07 | 241 | |
14bfba27 | 242 | if (number_of_iterations != 0) { |
38c43f07 | 243 | config.soxr_delay_index = soxr_execution_time_int / number_of_iterations; |
14bfba27 MB |
244 | } else { |
245 | debug(1, "No soxr-timing iterations performed, so \"basic\" iteration will be used."); | |
246 | config.soxr_delay_index = 0; // used as a flag | |
247 | } | |
38c43f07 MB |
248 | debug(2, "soxr_delay: %d nanoseconds, soxr_delay_threshold: %d milliseconds.", |
249 | config.soxr_delay_index, config.soxr_delay_threshold / 1000000); | |
c8b0be30 MB |
250 | if ((config.packet_stuffing == ST_soxr) && |
251 | (config.soxr_delay_index > config.soxr_delay_threshold)) | |
252 | inform("Note: this device may be too slow for \"soxr\" interpolation. Consider choosing the " | |
253 | "\"basic\" or \"auto\" interpolation setting."); | |
8568b58b | 254 | if (config.packet_stuffing == ST_auto) |
14bfba27 MB |
255 | debug( |
256 | 1, "\"%s\" interpolation has been chosen.", | |
257 | ((config.soxr_delay_index != 0) && (config.soxr_delay_index <= config.soxr_delay_threshold)) | |
258 | ? "soxr" | |
259 | : "basic"); | |
c8b0be30 | 260 | pthread_exit(NULL); |
ca8acb4a MB |
261 | } |
262 | ||
263 | #endif | |
264 | ||
f39ff3a4 | 265 | void usage(char *progname) { |
58fdb135 | 266 | |
ed629037 | 267 | #ifdef CONFIG_AIRPLAY_2 |
1e2a933e | 268 | if (has_fltp_capable_aac_decoder() == 0) { |
65831322 | 269 | printf("\nIMPORTANT NOTE: Shairport Sync can not run on this system.\n"); |
0dc34a46 | 270 | printf("A Floating Planar (\"fltp\") AAC decoder is required, "); |
65831322 | 271 | printf("but the system's ffmpeg library does not seem to include one.\n"); |
0dc34a46 MB |
272 | printf("See: " |
273 | "https://github.com/mikebrady/shairport-sync/blob/development/" | |
274 | "TROUBLESHOOTING.md#aac-decoder-issues-airplay-2-only\n\n"); | |
58fdb135 | 275 | |
ebd4e35b | 276 | } else { |
ed629037 | 277 | #endif |
3cbf7739 MB |
278 | // clang-format off |
279 | printf("Please use the configuration file for settings where possible.\n"); | |
280 | printf("Many more settings are available in the configuration file.\n"); | |
281 | printf("\n"); | |
0dc34a46 MB |
282 | printf("Usage: %s [options...]\n", progname); |
283 | printf(" or: %s [options...] -- [audio output-specific options]\n", progname); | |
284 | printf("\n"); | |
285 | printf("Options:\n"); | |
3cbf7739 MB |
286 | printf(" -h, --help Show this help.\n"); |
287 | printf(" -V, --version Show version information -- the version string.\n"); | |
7497b8e2 | 288 | printf(" -X, --displayConfig Output OS information, version string, command line, configuration file and active settings to the log.\n"); |
3cbf7739 MB |
289 | printf(" --statistics Print some interesting statistics. More will be printed if -v / -vv / -vvv are also chosen.\n"); |
290 | printf(" -v, --verbose Print debug information; -v some; -vv more; -vvv lots -- generally too much.\n"); | |
291 | printf(" -c, --configfile=FILE Read configuration settings from FILE. Default is %s.\n", configuration_file_path); | |
292 | printf(" -a, --name=NAME Set service name. Default is the hostname with first letter capitalised.\n"); | |
293 | printf(" --password=PASSWORD Require PASSWORD to connect. Default is no password. (Classic AirPlay only.)\n"); | |
294 | printf(" -p, --port=PORT Set RTSP listening port. Default 5000; 7000 for AirPlay 2./\n"); | |
295 | printf(" -L, --latency=FRAMES [Deprecated] Set the latency for audio sent from an unknown device.\n"); | |
296 | printf(" The default is to set it automatically.\n"); | |
297 | printf(" -S, --stuffing=MODE Set how to adjust current latency to match desired latency, where:\n"); | |
298 | printf(" \"basic\" inserts or deletes audio frames from packet frames with low processor overhead, and\n"); | |
299 | printf(" \"soxr\" uses libsoxr to minimally resample packet frames -- moderate processor overhead.\n"); | |
300 | printf(" The default \"auto\" setting chooses basic or soxr depending on processor capability.\n"); | |
301 | printf(" The \"soxr\" option is only available if built with soxr support.\n"); | |
302 | printf(" -B, --on-start=PROGRAM Run PROGRAM when playback is about to begin.\n"); | |
303 | printf(" -E, --on-stop=PROGRAM Run PROGRAM when playback has ended.\n"); | |
304 | printf(" For -B and -E options, specify the full path to the program and arguments, e.g. \"/usr/bin/logger\".\n"); | |
305 | printf(" Executable scripts work, but the file must be marked executable have the appropriate shebang (#!/bin/sh) on the first line.\n"); | |
306 | printf(" -w, --wait-cmd Wait until the -B or -E programs finish before continuing.\n"); | |
307 | printf(" -o, --output=BACKEND Select audio backend. They are listed at the end of this text. The first one is the default.\n"); | |
308 | printf(" -m, --mdns=BACKEND Use the mDNS backend named BACKEND to advertise the AirPlay service through Bonjour/ZeroConf.\n"); | |
309 | printf(" They are listed at the end of this text.\n"); | |
310 | printf(" If no mdns backend is specified, they are tried in order until one works.\n"); | |
311 | printf(" -r, --resync=THRESHOLD [Deprecated] resync if error exceeds this number of frames. Set to 0 to stop resyncing.\n"); | |
312 | printf(" -t, --timeout=SECONDS Go back to idle mode from play mode after a break in communications of this many seconds (default 120). Set to 0 never to exit play mode.\n"); | |
313 | printf(" --tolerance=TOLERANCE [Deprecated] Allow a synchronization error of TOLERANCE frames (default 88) before trying to correct it.\n"); | |
314 | printf(" --logOutputLevel Log the output level setting -- a debugging option, useful for determining the optimum maximum volume.\n"); | |
e76cfa69 | 315 | #ifdef CONFIG_LIBDAEMON |
3cbf7739 MB |
316 | printf(" -d, --daemon Daemonise.\n"); |
317 | printf(" -j, --justDaemoniseNoPIDFile Daemonise without a PID file.\n"); | |
318 | printf(" -k, --kill Kill the existing shairport daemon.\n"); | |
0dc34a46 | 319 | #endif |
e513e533 | 320 | #ifdef CONFIG_METADATA |
3cbf7739 MB |
321 | printf(" -M, --metadata-enable Ask for metadata from the source and process it. Much more flexibility with configuration file settings.\n"); |
322 | printf(" --metadata-pipename=PIPE send metadata to PIPE, e.g. --metadata-pipename=/tmp/%s-metadata.\n", config.appName); | |
0dc34a46 | 323 | printf(" The default is /tmp/%s-metadata.\n", config.appName); |
3cbf7739 MB |
324 | printf(" -g, --get-coverart Include cover art in the metadata to be gathered and sent.\n"); |
325 | #endif | |
326 | printf(" --log-to-syslog Send debug and statistics information through syslog\n"); | |
327 | printf(" If used, this should be the first command line argument.\n"); | |
328 | printf(" -u, --use-stderr [Deprecated] This setting is not needed -- stderr is now used by default and syslog is selected using --log-to-syslog.\n"); | |
0dc34a46 MB |
329 | printf("\n"); |
330 | mdns_ls_backends(); | |
331 | printf("\n"); | |
332 | audio_ls_outputs(); | |
3cbf7739 | 333 | // clang-format on |
ebd4e35b MB |
334 | |
335 | #ifdef CONFIG_AIRPLAY_2 | |
336 | } | |
337 | #endif | |
f39ff3a4 JL |
338 | } |
339 | ||
340 | int parse_options(int argc, char **argv) { | |
064bd293 MB |
341 | // there are potential memory leaks here -- it's called a second time, previously allocated |
342 | // strings will dangle. | |
c434f073 | 343 | char *raw_service_name = NULL; /* Used to pick up the service name before possibly expanding it */ |
064bd293 MB |
344 | char *stuffing = NULL; /* used for picking up the stuffing option */ |
345 | signed char c; /* used for argument parsing */ | |
73bf006c | 346 | // int i = 0; /* used for tracking options */ |
28f54af2 MB |
347 | int resync_threshold_in_frames = 0; |
348 | int tolerance_in_frames = 0; | |
cf29625d | 349 | poptContext optCon; /* context for parsing command-line options */ |
770b81af | 350 | struct poptOption optionsTable[] = { |
8991f342 | 351 | {"verbose", 'v', POPT_ARG_NONE, NULL, 'v', NULL, NULL}, |
6d088ac4 | 352 | {"kill", 'k', POPT_ARG_NONE, &killOption, 0, NULL, NULL}, |
8991f342 MB |
353 | {"daemon", 'd', POPT_ARG_NONE, &daemonisewith, 0, NULL, NULL}, |
354 | {"justDaemoniseNoPIDFile", 'j', POPT_ARG_NONE, &daemonisewithout, 0, NULL, NULL}, | |
355 | {"configfile", 'c', POPT_ARG_STRING, &config.configfile, 0, NULL, NULL}, | |
356 | {"statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL, NULL}, | |
357 | {"logOutputLevel", 0, POPT_ARG_NONE, &config.logOutputLevel, 0, NULL, NULL}, | |
358 | {"version", 'V', POPT_ARG_NONE, NULL, 0, NULL, NULL}, | |
7497b8e2 | 359 | {"displayConfig", 'X', POPT_ARG_NONE, &display_config_selected, 0, NULL, NULL}, |
8991f342 MB |
360 | {"port", 'p', POPT_ARG_INT, &config.port, 0, NULL, NULL}, |
361 | {"name", 'a', POPT_ARG_STRING, &raw_service_name, 0, NULL, NULL}, | |
362 | {"output", 'o', POPT_ARG_STRING, &config.output_name, 0, NULL, NULL}, | |
363 | {"on-start", 'B', POPT_ARG_STRING, &config.cmd_start, 0, NULL, NULL}, | |
364 | {"on-stop", 'E', POPT_ARG_STRING, &config.cmd_stop, 0, NULL, NULL}, | |
365 | {"wait-cmd", 'w', POPT_ARG_NONE, &config.cmd_blocking, 0, NULL, NULL}, | |
366 | {"mdns", 'm', POPT_ARG_STRING, &config.mdns_name, 0, NULL, NULL}, | |
367 | {"latency", 'L', POPT_ARG_INT, &config.userSuppliedLatency, 0, NULL, NULL}, | |
368 | {"stuffing", 'S', POPT_ARG_STRING, &stuffing, 'S', NULL, NULL}, | |
28f54af2 | 369 | {"resync", 'r', POPT_ARG_INT, &resync_threshold_in_frames, 'r', NULL, NULL}, |
8991f342 MB |
370 | {"timeout", 't', POPT_ARG_INT, &config.timeout, 't', NULL, NULL}, |
371 | {"password", 0, POPT_ARG_STRING, &config.password, 0, NULL, NULL}, | |
28f54af2 | 372 | {"tolerance", 'z', POPT_ARG_INT, &tolerance_in_frames, 'z', NULL, NULL}, |
fce6b513 | 373 | {"use-stderr", 'u', POPT_ARG_NONE, NULL, 'u', NULL, NULL}, |
0dc34a46 | 374 | {"log-to-syslog", 0, POPT_ARG_NONE, &log_to_syslog_selected, 0, NULL, NULL}, |
75f3f912 | 375 | #ifdef CONFIG_METADATA |
178af21d MB |
376 | {"metadata-enable", 'M', POPT_ARG_NONE, &config.metadata_enabled, 'M', NULL, NULL}, |
377 | {"metadata-pipename", 0, POPT_ARG_STRING, &config.metadata_pipename, 0, NULL, NULL}, | |
8991f342 | 378 | {"get-coverart", 'g', POPT_ARG_NONE, &config.get_coverart, 'g', NULL, NULL}, |
75f3f912 | 379 | #endif |
8991f342 | 380 | POPT_AUTOHELP{NULL, 0, 0, NULL, 0, NULL, NULL}}; |
392c03b2 | 381 | |
064bd293 | 382 | // we have to parse the command line arguments to look for a config file |
726c8201 MB |
383 | int optind; |
384 | optind = argc; | |
770b81af | 385 | int j; |
87a0475c MB |
386 | for (j = 0; j < argc; j++) |
387 | if (strcmp(argv[j], "--") == 0) | |
388 | optind = j; | |
392c03b2 | 389 | |
87a0475c | 390 | optCon = poptGetContext(NULL, optind, (const char **)argv, optionsTable, 0); |
b9d3a036 | 391 | if (optCon == NULL) |
d805ce91 | 392 | die("Can not get a secondary popt context."); |
770b81af | 393 | poptSetOtherOptionHelp(optCon, "[OPTIONS]* "); |
064bd293 | 394 | |
0dc34a46 | 395 | /* Now do options processing just to get a debug log destination and level */ |
b365f16b MB |
396 | debuglev = 0; |
397 | while ((c = poptGetNextOpt(optCon)) >= 0) { | |
398 | switch (c) { | |
399 | case 'v': | |
400 | debuglev++; | |
401 | break; | |
fce6b513 | 402 | case 'u': |
0dc34a46 MB |
403 | inform("Warning: the option -u is no longer needed and is deprecated. Debug and statistics " |
404 | "output to STDERR is now the default. Use \"--log-to-syslog\" to revert."); | |
fce6b513 | 405 | break; |
5f61e305 MB |
406 | case 'D': |
407 | inform("Warning: the option -D or --disconnectFromOutput is deprecated."); | |
408 | break; | |
409 | case 'R': | |
410 | inform("Warning: the option -R or --reconnectToOutput is deprecated."); | |
411 | break; | |
412 | case 'A': | |
e0aa75a8 MB |
413 | inform("Warning: the option -A or --AirPlayLatency is deprecated and ignored. This setting " |
414 | "is now " | |
cf29625d | 415 | "automatically received from the AirPlay device."); |
5f61e305 MB |
416 | break; |
417 | case 'i': | |
e0aa75a8 MB |
418 | inform("Warning: the option -i or --iTunesLatency is deprecated and ignored. This setting is " |
419 | "now " | |
cf29625d | 420 | "automatically received from iTunes"); |
5f61e305 MB |
421 | break; |
422 | case 'f': | |
e0aa75a8 MB |
423 | inform( |
424 | "Warning: the option --forkedDaapdLatency is deprecated and ignored. This setting is now " | |
425 | "automatically received from forkedDaapd"); | |
5f61e305 MB |
426 | break; |
427 | case 'r': | |
28f54af2 | 428 | config.resync_threshold = (resync_threshold_in_frames * 1.0) / 44100; |
cf29625d MB |
429 | inform("Warning: the option -r or --resync is deprecated. Please use the " |
430 | "\"resync_threshold_in_seconds\" setting in the config file instead."); | |
5f61e305 MB |
431 | break; |
432 | case 'z': | |
28f54af2 | 433 | config.tolerance = (tolerance_in_frames * 1.0) / 44100; |
cf29625d MB |
434 | inform("Warning: the option --tolerance is deprecated. Please use the " |
435 | "\"drift_tolerance_in_seconds\" setting in the config file instead."); | |
5f61e305 | 436 | break; |
cf29625d | 437 | } |
b365f16b MB |
438 | } |
439 | if (c < -1) { | |
440 | die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c)); | |
441 | } | |
cf29625d | 442 | |
d805ce91 | 443 | poptFreeContext(optCon); |
b9d3a036 | 444 | |
0dc34a46 MB |
445 | if (log_to_syslog_selected) { |
446 | // if this was the first command line argument, it'll already have been chosen | |
447 | if (log_to_syslog_select_is_first_command_line_argument == 0) { | |
448 | inform("Suggestion: make \"--log-to-syslog\" the first command line argument to ensure " | |
449 | "messages go to the syslog right from the beginning."); | |
450 | } | |
63e0dfda MB |
451 | #ifdef CONFIG_LIBDAEMON |
452 | log_to_default = 0; // a specific log output modality has been selected. | |
453 | #endif | |
0dc34a46 MB |
454 | log_to_syslog(); |
455 | } | |
456 | ||
e76cfa69 | 457 | #ifdef CONFIG_LIBDAEMON |
d022c8b4 | 458 | if ((daemonisewith) && (daemonisewithout)) |
e513e533 MB |
459 | die("Select either daemonize_with_pid_file or daemonize_without_pid_file -- you have selected " |
460 | "both!"); | |
d022c8b4 MB |
461 | if ((daemonisewith) || (daemonisewithout)) { |
462 | config.daemonise = 1; | |
463 | if (daemonisewith) | |
464 | config.daemonise_store_pid = 1; | |
e513e533 | 465 | }; |
e76cfa69 | 466 | #endif |
d022c8b4 | 467 | |
54d761ff | 468 | config.audio_backend_silent_lead_in_time_auto = |
8201903a MB |
469 | 1; // start outputting silence as soon as packets start arriving |
470 | config.default_airplay_volume = -24.0; | |
471 | config.high_threshold_airplay_volume = | |
07211e7e | 472 | -16.0; // if the volume exceeds this, reset to the default volume if idle for the |
8201903a MB |
473 | // limit_to_high_volume_threshold_time_in_minutes time |
474 | config.limit_to_high_volume_threshold_time_in_minutes = | |
72f1554c MB |
475 | 0; // after this time in minutes, if the volume is higher, use the default_airplay_volume |
476 | // volume for new play sessions. | |
9cfc6c47 | 477 | config.fixedLatencyOffset = 11025; // this sounds like it works properly. |
d9c9009f | 478 | config.diagnostic_drop_packet_fraction = 0.0; |
d2e1910f | 479 | config.active_state_timeout = 10.0; |
38c43f07 MB |
480 | config.soxr_delay_threshold = 30 * 1000000; // the soxr measurement time (nanoseconds) of two |
481 | // oneshots must not exceed this if soxr interpolation | |
482 | // is to be chosen automatically. | |
c8b0be30 MB |
483 | config.volume_range_hw_priority = |
484 | 0; // if combining software and hardware volume control, give the software priority | |
54d761ff MB |
485 | // i.e. when reducing volume, reduce the sw first before reducing the software. |
486 | // this is because some hw mixers mute at the bottom of their range, and they don't always | |
487 | // advertise this fact | |
488 | config.resend_control_first_check_time = | |
489 | 0.10; // wait this many seconds before requesting the resending of a missing packet | |
490 | config.resend_control_check_interval_time = | |
491 | 0.25; // wait this many seconds before again requesting the resending of a missing packet | |
492 | config.resend_control_last_check_time = | |
493 | 0.10; // give up if the packet is still missing this close to when it's needed | |
494 | config.missing_port_dacp_scan_interval_seconds = | |
495 | 2.0; // check at this interval if no DACP port number is known | |
496 | ||
497 | config.minimum_free_buffer_headroom = 125; // leave approximately one second's worth of buffers | |
498 | // free after calculating the effective latency. | |
499 | // e.g. if we have 1024 buffers or 352 frames = 8.17 seconds and we have a nominal latency of 2.0 | |
500 | // seconds then we can add an offset of 5.17 seconds and still leave a second's worth of buffers | |
501 | // for unexpected circumstances | |
72d71535 | 502 | |
77a5cf1e | 503 | #ifdef CONFIG_METADATA |
ca562872 MB |
504 | /* Get the metadata setting. */ |
505 | config.metadata_enabled = 1; // if metadata support is included, then enable it by default | |
506 | config.get_coverart = 1; // if metadata support is included, then enable it by default | |
77a5cf1e MB |
507 | #endif |
508 | ||
509 | #ifdef CONFIG_CONVOLUTION | |
ca562872 | 510 | config.convolution_max_length = 8192; |
77a5cf1e | 511 | #endif |
ca562872 | 512 | config.loudness_reference_volume_db = -20; |
77a5cf1e | 513 | |
69642bb7 | 514 | #ifdef CONFIG_METADATA_HUB |
4aaa8d19 | 515 | config.cover_art_cache_dir = "/tmp/shairport-sync/.cache/coverart"; |
4aab0a6f MB |
516 | config.scan_interval_when_active = |
517 | 1; // number of seconds between DACP server scans when playing something | |
518 | config.scan_interval_when_inactive = | |
daab7a63 | 519 | 1; // number of seconds between DACP server scans when playing nothing |
4aab0a6f MB |
520 | config.scan_max_bad_response_count = |
521 | 5; // number of successive bad results to ignore before giving up | |
54d761ff MB |
522 | // config.scan_max_inactive_count = |
523 | // (365 * 24 * 60 * 60) / config.scan_interval_when_inactive; // number of scans to do before | |
524 | // stopping if | |
525 | // not made active again (not used) | |
7a73e8fd | 526 | #endif |
1637a79d | 527 | |
f79222f7 MB |
528 | #ifdef CONFIG_AIRPLAY_2 |
529 | // the features code is a 64-bit number, but in the mDNS advertisement, the least significant 32 | |
530 | // bit are given first for example, if the features number is 0x1C340405F4A00, it will be given as | |
531 | // features=0x405F4A00,0x1C340 in the mDNS string, and in a signed decimal number in the plist: | |
532 | // 496155702020608 this setting here is the source of both the plist features response and the | |
533 | // mDNS string. | |
534 | // note: 0x300401F4A00 works but with weird delays and stuff | |
535 | // config.airplay_features = 0x1C340405FCA00; | |
536 | uint64_t mask = | |
537 | ((uint64_t)1 << 17) | ((uint64_t)1 << 16) | ((uint64_t)1 << 15) | ((uint64_t)1 << 50); | |
538 | config.airplay_features = | |
539 | 0x1C340405D4A00 & (~mask); // APX + Authentication4 (b14) with no metadata (see below) | |
540 | // Advertised with mDNS and returned with GET /info, see | |
541 | // https://openairplay.github.io/airplay-spec/status_flags.html 0x4: Audio cable attached, no PIN | |
542 | // required (transient pairing), 0x204: Audio cable attached, OneTimePairingRequired 0x604: Audio | |
543 | // cable attached, OneTimePairingRequired, device was setup for Homekit access control | |
544 | config.airplay_statusflags = 0x04; | |
545 | // Set to NULL to work with transient pairing | |
546 | config.airplay_pin = NULL; | |
547 | ||
9feb9010 | 548 | // use the MAC address placed in config.hw_addr to generate the default airplay_device_id |
f79222f7 MB |
549 | uint64_t temporary_airplay_id = nctoh64(config.hw_addr); |
550 | temporary_airplay_id = | |
551 | temporary_airplay_id >> 16; // we only use the first 6 bytes but have imported 8. | |
552 | ||
553 | // now generate a UUID | |
554 | // from https://stackoverflow.com/questions/51053568/generating-a-random-uuid-in-c | |
555 | // with thanks | |
556 | uuid_t binuuid; | |
557 | uuid_generate_random(binuuid); | |
558 | ||
3828ec1a | 559 | char *uuid = malloc(UUID_STR_LEN + 1); // leave space for the NUL at the end |
f79222f7 MB |
560 | // Produces a UUID string at uuid consisting of lower-case letters |
561 | uuid_unparse_lower(binuuid, uuid); | |
562 | config.airplay_pi = uuid; | |
563 | ||
564 | #endif | |
565 | ||
73bf006c | 566 | // config_setting_t *setting; |
064bd293 MB |
567 | const char *str = 0; |
568 | int value = 0; | |
ae84366e | 569 | double dvalue = 0.0; |
064bd293 | 570 | |
d022c8b4 | 571 | // debug(1, "Looking for the configuration file \"%s\".", config.configfile); |
064bd293 | 572 | |
87a0475c | 573 | config_init(&config_file_stuff); |
064bd293 | 574 | |
3cbf7739 | 575 | config_file_real_path = realpath(config.configfile, NULL); |
064bd293 | 576 | if (config_file_real_path == NULL) { |
827504a3 | 577 | debug(2, "can't resolve the configuration file \"%s\".", config.configfile); |
b365f16b | 578 | } else { |
827504a3 | 579 | debug(2, "looking for configuration file at full path \"%s\"", config_file_real_path); |
b365f16b MB |
580 | /* Read the file. If there is an error, report it and exit. */ |
581 | if (config_read_file(&config_file_stuff, config_file_real_path)) { | |
3d1ee2e0 MB |
582 | config_set_auto_convert(&config_file_stuff, |
583 | 1); // allow autoconversion from int/float to int/float | |
b365f16b MB |
584 | // make config.cfg point to it |
585 | config.cfg = &config_file_stuff; | |
6e9ebe6e | 586 | |
b365f16b | 587 | /* Get the Service Name. */ |
c434f073 MB |
588 | if (config_lookup_string(config.cfg, "general.name", &str)) { |
589 | raw_service_name = (char *)str; | |
590 | } | |
e76cfa69 | 591 | #ifdef CONFIG_LIBDAEMON |
b365f16b | 592 | /* Get the Daemonize setting. */ |
02694948 | 593 | config_set_lookup_bool(config.cfg, "sessioncontrol.daemonize_with_pid_file", &daemonisewith); |
87a0475c | 594 | |
d022c8b4 | 595 | /* Get the Just_Daemonize setting. */ |
c2e3fa5a MB |
596 | config_set_lookup_bool(config.cfg, "sessioncontrol.daemonize_without_pid_file", |
597 | &daemonisewithout); | |
02694948 | 598 | |
6195be01 MB |
599 | /* Get the directory path for the pid file created when the program is daemonised. */ |
600 | if (config_lookup_string(config.cfg, "sessioncontrol.daemon_pid_dir", &str)) | |
601 | config.piddir = (char *)str; | |
e76cfa69 | 602 | #endif |
6d088ac4 | 603 | |
b365f16b MB |
604 | /* Get the mdns_backend setting. */ |
605 | if (config_lookup_string(config.cfg, "general.mdns_backend", &str)) | |
606 | config.mdns_name = (char *)str; | |
607 | ||
608 | /* Get the output_backend setting. */ | |
609 | if (config_lookup_string(config.cfg, "general.output_backend", &str)) | |
610 | config.output_name = (char *)str; | |
611 | ||
612 | /* Get the port setting. */ | |
613 | if (config_lookup_int(config.cfg, "general.port", &value)) { | |
614 | if ((value < 0) || (value > 65535)) | |
3a02b79a MB |
615 | #ifdef CONFIG_AIRPLAY_2 |
616 | die("Invalid port number \"%sd\". It should be between 0 and 65535, default is 7000", | |
617 | value); | |
618 | #else | |
b365f16b MB |
619 | die("Invalid port number \"%sd\". It should be between 0 and 65535, default is 5000", |
620 | value); | |
3a02b79a | 621 | #endif |
b365f16b MB |
622 | else |
623 | config.port = value; | |
624 | } | |
87a0475c | 625 | |
b365f16b MB |
626 | /* Get the udp port base setting. */ |
627 | if (config_lookup_int(config.cfg, "general.udp_port_base", &value)) { | |
628 | if ((value < 0) || (value > 65535)) | |
629 | die("Invalid port number \"%sd\". It should be between 0 and 65535, default is 6001", | |
630 | value); | |
631 | else | |
632 | config.udp_port_base = value; | |
633 | } | |
87a0475c | 634 | |
064bd293 MB |
635 | /* Get the udp port range setting. This is number of ports that will be tried for free ports , |
636 | * starting at the port base. Only three ports are needed. */ | |
b365f16b | 637 | if (config_lookup_int(config.cfg, "general.udp_port_range", &value)) { |
093bf1dd MB |
638 | if ((value < 3) || (value > 65535)) |
639 | die("Invalid port range \"%sd\". It should be between 3 and 65535, default is 10", | |
b365f16b MB |
640 | value); |
641 | else | |
642 | config.udp_port_range = value; | |
643 | } | |
392c03b2 | 644 | |
b365f16b MB |
645 | /* Get the password setting. */ |
646 | if (config_lookup_string(config.cfg, "general.password", &str)) | |
647 | config.password = (char *)str; | |
648 | ||
649 | if (config_lookup_string(config.cfg, "general.interpolation", &str)) { | |
650 | if (strcasecmp(str, "basic") == 0) | |
651 | config.packet_stuffing = ST_basic; | |
9aa8f91c MB |
652 | else if (strcasecmp(str, "auto") == 0) |
653 | config.packet_stuffing = ST_auto; | |
b365f16b | 654 | else if (strcasecmp(str, "soxr") == 0) |
c9b3d2a2 | 655 | #ifdef CONFIG_SOXR |
3001f39b | 656 | config.packet_stuffing = ST_soxr; |
1f3ab8fd | 657 | #else |
178af21d | 658 | warn("The soxr option not available because this version of shairport-sync was built " |
c8b0be30 MB |
659 | "without libsoxr " |
660 | "support. Change the \"general/interpolation\" setting in the configuration file."); | |
1f3ab8fd | 661 | #endif |
b365f16b | 662 | else |
a382ac66 MB |
663 | die("Invalid interpolation option choice \"%s\". It should be \"auto\", \"basic\" or " |
664 | "\"soxr\"", | |
665 | str); | |
b365f16b | 666 | } |
c8b0be30 | 667 | |
9aa8f91c | 668 | #ifdef CONFIG_SOXR |
38c43f07 | 669 | |
9aa8f91c | 670 | /* Get the soxr_delay_threshold setting. */ |
38c43f07 | 671 | /* Convert between the input, given in milliseconds, and the stored values in nanoseconds. */ |
9aa8f91c | 672 | if (config_lookup_int(config.cfg, "general.soxr_delay_threshold", &value)) { |
38c43f07 MB |
673 | if ((value >= 1) && (value <= 100)) |
674 | config.soxr_delay_threshold = value * 1000000; | |
9aa8f91c MB |
675 | else |
676 | warn("Invalid general soxr_delay_threshold setting option choice \"%d\". It should be " | |
38c43f07 | 677 | "between 1 and 100, " |
c8b0be30 | 678 | "inclusive. Default is %d (milliseconds).", |
38c43f07 | 679 | value, config.soxr_delay_threshold / 1000000); |
9aa8f91c MB |
680 | } |
681 | #endif | |
392c03b2 | 682 | |
b365f16b | 683 | /* Get the statistics setting. */ |
ac8afe0a | 684 | if (config_set_lookup_bool(config.cfg, "general.statistics", |
b75c82d1 | 685 | &(config.statistics_requested))) { |
a0bb2993 MB |
686 | warn("The \"general\" \"statistics\" setting is deprecated. Please use the \"diagnostics\" " |
687 | "\"statistics\" setting instead."); | |
b365f16b | 688 | } |
392c03b2 | 689 | |
5a8241c7 | 690 | /* The old drift tolerance setting. */ |
5f61e305 MB |
691 | if (config_lookup_int(config.cfg, "general.drift", &value)) { |
692 | inform("The drift setting is deprecated. Use " | |
c80d59b3 | 693 | "drift_tolerance_in_seconds instead"); |
cf29625d | 694 | config.tolerance = 1.0 * value / 44100; |
5f61e305 | 695 | } |
5a8241c7 MB |
696 | |
697 | /* The old resync setting. */ | |
5f61e305 MB |
698 | if (config_lookup_int(config.cfg, "general.resync_threshold", &value)) { |
699 | inform("The resync_threshold setting is deprecated. Use " | |
c80d59b3 | 700 | "resync_threshold_in_seconds instead"); |
28f54af2 | 701 | config.resync_threshold = 1.0 * value / 44100; |
5f61e305 | 702 | } |
5a8241c7 | 703 | |
b365f16b | 704 | /* Get the drift tolerance setting. */ |
5a8241c7 | 705 | if (config_lookup_float(config.cfg, "general.drift_tolerance_in_seconds", &dvalue)) |
ae84366e | 706 | config.tolerance = dvalue; |
b365f16b MB |
707 | |
708 | /* Get the resync setting. */ | |
5a8241c7 | 709 | if (config_lookup_float(config.cfg, "general.resync_threshold_in_seconds", &dvalue)) |
28f54af2 MB |
710 | config.resync_threshold = dvalue; |
711 | ||
712 | /* Get the resync recovery time setting. */ | |
713 | if (config_lookup_float(config.cfg, "general.resync_recovery_time_in_seconds", &dvalue)) | |
714 | config.resync_recovery_time = dvalue; | |
b365f16b MB |
715 | |
716 | /* Get the verbosity setting. */ | |
e052b5fb | 717 | if (config_lookup_int(config.cfg, "general.log_verbosity", &value)) { |
a0bb2993 MB |
718 | warn("The \"general\" \"log_verbosity\" setting is deprecated. Please use the " |
719 | "\"diagnostics\" \"log_verbosity\" setting instead."); | |
b365f16b MB |
720 | if ((value >= 0) && (value <= 3)) |
721 | debuglev = value; | |
722 | else | |
723 | die("Invalid log verbosity setting option choice \"%d\". It should be between 0 and 3, " | |
724 | "inclusive.", | |
725 | value); | |
e052b5fb | 726 | } |
b365f16b | 727 | |
c6aa6fd2 MB |
728 | /* Get the verbosity setting. */ |
729 | if (config_lookup_int(config.cfg, "diagnostics.log_verbosity", &value)) { | |
730 | if ((value >= 0) && (value <= 3)) | |
731 | debuglev = value; | |
732 | else | |
a0bb2993 MB |
733 | die("Invalid diagnostics log_verbosity setting option choice \"%d\". It should be " |
734 | "between 0 and 3, " | |
c6aa6fd2 MB |
735 | "inclusive.", |
736 | value); | |
737 | } | |
738 | ||
db8f10cf MB |
739 | /* Get the config.debugger_show_file_and_line in debug messages setting. */ |
740 | if (config_lookup_string(config.cfg, "diagnostics.log_show_file_and_line", &str)) { | |
741 | if (strcasecmp(str, "no") == 0) | |
742 | config.debugger_show_file_and_line = 0; | |
743 | else if (strcasecmp(str, "yes") == 0) | |
744 | config.debugger_show_file_and_line = 1; | |
745 | else | |
746 | die("Invalid diagnostics log_show_file_and_line option choice \"%s\". It should be " | |
a382ac66 MB |
747 | "\"yes\" or \"no\"", |
748 | str); | |
db8f10cf MB |
749 | } |
750 | ||
a0bb2993 MB |
751 | /* Get the show elapsed time in debug messages setting. */ |
752 | if (config_lookup_string(config.cfg, "diagnostics.log_show_time_since_startup", &str)) { | |
753 | if (strcasecmp(str, "no") == 0) | |
754 | config.debugger_show_elapsed_time = 0; | |
755 | else if (strcasecmp(str, "yes") == 0) | |
756 | config.debugger_show_elapsed_time = 1; | |
757 | else | |
758 | die("Invalid diagnostics log_show_time_since_startup option choice \"%s\". It should be " | |
a382ac66 MB |
759 | "\"yes\" or \"no\"", |
760 | str); | |
a0bb2993 MB |
761 | } |
762 | ||
763 | /* Get the show relative time in debug messages setting. */ | |
764 | if (config_lookup_string(config.cfg, "diagnostics.log_show_time_since_last_message", &str)) { | |
765 | if (strcasecmp(str, "no") == 0) | |
766 | config.debugger_show_relative_time = 0; | |
767 | else if (strcasecmp(str, "yes") == 0) | |
768 | config.debugger_show_relative_time = 1; | |
769 | else | |
770 | die("Invalid diagnostics log_show_time_since_last_message option choice \"%s\". It " | |
a382ac66 MB |
771 | "should be \"yes\" or \"no\"", |
772 | str); | |
a0bb2993 MB |
773 | } |
774 | ||
c6aa6fd2 MB |
775 | /* Get the statistics setting. */ |
776 | if (config_lookup_string(config.cfg, "diagnostics.statistics", &str)) { | |
777 | if (strcasecmp(str, "no") == 0) | |
778 | config.statistics_requested = 0; | |
779 | else if (strcasecmp(str, "yes") == 0) | |
780 | config.statistics_requested = 1; | |
781 | else | |
a0bb2993 | 782 | die("Invalid diagnostics statistics option choice \"%s\". It should be \"yes\" or " |
a382ac66 MB |
783 | "\"no\"", |
784 | str); | |
c6aa6fd2 MB |
785 | } |
786 | ||
c6aa6fd2 MB |
787 | /* Get the disable_resend_requests setting. */ |
788 | if (config_lookup_string(config.cfg, "diagnostics.disable_resend_requests", &str)) { | |
789 | config.disable_resend_requests = 0; // this is for legacy -- only set by -t 0 | |
790 | if (strcasecmp(str, "no") == 0) | |
791 | config.disable_resend_requests = 0; | |
792 | else if (strcasecmp(str, "yes") == 0) | |
793 | config.disable_resend_requests = 1; | |
794 | else | |
795 | die("Invalid diagnostic disable_resend_requests option choice \"%s\". It should be " | |
796 | "\"yes\" " | |
a382ac66 MB |
797 | "or \"no\"", |
798 | str); | |
c6aa6fd2 MB |
799 | } |
800 | ||
d9c9009f MB |
801 | /* Get the drop packets setting. */ |
802 | if (config_lookup_float(config.cfg, "diagnostics.drop_this_fraction_of_audio_packets", | |
803 | &dvalue)) { | |
804 | if ((dvalue >= 0.0) && (dvalue <= 3.0)) | |
805 | config.diagnostic_drop_packet_fraction = dvalue; | |
806 | else | |
807 | die("Invalid diagnostics drop_this_fraction_of_audio_packets setting \"%d\". It should " | |
808 | "be " | |
809 | "between 0.0 and 1.0, " | |
810 | "inclusive.", | |
811 | dvalue); | |
812 | } | |
813 | ||
6406c296 MB |
814 | /* Get the diagnostics output default. */ |
815 | if (config_lookup_string(config.cfg, "diagnostics.log_output_to", &str)) { | |
63e0dfda MB |
816 | #ifdef CONFIG_LIBDAEMON |
817 | log_to_default = 0; // a specific log output modality has been selected. | |
818 | #endif | |
6406c296 MB |
819 | if (strcasecmp(str, "syslog") == 0) |
820 | log_to_syslog(); | |
821 | else if (strcasecmp(str, "stdout") == 0) { | |
822 | log_to_stdout(); | |
823 | } else if (strcasecmp(str, "stderr") == 0) { | |
824 | log_to_stderr(); | |
825 | } else { | |
ca562872 MB |
826 | config.log_file_path = (char *)str; |
827 | config.log_fd = -1; | |
828 | log_to_file(); | |
6406c296 MB |
829 | } |
830 | } | |
b365f16b MB |
831 | /* Get the ignore_volume_control setting. */ |
832 | if (config_lookup_string(config.cfg, "general.ignore_volume_control", &str)) { | |
833 | if (strcasecmp(str, "no") == 0) | |
834 | config.ignore_volume_control = 0; | |
835 | else if (strcasecmp(str, "yes") == 0) | |
836 | config.ignore_volume_control = 1; | |
837 | else | |
a382ac66 MB |
838 | die("Invalid ignore_volume_control option choice \"%s\". It should be \"yes\" or \"no\"", |
839 | str); | |
b365f16b | 840 | } |
cf29625d MB |
841 | |
842 | /* Get the optional volume_max_db setting. */ | |
2a4a4ed2 | 843 | if (config_lookup_float(config.cfg, "general.volume_max_db", &dvalue)) { |
2d8fbb75 | 844 | // debug(1, "Max volume setting of %f dB", dvalue); |
2a4a4ed2 MB |
845 | config.volume_max_db = dvalue; |
846 | config.volume_max_db_set = 1; | |
847 | } | |
392c03b2 | 848 | |
72f1554c MB |
849 | /* Get the optional default_volume setting. */ |
850 | if (config_lookup_float(config.cfg, "general.default_airplay_volume", &dvalue)) { | |
851 | // debug(1, "Default airplay volume setting of %f on the -30.0 to 0 scale", dvalue); | |
852 | if ((dvalue >= -30.0) && (dvalue <= 0.0)) { | |
853 | config.default_airplay_volume = dvalue; | |
854 | } else { | |
855 | warn("The default airplay volume setting must be between -30.0 and 0.0."); | |
856 | } | |
857 | } | |
858 | ||
859 | /* Get the optional high_volume_threshold setting. */ | |
860 | if (config_lookup_float(config.cfg, "general.high_threshold_airplay_volume", &dvalue)) { | |
861 | // debug(1, "High threshold airplay volume setting of %f on the -30.0 to 0 scale", dvalue); | |
862 | if ((dvalue >= -30.0) && (dvalue <= 0.0)) { | |
863 | config.high_threshold_airplay_volume = dvalue; | |
864 | } else { | |
865 | warn("The high threshold airplay volume setting must be between -30.0 and 0.0."); | |
866 | } | |
867 | } | |
868 | ||
869 | /* Get the optional high volume idle tiomeout setting. */ | |
870 | if (config_lookup_float(config.cfg, "general.high_volume_idle_timeout_in_minutes", &dvalue)) { | |
871 | // debug(1, "High high_volume_idle_timeout_in_minutes setting of %f", dvalue); | |
872 | if (dvalue >= 0.0) { | |
873 | config.limit_to_high_volume_threshold_time_in_minutes = dvalue; | |
874 | } else { | |
875 | warn("The high volume idle timeout in minutes setting must be 0.0 or greater. A setting " | |
876 | "of 0.0 disables the high volume check."); | |
877 | } | |
878 | } | |
879 | ||
e37c277b MB |
880 | if (config_lookup_string(config.cfg, "general.run_this_when_volume_is_set", &str)) { |
881 | config.cmd_set_volume = (char *)str; | |
882 | } | |
883 | ||
1bb56b4c | 884 | /* Get the playback_mode setting */ |
064bd293 | 885 | if (config_lookup_string(config.cfg, "general.playback_mode", &str)) { |
1bb56b4c MB |
886 | if (strcasecmp(str, "stereo") == 0) |
887 | config.playback_mode = ST_stereo; | |
888 | else if (strcasecmp(str, "mono") == 0) | |
889 | config.playback_mode = ST_mono; | |
8a9665f6 MB |
890 | else if (strcasecmp(str, "reverse stereo") == 0) |
891 | config.playback_mode = ST_reverse_stereo; | |
892 | else if (strcasecmp(str, "both left") == 0) | |
893 | config.playback_mode = ST_left_only; | |
894 | else if (strcasecmp(str, "both right") == 0) | |
895 | config.playback_mode = ST_right_only; | |
46bcdce7 | 896 | else |
c80d59b3 | 897 | die("Invalid playback_mode choice \"%s\". It should be \"stereo\" (default), \"mono\", " |
a382ac66 MB |
898 | "\"reverse stereo\", \"both left\", \"both right\"", |
899 | str); | |
46bcdce7 | 900 | } |
2cab35cd | 901 | |
f83b86ec MB |
902 | /* Get the volume control profile setting -- "standard" or "flat" */ |
903 | if (config_lookup_string(config.cfg, "general.volume_control_profile", &str)) { | |
904 | if (strcasecmp(str, "standard") == 0) | |
905 | config.volume_control_profile = VCP_standard; | |
906 | else if (strcasecmp(str, "flat") == 0) | |
907 | config.volume_control_profile = VCP_flat; | |
ebe1ca17 | 908 | else if (strcasecmp(str, "dasl_tapered") == 0) |
909 | config.volume_control_profile = VCP_dasl_tapered; | |
f83b86ec | 910 | else |
baf51cf2 | 911 | die("Invalid volume_control_profile choice \"%s\". It should be \"standard\" (default), " |
ebe1ca17 | 912 | "\"dasl_tapered\", or \"flat\"", |
a382ac66 | 913 | str); |
f83b86ec | 914 | } |
552218ab | 915 | |
3d1ee2e0 MB |
916 | config_set_lookup_bool(config.cfg, "general.volume_control_combined_hardware_priority", |
917 | &config.volume_range_hw_priority); | |
f83b86ec | 918 | |
2cab35cd MB |
919 | /* Get the interface to listen on, if specified Default is all interfaces */ |
920 | /* we keep the interface name and the index */ | |
cf29625d MB |
921 | |
922 | if (config_lookup_string(config.cfg, "general.interface", &str)) | |
2cab35cd MB |
923 | config.interface = strdup(str); |
924 | ||
cf29625d | 925 | if (config_lookup_string(config.cfg, "general.interface", &str)) { |
c849c1fc | 926 | |
7d350c3d | 927 | config.interface_index = if_nametoindex(config.interface); |
c849c1fc | 928 | |
7d350c3d | 929 | if (config.interface_index == 0) { |
cf29625d MB |
930 | inform( |
931 | "The mdns service interface \"%s\" was not found, so the setting has been ignored.", | |
932 | config.interface); | |
2cab35cd MB |
933 | free(config.interface); |
934 | config.interface = NULL; | |
cf29625d | 935 | } |
2cab35cd | 936 | } |
cf29625d | 937 | |
064bd293 MB |
938 | /* Get the regtype -- the service type and protocol, separated by a dot. Default is |
939 | * "_raop._tcp" */ | |
bfadbf38 MB |
940 | if (config_lookup_string(config.cfg, "general.regtype", &str)) |
941 | config.regtype = strdup(str); | |
bfadbf38 | 942 | |
064bd293 MB |
943 | /* Get the volume range, in dB, that should be used If not set, it means you just use the |
944 | * range set by the mixer. */ | |
ea250113 MB |
945 | if (config_lookup_int(config.cfg, "general.volume_range_db", &value)) { |
946 | if ((value < 30) || (value > 150)) | |
012dd26c MB |
947 | die("Invalid volume range %d dB. It should be between 30 and 150 dB. Zero means use " |
948 | "the mixer's native range. The setting reamins at %d.", | |
949 | value, config.volume_range_db); | |
ddd76b10 MB |
950 | else |
951 | config.volume_range_db = value; | |
952 | } | |
953 | ||
72f1c3b5 | 954 | /* Get the alac_decoder setting. */ |
0479994c | 955 | if (config_lookup_string(config.cfg, "general.alac_decoder", &str)) { |
72f1c3b5 | 956 | if (strcasecmp(str, "hammerton") == 0) |
945483d9 | 957 | config.use_apple_decoder = 0; |
72f1c3b5 | 958 | else if (strcasecmp(str, "apple") == 0) { |
064bd293 | 959 | if ((config.decoders_supported & 1 << decoder_apple_alac) != 0) |
945483d9 MB |
960 | config.use_apple_decoder = 1; |
961 | else | |
064bd293 MB |
962 | inform("Support for the Apple ALAC decoder has not been compiled into this version of " |
963 | "Shairport Sync. The default decoder will be used."); | |
945483d9 | 964 | } else |
a382ac66 MB |
965 | die("Invalid alac_decoder option choice \"%s\". It should be \"hammerton\" or \"apple\"", |
966 | str); | |
945483d9 | 967 | } |
53715857 | 968 | |
686fc2a5 | 969 | /* Get the resend control settings. */ |
54d761ff | 970 | if (config_lookup_float(config.cfg, "general.resend_control_first_check_time", &dvalue)) { |
686fc2a5 MB |
971 | if ((dvalue >= 0.0) && (dvalue <= 3.0)) |
972 | config.resend_control_first_check_time = dvalue; | |
973 | else | |
012dd26c | 974 | warn("Invalid general resend_control_first_check_time setting \"%f\". It should " |
54d761ff MB |
975 | "be " |
976 | "between 0.0 and 3.0, " | |
977 | "inclusive. The setting remains at %f seconds.", | |
978 | dvalue, config.resend_control_first_check_time); | |
686fc2a5 MB |
979 | } |
980 | ||
54d761ff | 981 | if (config_lookup_float(config.cfg, "general.resend_control_check_interval_time", &dvalue)) { |
686fc2a5 MB |
982 | if ((dvalue >= 0.0) && (dvalue <= 3.0)) |
983 | config.resend_control_check_interval_time = dvalue; | |
984 | else | |
012dd26c | 985 | warn("Invalid general resend_control_check_interval_time setting \"%f\". It should " |
54d761ff MB |
986 | "be " |
987 | "between 0.0 and 3.0, " | |
988 | "inclusive. The setting remains at %f seconds.", | |
989 | dvalue, config.resend_control_check_interval_time); | |
686fc2a5 MB |
990 | } |
991 | ||
54d761ff | 992 | if (config_lookup_float(config.cfg, "general.resend_control_last_check_time", &dvalue)) { |
686fc2a5 MB |
993 | if ((dvalue >= 0.0) && (dvalue <= 3.0)) |
994 | config.resend_control_last_check_time = dvalue; | |
995 | else | |
012dd26c | 996 | warn("Invalid general resend_control_last_check_time setting \"%f\". It should " |
54d761ff MB |
997 | "be " |
998 | "between 0.0 and 3.0, " | |
999 | "inclusive. The setting remains at %f seconds.", | |
1000 | dvalue, config.resend_control_last_check_time); | |
686fc2a5 | 1001 | } |
945483d9 | 1002 | |
1b7e23c0 MB |
1003 | if (config_lookup_float(config.cfg, "general.missing_port_dacp_scan_interval_seconds", |
1004 | &dvalue)) { | |
1005 | if ((dvalue >= 0.0) && (dvalue <= 300.0)) | |
1006 | config.missing_port_dacp_scan_interval_seconds = dvalue; | |
1007 | else | |
012dd26c | 1008 | warn("Invalid general missing_port_dacp_scan_interval_seconds setting \"%f\". It should " |
54d761ff MB |
1009 | "be " |
1010 | "between 0.0 and 300.0, " | |
1011 | "inclusive. The setting remains at %f seconds.", | |
1012 | dvalue, config.missing_port_dacp_scan_interval_seconds); | |
1b7e23c0 MB |
1013 | } |
1014 | ||
064bd293 | 1015 | /* Get the default latency. Deprecated! */ |
b365f16b | 1016 | if (config_lookup_int(config.cfg, "latencies.default", &value)) |
440b592f | 1017 | config.userSuppliedLatency = value; |
b365f16b | 1018 | |
064bd293 | 1019 | #ifdef CONFIG_METADATA |
b365f16b MB |
1020 | /* Get the metadata setting. */ |
1021 | if (config_lookup_string(config.cfg, "metadata.enabled", &str)) { | |
1022 | if (strcasecmp(str, "no") == 0) | |
1023 | config.metadata_enabled = 0; | |
1024 | else if (strcasecmp(str, "yes") == 0) | |
1025 | config.metadata_enabled = 1; | |
1026 | else | |
b4cd4fbc | 1027 | die("Invalid metadata enabled option choice \"%s\". It should be \"yes\" or \"no\"", str); |
b365f16b | 1028 | } |
87a0475c | 1029 | |
b365f16b MB |
1030 | if (config_lookup_string(config.cfg, "metadata.include_cover_art", &str)) { |
1031 | if (strcasecmp(str, "no") == 0) | |
1032 | config.get_coverart = 0; | |
1033 | else if (strcasecmp(str, "yes") == 0) | |
1034 | config.get_coverart = 1; | |
1035 | else | |
1036 | die("Invalid metadata include_cover_art option choice \"%s\". It should be \"yes\" or " | |
a382ac66 MB |
1037 | "\"no\"", |
1038 | str); | |
b365f16b | 1039 | } |
87a0475c | 1040 | |
b365f16b MB |
1041 | if (config_lookup_string(config.cfg, "metadata.pipe_name", &str)) { |
1042 | config.metadata_pipename = (char *)str; | |
1043 | } | |
40446668 | 1044 | |
7c1a5fd0 MB |
1045 | if (config_lookup_float(config.cfg, "metadata.progress_interval", &dvalue)) { |
1046 | config.metadata_progress_interval = dvalue; | |
1047 | } | |
1048 | ||
41de3e5c MB |
1049 | if (config_lookup_string(config.cfg, "metadata.socket_address", &str)) { |
1050 | config.metadata_sockaddr = (char *)str; | |
1051 | } | |
1052 | if (config_lookup_int(config.cfg, "metadata.socket_port", &value)) { | |
1053 | config.metadata_sockport = value; | |
1054 | } | |
1055 | config.metadata_sockmsglength = 500; | |
1056 | if (config_lookup_int(config.cfg, "metadata.socket_msglength", &value)) { | |
1057 | config.metadata_sockmsglength = value < 500 ? 500 : value > 65000 ? 65000 : value; | |
1058 | } | |
1059 | ||
1060 | #endif | |
1061 | ||
1062 | #ifdef CONFIG_METADATA_HUB | |
6c57ce97 MB |
1063 | if (config_lookup_string(config.cfg, "metadata.cover_art_cache_directory", &str)) { |
1064 | config.cover_art_cache_dir = (char *)str; | |
1065 | } | |
1066 | ||
e0ffb18d | 1067 | if (config_lookup_string(config.cfg, "diagnostics.retain_cover_art", &str)) { |
6c57ce97 MB |
1068 | if (strcasecmp(str, "no") == 0) |
1069 | config.retain_coverart = 0; | |
1070 | else if (strcasecmp(str, "yes") == 0) | |
1071 | config.retain_coverart = 1; | |
1072 | else | |
b4cd4fbc | 1073 | die("Invalid metadata \"retain_cover_art\" option choice \"%s\". It should be \"yes\" or " |
a382ac66 MB |
1074 | "\"no\"", |
1075 | str); | |
6c57ce97 | 1076 | } |
064bd293 | 1077 | #endif |
62dc112a | 1078 | |
b365f16b MB |
1079 | if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_play_begins", &str)) { |
1080 | config.cmd_start = (char *)str; | |
1081 | } | |
392c03b2 | 1082 | |
b365f16b MB |
1083 | if (config_lookup_string(config.cfg, "sessioncontrol.run_this_after_play_ends", &str)) { |
1084 | config.cmd_stop = (char *)str; | |
1085 | } | |
e73657d6 | 1086 | |
5af61798 | 1087 | if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_entering_active_state", |
2e442853 | 1088 | &str)) { |
2e181f93 MB |
1089 | config.cmd_active_start = (char *)str; |
1090 | } | |
1091 | ||
5af61798 | 1092 | if (config_lookup_string(config.cfg, "sessioncontrol.run_this_after_exiting_active_state", |
2e442853 | 1093 | &str)) { |
2e181f93 MB |
1094 | config.cmd_active_stop = (char *)str; |
1095 | } | |
1096 | ||
5af61798 | 1097 | if (config_lookup_float(config.cfg, "sessioncontrol.active_state_timeout", &dvalue)) { |
2e181f93 | 1098 | if (dvalue < 0.0) |
b4cd4fbc | 1099 | warn("Invalid value \"%f\" for \"active_state_timeout\". It must be positive. " |
2e442853 | 1100 | "The default of %f will be used instead.", |
5af61798 | 1101 | dvalue, config.active_state_timeout); |
2e181f93 | 1102 | else |
5af61798 | 1103 | config.active_state_timeout = dvalue; |
2e181f93 MB |
1104 | } |
1105 | ||
2e442853 | 1106 | if (config_lookup_string(config.cfg, |
277d401d MB |
1107 | "sessioncontrol.run_this_if_an_unfixable_error_is_detected", &str)) { |
1108 | config.cmd_unfixable = (char *)str; | |
1109 | } | |
1110 | ||
b365f16b MB |
1111 | if (config_lookup_string(config.cfg, "sessioncontrol.wait_for_completion", &str)) { |
1112 | if (strcasecmp(str, "no") == 0) | |
1113 | config.cmd_blocking = 0; | |
1114 | else if (strcasecmp(str, "yes") == 0) | |
1115 | config.cmd_blocking = 1; | |
1116 | else | |
b4cd4fbc | 1117 | warn("Invalid \"wait_for_completion\" option choice \"%s\". It should be " |
a382ac66 MB |
1118 | "\"yes\" or \"no\". It is set to \"no\".", |
1119 | str); | |
b365f16b | 1120 | } |
87a0475c | 1121 | |
e513e533 MB |
1122 | if (config_lookup_string(config.cfg, "sessioncontrol.before_play_begins_returns_output", |
1123 | &str)) { | |
3e9e2285 CC |
1124 | if (strcasecmp(str, "no") == 0) |
1125 | config.cmd_start_returns_output = 0; | |
1126 | else if (strcasecmp(str, "yes") == 0) | |
1127 | config.cmd_start_returns_output = 1; | |
1128 | else | |
b4cd4fbc | 1129 | die("Invalid \"before_play_begins_returns_output\" option choice \"%s\". It " |
e513e533 | 1130 | "should be " |
a382ac66 MB |
1131 | "\"yes\" or \"no\"", |
1132 | str); | |
3e9e2285 CC |
1133 | } |
1134 | ||
b365f16b MB |
1135 | if (config_lookup_string(config.cfg, "sessioncontrol.allow_session_interruption", &str)) { |
1136 | config.dont_check_timeout = 0; // this is for legacy -- only set by -t 0 | |
1137 | if (strcasecmp(str, "no") == 0) | |
1138 | config.allow_session_interruption = 0; | |
1139 | else if (strcasecmp(str, "yes") == 0) | |
1140 | config.allow_session_interruption = 1; | |
1141 | else | |
b4cd4fbc | 1142 | die("Invalid \"allow_interruption\" option choice \"%s\". It should be " |
064bd293 | 1143 | "\"yes\" " |
a382ac66 MB |
1144 | "or \"no\"", |
1145 | str); | |
b365f16b | 1146 | } |
87a0475c | 1147 | |
b365f16b MB |
1148 | if (config_lookup_int(config.cfg, "sessioncontrol.session_timeout", &value)) { |
1149 | config.timeout = value; | |
1150 | config.dont_check_timeout = 0; // this is for legacy -- only set by -t 0 | |
1151 | } | |
e513e533 | 1152 | |
7b9cd28e | 1153 | #ifdef CONFIG_CONVOLUTION |
7b9cd28e YP |
1154 | if (config_lookup_string(config.cfg, "dsp.convolution", &str)) { |
1155 | if (strcasecmp(str, "no") == 0) | |
1156 | config.convolution = 0; | |
1157 | else if (strcasecmp(str, "yes") == 0) | |
1158 | config.convolution = 1; | |
1159 | else | |
b4cd4fbc | 1160 | die("Invalid dsp.convolution setting \"%s\". It should be \"yes\" or \"no\"", str); |
7b9cd28e | 1161 | } |
e513e533 | 1162 | |
7b9cd28e YP |
1163 | if (config_lookup_float(config.cfg, "dsp.convolution_gain", &dvalue)) { |
1164 | config.convolution_gain = dvalue; | |
1165 | if (dvalue > 10 || dvalue < -50) | |
e513e533 MB |
1166 | die("Invalid value \"%f\" for dsp.convolution_gain. It should be between -50 and +10 dB", |
1167 | dvalue); | |
7b9cd28e | 1168 | } |
e513e533 | 1169 | |
7b9cd28e YP |
1170 | if (config_lookup_int(config.cfg, "dsp.convolution_max_length", &value)) { |
1171 | config.convolution_max_length = value; | |
e513e533 | 1172 | |
7b9cd28e YP |
1173 | if (value < 1 || value > 200000) |
1174 | die("dsp.convolution_max_length must be within 1 and 200000"); | |
1175 | } | |
e513e533 | 1176 | |
7b9cd28e | 1177 | if (config_lookup_string(config.cfg, "dsp.convolution_ir_file", &str)) { |
2f2442f4 | 1178 | config.convolution_ir_file = strdup(str); |
54d761ff MB |
1179 | config.convolver_valid = |
1180 | convolver_init(config.convolution_ir_file, config.convolution_max_length); | |
7b9cd28e | 1181 | } |
e513e533 | 1182 | |
7b9cd28e | 1183 | if (config.convolution && config.convolution_ir_file == NULL) { |
2f2442f4 | 1184 | warn("Convolution enabled but no convolution_ir_file provided"); |
7b9cd28e YP |
1185 | } |
1186 | #endif | |
7b9cd28e YP |
1187 | if (config_lookup_string(config.cfg, "dsp.loudness", &str)) { |
1188 | if (strcasecmp(str, "no") == 0) | |
1189 | config.loudness = 0; | |
1190 | else if (strcasecmp(str, "yes") == 0) | |
1191 | config.loudness = 1; | |
1192 | else | |
b4cd4fbc | 1193 | die("Invalid dsp.loudness \"%s\". It should be \"yes\" or \"no\"", str); |
7b9cd28e | 1194 | } |
e513e533 | 1195 | |
7b9cd28e YP |
1196 | if (config_lookup_float(config.cfg, "dsp.loudness_reference_volume_db", &dvalue)) { |
1197 | config.loudness_reference_volume_db = dvalue; | |
1198 | if (dvalue > 0 || dvalue < -100) | |
e513e533 MB |
1199 | die("Invalid value \"%f\" for dsp.loudness_reference_volume_db. It should be between " |
1200 | "-100 and 0", | |
1201 | dvalue); | |
7b9cd28e | 1202 | } |
e513e533 | 1203 | |
70a9d371 | 1204 | if (config.loudness == 1 && config_lookup_string(config.cfg, "alsa.mixer_control_name", &str)) |
e513e533 MB |
1205 | die("Loudness activated but hardware volume is active. You must remove " |
1206 | "\"alsa.mixer_control_name\" to use the loudness filter."); | |
1207 | ||
b365f16b MB |
1208 | } else { |
1209 | if (config_error_type(&config_file_stuff) == CONFIG_ERR_FILE_IO) | |
1a0cd509 | 1210 | debug(2, "Error reading configuration file \"%s\": \"%s\".", |
b365f16b MB |
1211 | config_error_file(&config_file_stuff), config_error_text(&config_file_stuff)); |
1212 | else { | |
1213 | die("Line %d of the configuration file \"%s\":\n%s", config_error_line(&config_file_stuff), | |
87a0475c | 1214 | config_error_file(&config_file_stuff), config_error_text(&config_file_stuff)); |
b365f16b | 1215 | } |
4ec9fd5b | 1216 | } |
c9b3d2a2 | 1217 | #if defined(CONFIG_DBUS_INTERFACE) |
bafdd94e MB |
1218 | /* Get the dbus service sbus setting. */ |
1219 | if (config_lookup_string(config.cfg, "general.dbus_service_bus", &str)) { | |
1220 | if (strcasecmp(str, "system") == 0) | |
1221 | config.dbus_service_bus_type = DBT_system; | |
1222 | else if (strcasecmp(str, "session") == 0) | |
1223 | config.dbus_service_bus_type = DBT_session; | |
1224 | else | |
1225 | die("Invalid dbus_service_bus option choice \"%s\". It should be \"system\" (default) or " | |
a382ac66 MB |
1226 | "\"session\"", |
1227 | str); | |
bafdd94e | 1228 | } |
74f41a17 MB |
1229 | #endif |
1230 | ||
c9b3d2a2 | 1231 | #if defined(CONFIG_MPRIS_INTERFACE) |
bafdd94e MB |
1232 | /* Get the mpris service sbus setting. */ |
1233 | if (config_lookup_string(config.cfg, "general.mpris_service_bus", &str)) { | |
1234 | if (strcasecmp(str, "system") == 0) | |
1235 | config.mpris_service_bus_type = DBT_system; | |
1236 | else if (strcasecmp(str, "session") == 0) | |
1237 | config.mpris_service_bus_type = DBT_session; | |
1238 | else | |
1239 | die("Invalid mpris_service_bus option choice \"%s\". It should be \"system\" (default) or " | |
a382ac66 MB |
1240 | "\"session\"", |
1241 | str); | |
bafdd94e | 1242 | } |
74f41a17 MB |
1243 | #endif |
1244 | ||
c9b3d2a2 | 1245 | #ifdef CONFIG_MQTT |
c2e3fa5a MB |
1246 | config_set_lookup_bool(config.cfg, "mqtt.enabled", &config.mqtt_enabled); |
1247 | if (config.mqtt_enabled && !config.metadata_enabled) { | |
1248 | die("You need to have metadata enabled in order to use mqtt"); | |
1249 | } | |
1250 | if (config_lookup_string(config.cfg, "mqtt.hostname", &str)) { | |
1251 | config.mqtt_hostname = (char *)str; | |
1252 | // TODO: Document that, if this is false, whole mqtt func is disabled | |
1253 | } | |
19fbeded DC |
1254 | config.mqtt_port = 1883; |
1255 | if (config_lookup_int(config.cfg, "mqtt.port", &value)) { | |
1256 | if ((value < 0) || (value > 65535)) | |
c8b0be30 MB |
1257 | die("Invalid mqtt port number \"%sd\". It should be between 0 and 65535, default is 1883", |
1258 | value); | |
19fbeded | 1259 | else |
c8b0be30 | 1260 | config.mqtt_port = value; |
c2e3fa5a MB |
1261 | } |
1262 | ||
1263 | if (config_lookup_string(config.cfg, "mqtt.username", &str)) { | |
1264 | config.mqtt_username = (char *)str; | |
1265 | } | |
1266 | if (config_lookup_string(config.cfg, "mqtt.password", &str)) { | |
1267 | config.mqtt_password = (char *)str; | |
1268 | } | |
1269 | int capath = 0; | |
1270 | if (config_lookup_string(config.cfg, "mqtt.capath", &str)) { | |
1271 | config.mqtt_capath = (char *)str; | |
1272 | capath = 1; | |
1273 | } | |
1274 | if (config_lookup_string(config.cfg, "mqtt.cafile", &str)) { | |
1275 | if (capath) | |
1276 | die("Supply either mqtt cafile or mqtt capath -- you have supplied both!"); | |
1277 | config.mqtt_cafile = (char *)str; | |
1278 | } | |
1279 | int certkeynum = 0; | |
1280 | if (config_lookup_string(config.cfg, "mqtt.certfile", &str)) { | |
1281 | config.mqtt_certfile = (char *)str; | |
1282 | certkeynum++; | |
1283 | } | |
1284 | if (config_lookup_string(config.cfg, "mqtt.keyfile", &str)) { | |
1285 | config.mqtt_keyfile = (char *)str; | |
1286 | certkeynum++; | |
1287 | } | |
1288 | if (certkeynum != 0 && certkeynum != 2) { | |
1289 | die("If you want to use TLS Client Authentication, you have to specify " | |
1290 | "mqtt.certfile AND mqtt.keyfile.\nYou have supplied only one of them.\n" | |
1291 | "If you do not want to use TLS Client Authentication, leave both empty."); | |
1292 | } | |
1293 | ||
1294 | if (config_lookup_string(config.cfg, "mqtt.topic", &str)) { | |
1295 | config.mqtt_topic = (char *)str; | |
1296 | } | |
1297 | config_set_lookup_bool(config.cfg, "mqtt.publish_raw", &config.mqtt_publish_raw); | |
1298 | config_set_lookup_bool(config.cfg, "mqtt.publish_parsed", &config.mqtt_publish_parsed); | |
1299 | config_set_lookup_bool(config.cfg, "mqtt.publish_cover", &config.mqtt_publish_cover); | |
60b9347a TZ |
1300 | if (config.mqtt_publish_cover && !config.get_coverart) { |
1301 | die("You need to have metadata.include_cover_art enabled in order to use mqtt.publish_cover"); | |
1302 | } | |
c2e3fa5a | 1303 | config_set_lookup_bool(config.cfg, "mqtt.enable_remote", &config.mqtt_enable_remote); |
3d720d65 MB |
1304 | if (config_lookup_string(config.cfg, "mqtt.empty_payload_substitute", &str)) { |
1305 | if (strlen(str) == 0) | |
1306 | config.mqtt_empty_payload_substitute = NULL; | |
1307 | else | |
1308 | config.mqtt_empty_payload_substitute = strdup(str); | |
1309 | } else { | |
1310 | config.mqtt_empty_payload_substitute = strdup("--"); | |
1311 | } | |
60b9347a | 1312 | #ifndef CONFIG_AVAHI |
c8b0be30 MB |
1313 | if (config.mqtt_enable_remote) { |
1314 | die("You have enabled MQTT remote control which requires shairport-sync to be built with " | |
1315 | "Avahi, but your installation is not using avahi. Please reinstall/recompile with " | |
1316 | "avahi enabled, or disable remote control."); | |
1317 | } | |
60b9347a | 1318 | #endif |
f79222f7 MB |
1319 | #endif |
1320 | ||
1321 | #ifdef CONFIG_AIRPLAY_2 | |
3d720d65 | 1322 | long long aid; |
f79222f7 MB |
1323 | |
1324 | // replace the airplay_device_id with this, if provided | |
1325 | if (config_lookup_int64(config.cfg, "general.airplay_device_id", &aid)) { | |
1326 | temporary_airplay_id = aid; | |
1327 | } | |
1328 | ||
1329 | // add the airplay_device_id_offset if provided | |
1330 | if (config_lookup_int64(config.cfg, "general.airplay_device_id_offset", &aid)) { | |
1331 | temporary_airplay_id += aid; | |
1332 | } | |
1333 | ||
3bfafa48 | 1334 | #endif |
87a0475c MB |
1335 | } |
1336 | ||
064bd293 MB |
1337 | // now, do the command line options again, but this time do them fully -- it's a unix convention |
1338 | // that command line | |
1339 | // arguments have precedence over configuration file settings. | |
726c8201 MB |
1340 | optind = argc; |
1341 | for (j = 0; j < argc; j++) | |
1342 | if (strcmp(argv[j], "--") == 0) | |
1343 | optind = j; | |
1344 | ||
1345 | optCon = poptGetContext(NULL, optind, (const char **)argv, optionsTable, 0); | |
b9d3a036 | 1346 | if (optCon == NULL) |
d805ce91 | 1347 | die("Can not get a popt context."); |
770b81af | 1348 | poptSetOtherOptionHelp(optCon, "[OPTIONS]* "); |
b8e8a22c | 1349 | |
770b81af | 1350 | /* Now do options processing, get portname */ |
726c8201 | 1351 | int tdebuglev = 0; |
770b81af MB |
1352 | while ((c = poptGetNextOpt(optCon)) >= 0) { |
1353 | switch (c) { | |
726c8201 MB |
1354 | case 'v': |
1355 | tdebuglev++; | |
1356 | break; | |
1357 | case 't': | |
1358 | if (config.timeout == 0) { | |
1359 | config.dont_check_timeout = 1; | |
1360 | config.allow_session_interruption = 1; | |
1361 | } else { | |
1362 | config.dont_check_timeout = 0; | |
1363 | config.allow_session_interruption = 0; | |
1364 | } | |
1365 | break; | |
1366 | #ifdef CONFIG_METADATA | |
1367 | case 'M': | |
1368 | config.metadata_enabled = 1; | |
1369 | break; | |
1370 | case 'g': | |
1371 | if (config.metadata_enabled == 0) | |
77a5cf1e | 1372 | die("If you want to get cover art, ensure metadata_enabled is true."); |
726c8201 MB |
1373 | break; |
1374 | #endif | |
1375 | case 'S': | |
1376 | if (strcmp(stuffing, "basic") == 0) | |
1377 | config.packet_stuffing = ST_basic; | |
9aa8f91c MB |
1378 | else if (strcmp(stuffing, "auto") == 0) |
1379 | config.packet_stuffing = ST_auto; | |
726c8201 | 1380 | else if (strcmp(stuffing, "soxr") == 0) |
c9b3d2a2 | 1381 | #ifdef CONFIG_SOXR |
726c8201 | 1382 | config.packet_stuffing = ST_soxr; |
90e97bc1 | 1383 | #else |
3001f39b MB |
1384 | die("The soxr option not available because this version of shairport-sync was built " |
1385 | "without libsoxr " | |
1f3ab8fd | 1386 | "support. Change the -S option setting."); |
90e97bc1 | 1387 | #endif |
726c8201 MB |
1388 | else |
1389 | die("Illegal stuffing option \"%s\" -- must be \"basic\" or \"soxr\"", stuffing); | |
1390 | break; | |
f39ff3a4 | 1391 | } |
770b81af MB |
1392 | } |
1393 | if (c < -1) { | |
726c8201 | 1394 | die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c)); |
770b81af | 1395 | } |
064bd293 | 1396 | |
d805ce91 | 1397 | poptFreeContext(optCon); |
6d088ac4 | 1398 | |
54d761ff | 1399 | // here, we are finally finished reading the options |
6d088ac4 | 1400 | |
f79222f7 MB |
1401 | // finish the Airplay 2 options |
1402 | ||
1403 | #ifdef CONFIG_AIRPLAY_2 | |
1404 | ||
1405 | char shared_memory_interface_name[256] = ""; | |
1406 | snprintf(shared_memory_interface_name, sizeof(shared_memory_interface_name), "/%s-%" PRIx64 "", | |
1407 | config.appName, temporary_airplay_id); | |
1408 | // debug(1, "smi name: \"%s\"", shared_memory_interface_name); | |
1409 | ||
fd880056 | 1410 | config.nqptp_shared_memory_interface_name = strdup(NQPTP_INTERFACE_NAME); |
f79222f7 MB |
1411 | |
1412 | char apids[6 * 2 + 5 + 1]; // six pairs of digits, 5 colons and a NUL | |
1413 | apids[6 * 2 + 5] = 0; // NUL termination | |
1414 | int i; | |
1415 | char hexchar[] = "0123456789abcdef"; | |
1416 | for (i = 5; i >= 0; i--) { | |
c924387a MB |
1417 | // In AirPlay 2 mode, the AP1 name prefix must be |
1418 | // the same as the AirPlay 2 device id less the colons. | |
dabfee5b | 1419 | config.ap1_prefix[i] = temporary_airplay_id & 0xFF; |
f79222f7 MB |
1420 | apids[i * 3 + 1] = hexchar[temporary_airplay_id & 0xF]; |
1421 | temporary_airplay_id = temporary_airplay_id >> 4; | |
1422 | apids[i * 3] = hexchar[temporary_airplay_id & 0xF]; | |
1423 | temporary_airplay_id = temporary_airplay_id >> 4; | |
1424 | if (i != 0) | |
1425 | apids[i * 3 - 1] = ':'; | |
1426 | } | |
1427 | ||
1428 | config.airplay_device_id = strdup(apids); | |
1429 | ||
1430 | #ifdef CONFIG_METADATA | |
1431 | // If we are asking for metadata, turn on the relevant bits | |
1432 | if (config.metadata_enabled != 0) { | |
1433 | config.airplay_features |= (1 << 17) | (1 << 16); // 16 is progress, 17 is text | |
1434 | // If we are asking for artwork, turn on the relevant bit | |
1435 | if (config.get_coverart) | |
1436 | config.airplay_features |= (1 << 15); // 15 is artwork | |
1437 | } | |
1438 | #endif | |
1439 | ||
1440 | #endif | |
1441 | ||
6d088ac4 MB |
1442 | #ifdef CONFIG_LIBDAEMON |
1443 | if ((daemonisewith) && (daemonisewithout)) | |
1444 | die("Select either daemonize_with_pid_file or daemonize_without_pid_file -- you have selected " | |
1445 | "both!"); | |
1446 | if ((daemonisewith) || (daemonisewithout)) { | |
1447 | config.daemonise = 1; | |
1448 | if (daemonisewith) | |
1449 | config.daemonise_store_pid = 1; | |
1450 | }; | |
1451 | #else | |
1452 | /* Check if we are called with -d or --daemon or -j or justDaemoniseNoPIDFile options*/ | |
1453 | if ((daemonisewith != 0) || (daemonisewithout != 0)) { | |
54d761ff MB |
1454 | fprintf(stderr, |
1455 | "%s was built without libdaemon, so does not support daemonisation using the " | |
1456 | "-d, --daemon, -j or --justDaemoniseNoPIDFile options\n", | |
c8b0be30 | 1457 | config.appName); |
57bcea52 | 1458 | exit(EXIT_FAILURE); |
6d088ac4 MB |
1459 | } |
1460 | ||
1461 | #endif | |
1462 | ||
ea723e0b | 1463 | #ifdef CONFIG_METADATA |
b413a29d MB |
1464 | if ((config.metadata_enabled == 1) && (config.metadata_pipename == NULL)) { |
1465 | char temp_metadata_pipe_name[4096]; | |
1466 | strcpy(temp_metadata_pipe_name, "/tmp/"); | |
1467 | strcat(temp_metadata_pipe_name, config.appName); | |
1468 | strcat(temp_metadata_pipe_name, "-metadata"); | |
1469 | config.metadata_pipename = strdup(temp_metadata_pipe_name); | |
0dc34a46 | 1470 | debug(2, "default metadata_pipename is \"%s\".", temp_metadata_pipe_name); |
b413a29d | 1471 | } |
ea723e0b MB |
1472 | #endif |
1473 | ||
064bd293 MB |
1474 | /* if the regtype hasn't been set, do it now */ |
1475 | if (config.regtype == NULL) | |
223fba38 | 1476 | config.regtype = strdup("_raop._tcp"); |
e1034e11 MB |
1477 | #ifdef CONFIG_AIRPLAY_2 |
1478 | if (config.regtype2 == NULL) | |
1479 | config.regtype2 = strdup("_airplay._tcp"); | |
3a02b79a | 1480 | #endif |
064bd293 MB |
1481 | |
1482 | if (tdebuglev != 0) | |
726c8201 | 1483 | debuglev = tdebuglev; |
c434f073 | 1484 | |
8201903a MB |
1485 | // now set the initial volume to the default volume |
1486 | config.airplay_volume = | |
1487 | config.default_airplay_volume; // if no volume is ever set or requested, default to initial | |
1488 | // default value if nothing else comes in first. | |
064bd293 | 1489 | // now, do the substitutions in the service name |
c434f073 MB |
1490 | char hostname[100]; |
1491 | gethostname(hostname, 100); | |
fd880056 | 1492 | |
a774a6fc | 1493 | // strip off a terminating .<anything>, e.g. .local from the hostname |
fd880056 | 1494 | char *last_dot = strrchr(hostname, '.'); |
a774a6fc MB |
1495 | if (last_dot != NULL) |
1496 | *last_dot = '\0'; | |
175ae0a6 | 1497 | |
175ae0a6 MB |
1498 | char *i0; |
1499 | if (raw_service_name == NULL) | |
1500 | i0 = strdup("%H"); // this is the default it the Service Name wasn't specified | |
1501 | else | |
1502 | i0 = strdup(raw_service_name); | |
53715857 | 1503 | |
175ae0a6 MB |
1504 | // here, do the substitutions for %h, %H, %v and %V |
1505 | char *i1 = str_replace(i0, "%h", hostname); | |
064bd293 MB |
1506 | if ((hostname[0] >= 'a') && (hostname[0] <= 'z')) |
1507 | hostname[0] = hostname[0] - 0x20; // convert a lowercase first letter into a capital letter | |
1508 | char *i2 = str_replace(i1, "%H", hostname); | |
1509 | char *i3 = str_replace(i2, "%v", PACKAGE_VERSION); | |
c434f073 | 1510 | char *vs = get_version_string(); |
175ae0a6 MB |
1511 | config.service_name = str_replace(i3, "%V", vs); // service name complete |
1512 | free(i0); | |
c434f073 MB |
1513 | free(i1); |
1514 | free(i2); | |
1515 | free(i3); | |
1516 | free(vs); | |
c2e3fa5a | 1517 | |
c9b3d2a2 | 1518 | #ifdef CONFIG_MQTT |
3bfafa48 | 1519 | // mqtt topic was not set. As we have the service name just now, set it |
c2e3fa5a MB |
1520 | if (config.mqtt_topic == NULL) { |
1521 | int topic_length = 1 + strlen(config.service_name) + 1; | |
1522 | char *topic = malloc(topic_length + 1); | |
1523 | snprintf(topic, topic_length, "/%s/", config.service_name); | |
3bfafa48 TZ |
1524 | config.mqtt_topic = topic; |
1525 | } | |
02694948 TZ |
1526 | #endif |
1527 | ||
e76cfa69 MB |
1528 | #ifdef CONFIG_LIBDAEMON |
1529 | ||
825b418b | 1530 | // now, check and calculate the pid directory |
69642bb7 | 1531 | #ifdef DEFINED_CUSTOM_PID_DIR |
5d5802db MB |
1532 | char *use_this_pid_dir = PIDDIR; |
1533 | #else | |
b413a29d MB |
1534 | char temp_pid_dir[4096]; |
1535 | strcpy(temp_pid_dir, "/var/run/"); | |
1536 | strcat(temp_pid_dir, config.appName); | |
63e0dfda | 1537 | debug(3, "Default PID directory is \"%s\".", temp_pid_dir); |
b413a29d | 1538 | char *use_this_pid_dir = temp_pid_dir; |
5d5802db MB |
1539 | #endif |
1540 | // debug(1,"config.piddir \"%s\".",config.piddir); | |
1541 | if (config.piddir) | |
1542 | use_this_pid_dir = config.piddir; | |
1543 | if (use_this_pid_dir) | |
1544 | config.computed_piddir = strdup(use_this_pid_dir); | |
e76cfa69 | 1545 | #endif |
87a0475c | 1546 | return optind + 1; |
f39ff3a4 JL |
1547 | } |
1548 | ||
c9b3d2a2 | 1549 | #if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE) |
79e22063 | 1550 | static GMainLoop *g_main_loop = NULL; |
1e07e1e0 MB |
1551 | |
1552 | pthread_t dbus_thread; | |
8991f342 | 1553 | void *dbus_thread_func(__attribute__((unused)) void *arg) { |
f1d45034 MB |
1554 | g_main_loop = g_main_loop_new(NULL, FALSE); |
1555 | g_main_loop_run(g_main_loop); | |
405a028f | 1556 | debug(2, "g_main_loop thread exit"); |
f1d45034 | 1557 | pthread_exit(NULL); |
1e07e1e0 MB |
1558 | } |
1559 | #endif | |
1560 | ||
e76cfa69 | 1561 | #ifdef CONFIG_LIBDAEMON |
0c977cea MB |
1562 | char pid_file_path_string[4096] = "\0"; |
1563 | ||
1564 | const char *pid_file_proc(void) { | |
1565 | snprintf(pid_file_path_string, sizeof(pid_file_path_string), "%s/%s.pid", config.computed_piddir, | |
1566 | daemon_pid_file_ident ? daemon_pid_file_ident : "unknown"); | |
63e0dfda | 1567 | debug(1, "PID file: \"%s\".", pid_file_path_string); |
0c977cea | 1568 | return pid_file_path_string; |
249f3561 | 1569 | } |
e76cfa69 | 1570 | #endif |
e02be8a6 | 1571 | |
3a02b79a MB |
1572 | void exit_rtsp_listener() { |
1573 | pthread_cancel(rtsp_listener_thread); | |
1574 | pthread_join(rtsp_listener_thread, NULL); // not sure you need this | |
1575 | } | |
1576 | ||
185000d0 MB |
1577 | void exit_function() { |
1578 | ||
4963c65a | 1579 | if (type_of_exit_cleanup != TOE_emergency) { |
ca562872 MB |
1580 | // the following is to ensure that if libdaemon has been included |
1581 | // that most of this code will be skipped when the parent process is exiting | |
1582 | // exec | |
185000d0 | 1583 | #ifdef CONFIG_LIBDAEMON |
ca562872 MB |
1584 | if ((this_is_the_daemon_process) || |
1585 | (config.daemonise == 0)) { // if this is the daemon process that is exiting or it's not | |
106d0b32 | 1586 | // actually daemonised at all |
185000d0 | 1587 | #endif |
ca562872 MB |
1588 | debug(2, "exit function called..."); |
1589 | /* | |
1590 | Actually, there is no terminate_mqtt() function. | |
1591 | #ifdef CONFIG_MQTT | |
1592 | if (config.mqtt_enabled) { | |
1593 | terminate_mqtt(); | |
1594 | } | |
1595 | #endif | |
1596 | */ | |
f1d45034 | 1597 | |
d4e00380 MB |
1598 | debug(2, "Stopping the activity monitor."); |
1599 | activity_monitor_stop(0); | |
1600 | debug(2, "Stopping the activity monitor done."); | |
1601 | ||
1602 | #ifdef CONFIG_DACP_CLIENT | |
1603 | debug(2, "Stopping DACP Monitor"); | |
1604 | dacp_monitor_stop(); | |
1605 | debug(2, "Stopping DACP Monitor Done"); | |
1606 | #endif | |
1607 | ||
c9b3d2a2 | 1608 | #if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE) |
ca562872 MB |
1609 | /* |
1610 | Actually, there is no stop_mpris_service() function. | |
1611 | #ifdef CONFIG_MPRIS_INTERFACE | |
1612 | stop_mpris_service(); | |
1613 | #endif | |
1614 | */ | |
c9b3d2a2 | 1615 | #ifdef CONFIG_DBUS_INTERFACE |
a68f28ac MB |
1616 | debug(2, "Stopping D-Bus service"); |
1617 | stop_dbus_service(); | |
62fca43f | 1618 | debug(2, "Stopping D-Bus service done"); |
f1d45034 | 1619 | #endif |
a68f28ac MB |
1620 | if (g_main_loop) { |
1621 | debug(2, "Stopping D-Bus Loop Thread"); | |
1622 | g_main_loop_quit(g_main_loop); | |
fd880056 MB |
1623 | |
1624 | // If the request to exit has come from the D-Bus system, | |
4963c65a | 1625 | // the D-Bus Loop Thread will not exit until the request is completed |
fd880056 | 1626 | // so don't wait for it |
4963c65a MB |
1627 | if (type_of_exit_cleanup != TOE_dbus) |
1628 | pthread_join(dbus_thread, NULL); | |
63e0dfda | 1629 | debug(2, "Stopping D-Bus Loop Thread Done"); |
a68f28ac | 1630 | } |
f1d45034 MB |
1631 | #endif |
1632 | ||
69642bb7 | 1633 | #ifdef CONFIG_METADATA_HUB |
ca562872 MB |
1634 | debug(2, "Stopping metadata hub"); |
1635 | metadata_hub_stop(); | |
63e0dfda | 1636 | debug(2, "Stopping metadata done"); |
f1d45034 MB |
1637 | #endif |
1638 | ||
1639 | #ifdef CONFIG_METADATA | |
a68f28ac MB |
1640 | debug(2, "Stopping metadata"); |
1641 | metadata_stop(); // close down the metadata pipe | |
63e0dfda | 1642 | debug(2, "Stopping metadata done"); |
f1d45034 | 1643 | #endif |
2e442853 | 1644 | |
ca562872 MB |
1645 | if ((config.output) && (config.output->deinit)) { |
1646 | debug(2, "Deinitialise the audio backend."); | |
1647 | config.output->deinit(); | |
62fca43f | 1648 | debug(2, "Deinitialise the audio backend done."); |
ca562872 | 1649 | } |
c8b0be30 | 1650 | |
175ae0a6 | 1651 | #ifdef CONFIG_SOXR |
a68f28ac | 1652 | // be careful -- not sure if the thread can be cancelled cleanly, so wait for it to shut down |
62fca43f | 1653 | if (soxr_time_check_thread_started != 0) { |
d4f1863c | 1654 | debug(2, "Waiting for SoXr timecheck to terminate..."); |
62fca43f MB |
1655 | pthread_join(soxr_time_check_thread, NULL); |
1656 | soxr_time_check_thread_started = 0; | |
d4f1863c | 1657 | debug(2, "Waiting for SoXr timecheck to terminate done"); |
62fca43f | 1658 | } |
63e0dfda | 1659 | |
175ae0a6 MB |
1660 | #endif |
1661 | ||
ca562872 MB |
1662 | if (conns) |
1663 | free(conns); // make sure the connections have been deleted first | |
53715857 | 1664 | |
ca562872 MB |
1665 | if (config.service_name) |
1666 | free(config.service_name); | |
53715857 | 1667 | |
3d720d65 MB |
1668 | #ifdef CONFIG_MQTT |
1669 | if (config.mqtt_empty_payload_substitute) | |
1670 | free(config.mqtt_empty_payload_substitute); | |
1671 | #endif | |
1672 | ||
53715857 | 1673 | #ifdef CONFIG_CONVOLUTION |
ca562872 MB |
1674 | if (config.convolution_ir_file) |
1675 | free(config.convolution_ir_file); | |
b88c9255 | 1676 | #endif |
185000d0 | 1677 | |
ca562872 MB |
1678 | if (config.regtype) |
1679 | free(config.regtype); | |
e1034e11 MB |
1680 | #ifdef CONFIG_AIRPLAY_2 |
1681 | if (config.regtype2) | |
1682 | free(config.regtype2); | |
966d68bd MB |
1683 | if (config.nqptp_shared_memory_interface_name) |
1684 | free(config.nqptp_shared_memory_interface_name); | |
1685 | if (config.airplay_device_id) | |
1686 | free(config.airplay_device_id); | |
1687 | if (config.airplay_pin) | |
1688 | free(config.airplay_pin); | |
1689 | if (config.airplay_pi) | |
1690 | free(config.airplay_pi); | |
c6fd0fbf | 1691 | ptp_shm_interface_close(); // close it if it's open |
e1034e11 MB |
1692 | #endif |
1693 | ||
e76cfa69 | 1694 | #ifdef CONFIG_LIBDAEMON |
ca562872 MB |
1695 | if (this_is_the_daemon_process) { |
1696 | daemon_retval_send(0); | |
1697 | daemon_pid_file_remove(); | |
1698 | daemon_signal_done(); | |
1699 | if (config.computed_piddir) | |
1700 | free(config.computed_piddir); | |
1701 | } | |
1702 | } | |
e76cfa69 | 1703 | #endif |
ca562872 MB |
1704 | if (config.cfg) |
1705 | config_destroy(config.cfg); | |
3cbf7739 MB |
1706 | if (config_file_real_path) |
1707 | free(config_file_real_path); | |
ca562872 MB |
1708 | if (config.appName) |
1709 | free(config.appName); | |
966d68bd | 1710 | |
ca562872 | 1711 | // probably should be freeing malloc'ed memory here, including strdup-created strings... |
05beeaab | 1712 | |
827504a3 | 1713 | #ifdef CONFIG_LIBDAEMON |
ca562872 | 1714 | if (this_is_the_daemon_process) { // this is the daemon that is exiting |
63e0dfda | 1715 | debug(1, "libdaemon daemon process exit"); |
ca562872 MB |
1716 | } else { |
1717 | if (config.daemonise) | |
63e0dfda | 1718 | debug(1, "libdaemon parent process exit"); |
ca562872 | 1719 | else |
63e0dfda | 1720 | debug(1, "normal exit"); |
ca562872 | 1721 | } |
827504a3 | 1722 | #else |
0dc34a46 MB |
1723 | mdns_unregister(); // once the dacp handler is done and all player threrads are done it should |
1724 | // be safe | |
ace5537a | 1725 | debug(1, "normal exit"); |
827504a3 | 1726 | #endif |
ca562872 MB |
1727 | } else { |
1728 | debug(1, "emergency exit"); | |
1729 | } | |
e76cfa69 MB |
1730 | } |
1731 | ||
53715857 MB |
1732 | // for removing zombie script processes |
1733 | // see: http://www.microhowto.info/howto/reap_zombie_processes_using_a_sigchld_handler.html | |
1734 | // used with thanks. | |
1735 | ||
1736 | void handle_sigchld(__attribute__((unused)) int sig) { | |
1737 | int saved_errno = errno; | |
54d761ff MB |
1738 | while (waitpid((pid_t)(-1), 0, WNOHANG) > 0) { |
1739 | } | |
53715857 MB |
1740 | errno = saved_errno; |
1741 | } | |
1742 | ||
3a02b79a MB |
1743 | // for clean exits |
1744 | void intHandler(__attribute__((unused)) int k) { | |
1745 | debug(2, "exit on SIGINT"); | |
1746 | exit(EXIT_SUCCESS); | |
1747 | } | |
1748 | ||
1749 | void termHandler(__attribute__((unused)) int k) { | |
1750 | debug(2, "exit on SIGTERM"); | |
827504a3 MB |
1751 | exit(EXIT_SUCCESS); |
1752 | } | |
1753 | ||
efa67133 MB |
1754 | void _display_config(const char *filename, const int linenumber, __attribute__((unused)) int argc, |
1755 | __attribute__((unused)) char **argv) { | |
6e9ebe6e MB |
1756 | _inform(filename, linenumber, ">> Display Config Start."); |
1757 | ||
1758 | // see the man entry on popen | |
1759 | FILE *fp; | |
1760 | int status; | |
1761 | char result[1024]; | |
1762 | ||
1763 | fp = popen("uname -a 2>/dev/null", "r"); | |
1764 | if (fp != NULL) { | |
1765 | if (fgets(result, 1024, fp) != NULL) { | |
1766 | _inform(filename, linenumber, ""); | |
1767 | _inform(filename, linenumber, "From \"uname -a\":"); | |
1768 | if (result[strlen(result) - 1] <= ' ') | |
1769 | result[strlen(result) - 1] = '\0'; // remove the last character if it's not printable | |
1770 | _inform(filename, linenumber, " %s", result); | |
1771 | } | |
1772 | status = pclose(fp); | |
1773 | if (status == -1) { | |
1774 | debug(1, "Error on pclose"); | |
1775 | } | |
1776 | } | |
1777 | ||
1778 | fp = popen("(cat /etc/os-release | grep PRETTY_NAME | sed 's/PRETTY_NAME=//' | sed 's/\"//g') " | |
1779 | "2>/dev/null", | |
1780 | "r"); | |
1781 | if (fp != NULL) { | |
1782 | if (fgets(result, 1024, fp) != NULL) { | |
1783 | _inform(filename, linenumber, ""); | |
1784 | _inform(filename, linenumber, "From /etc/os-release:"); | |
1785 | if (result[strlen(result) - 1] <= ' ') | |
1786 | result[strlen(result) - 1] = '\0'; // remove the last character if it's not printable | |
1787 | _inform(filename, linenumber, " %s", result); | |
1788 | } | |
1789 | status = pclose(fp); | |
1790 | if (status == -1) { | |
1791 | debug(1, "Error on pclose"); | |
1792 | } | |
1793 | } | |
1794 | ||
1795 | fp = popen("cat /sys/firmware/devicetree/base/model 2>/dev/null", "r"); | |
1796 | if (fp != NULL) { | |
1797 | if (fgets(result, 1024, fp) != NULL) { | |
1798 | _inform(filename, linenumber, ""); | |
1799 | _inform(filename, linenumber, "From /sys/firmware/devicetree/base/model:"); | |
1800 | _inform(filename, linenumber, " %s", result); | |
1801 | } | |
1802 | status = pclose(fp); | |
1803 | if (status == -1) { | |
1804 | debug(1, "Error on pclose"); | |
1805 | } | |
3cbf7739 | 1806 | } |
6e9ebe6e | 1807 | |
3cbf7739 MB |
1808 | char *version_string = get_version_string(); |
1809 | if (version_string) { | |
6e9ebe6e MB |
1810 | _inform(filename, linenumber, ""); |
1811 | _inform(filename, linenumber, "Shairport Sync Version String:"); | |
1812 | _inform(filename, linenumber, " %s", version_string); | |
3cbf7739 MB |
1813 | free(version_string); |
1814 | } else { | |
6e9ebe6e | 1815 | debug(1, "Can't print version string!\n"); |
3cbf7739 | 1816 | } |
efa67133 | 1817 | |
3cbf7739 | 1818 | if (argc != 0) { |
6e9ebe6e | 1819 | char *obfp = result; |
3cbf7739 | 1820 | int i; |
93c1f1ae | 1821 | for (i = 0; i < argc - 1; i++) { |
6e9ebe6e MB |
1822 | snprintf(obfp, strlen(argv[i]) + 2, "%s ", argv[i]); |
1823 | obfp += strlen(argv[i]) + 1; | |
3cbf7739 | 1824 | } |
6e9ebe6e MB |
1825 | snprintf(obfp, strlen(argv[i]) + 1, "%s", argv[i]); |
1826 | obfp += strlen(argv[i]); | |
1827 | *obfp = 0; | |
1828 | ||
1829 | _inform(filename, linenumber, ""); | |
1830 | _inform(filename, linenumber, "Command Line:"); | |
1831 | _inform(filename, linenumber, " %s", result); | |
3cbf7739 | 1832 | } |
6e9ebe6e | 1833 | |
3cbf7739 | 1834 | if (config.cfg == NULL) |
6e9ebe6e | 1835 | _inform(filename, linenumber, "No configuration file."); |
3cbf7739 | 1836 | else { |
6e9ebe6e MB |
1837 | int configpipe[2]; |
1838 | if (pipe(configpipe) == 0) { | |
1839 | FILE *cw; | |
1840 | cw = fdopen(configpipe[1], "w"); | |
1841 | _inform(filename, linenumber, ""); | |
1842 | _inform(filename, linenumber, "Configuration File:"); | |
1843 | _inform(filename, linenumber, " %s", config_file_real_path); | |
1844 | _inform(filename, linenumber, ""); | |
6e9ebe6e MB |
1845 | config_write(config.cfg, cw); |
1846 | fclose(cw); | |
efa67133 | 1847 | // get back the raw configuration file settings text |
6e9ebe6e MB |
1848 | FILE *cr; |
1849 | cr = fdopen(configpipe[0], "r"); | |
efa67133 MB |
1850 | int i = 0; |
1851 | int ch = 0; | |
1852 | do { | |
1853 | ch = fgetc(cr); | |
1854 | if (ch == EOF) { | |
1855 | result[i] = '\0'; | |
1856 | } else { | |
1857 | result[i] = (char)ch; | |
1858 | i++; | |
1859 | } | |
1860 | } while (ch != EOF); | |
6e9ebe6e | 1861 | fclose(cr); |
efa67133 MB |
1862 | // debug(1,"result is \"%s\".",result); |
1863 | // remove empty stanzas | |
1864 | char *i0 = str_replace(result, "general : \n{\n};\n", ""); | |
1865 | char *i1 = str_replace(i0, "sessioncontrol : \n{\n};\n", ""); | |
1866 | char *i2 = str_replace(i1, "alsa : \n{\n};\n", ""); | |
1867 | char *i3 = str_replace(i2, "sndio : \n{\n};\n", ""); | |
1868 | char *i4 = str_replace(i3, "pa : \n{\n};\n", ""); | |
1869 | char *i5 = str_replace(i4, "jack : \n{\n};\n", ""); | |
1870 | char *i6 = str_replace(i5, "pipe : \n{\n};\n", ""); | |
1871 | char *i7 = str_replace(i6, "dsp : \n{\n};\n", ""); | |
1872 | char *i8 = str_replace(i7, "metadata : \n{\n};\n", ""); | |
1873 | char *i9 = str_replace(i8, "mqtt : \n{\n};\n", ""); | |
1874 | char *i10 = str_replace(i9, "diagnostics : \n{\n};\n", ""); | |
1875 | // debug(1,"i10 is \"%s\".",i10); | |
1876 | ||
1877 | // free intermediate strings | |
1878 | free(i9); | |
1879 | free(i8); | |
1880 | free(i7); | |
1881 | free(i6); | |
1882 | free(i5); | |
1883 | free(i4); | |
1884 | free(i3); | |
1885 | free(i2); | |
1886 | free(i1); | |
1887 | free(i0); | |
1888 | ||
1889 | // print it out | |
1890 | if (strlen(i10) == 0) | |
1891 | _inform(filename, linenumber, "The Configuration file contains no active settings."); | |
1892 | else { | |
1893 | _inform(filename, linenumber, "Configuration File Settings:"); | |
1894 | char *p = i10; | |
1895 | while (*p != '\0') { | |
1896 | i = 0; | |
1897 | while ((*p != '\0') && (*p != '\n')) { | |
1898 | result[i] = *p; | |
1899 | p++; | |
1900 | i++; | |
1901 | } | |
1902 | if (i != 0) { | |
1903 | result[i] = '\0'; | |
1904 | _inform(filename, linenumber, " %s", result); | |
1905 | } | |
1906 | if (*p == '\n') | |
1907 | p++; | |
1908 | } | |
1909 | } | |
1910 | ||
1911 | free(i10); // free the cleaned-up configuration string | |
1912 | ||
1913 | /* | |
1914 | while (fgets(result, 1024, cr) != NULL) { | |
1915 | // replace funny character at the end, if it's there | |
1916 | if (result[strlen(result) - 1] <= ' ') | |
1917 | result[strlen(result) - 1] = '\0'; // remove the last character if it's not | |
1918 | printable _inform(filename, linenumber, " %s", result); | |
1919 | } | |
1920 | */ | |
6e9ebe6e MB |
1921 | } else { |
1922 | debug(1, "Error making pipe.\n"); | |
1923 | } | |
3cbf7739 | 1924 | } |
6e9ebe6e MB |
1925 | _inform(filename, linenumber, ""); |
1926 | _inform(filename, linenumber, ">> Display Config End."); | |
3cbf7739 MB |
1927 | } |
1928 | ||
6e9ebe6e MB |
1929 | #define display_config(argc, argv) _display_config(__FILE__, __LINE__, argc, argv) |
1930 | ||
a2fb5d21 | 1931 | int main(int argc, char **argv) { |
919c4a41 | 1932 | memset(&config, 0, sizeof(config)); // also clears all strings, BTW |
2e7e5b68 HN |
1933 | /* Check if we are called with -V or --version parameter */ |
1934 | if (argc >= 2 && ((strcmp(argv[1], "-V") == 0) || (strcmp(argv[1], "--version") == 0))) { | |
1935 | print_version(); | |
14de9e27 | 1936 | exit(EXIT_SUCCESS); |
2e7e5b68 | 1937 | } |
6e9ebe6e | 1938 | |
3cbf7739 MB |
1939 | // this is a bit weird, but necessary -- basename() may modify the argument passed in |
1940 | char *basec = strdup(argv[0]); | |
1941 | char *bname = basename(basec); | |
1942 | config.appName = strdup(bname); | |
1943 | if (config.appName == NULL) | |
1944 | die("can not allocate memory for the app name!"); | |
1945 | free(basec); | |
1946 | ||
1947 | strcpy(configuration_file_path, SYSCONFDIR); | |
1948 | // strcat(configuration_file_path, "/shairport-sync"); // thinking about adding a special | |
1949 | // shairport-sync directory | |
1950 | strcat(configuration_file_path, "/"); | |
1951 | strcat(configuration_file_path, config.appName); | |
1952 | strcat(configuration_file_path, ".conf"); | |
1953 | config.configfile = configuration_file_path; | |
2e7e5b68 | 1954 | |
38c43f07 | 1955 | #ifdef CONFIG_AIRPLAY_2 |
e1228c4f MB |
1956 | #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(53, 10, 0) |
1957 | avcodec_init(); | |
1958 | #endif | |
1959 | #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100) | |
38c43f07 | 1960 | avcodec_register_all(); |
e1228c4f | 1961 | #endif |
38c43f07 MB |
1962 | #endif |
1963 | ||
2e7e5b68 HN |
1964 | /* Check if we are called with -h or --help parameter */ |
1965 | if (argc >= 2 && ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0))) { | |
1966 | usage(argv[0]); | |
14de9e27 | 1967 | exit(EXIT_SUCCESS); |
2e7e5b68 HN |
1968 | } |
1969 | ||
0dc34a46 MB |
1970 | /* Check if we are called with -log-to-syslog */ |
1971 | if (argc >= 2 && (strcmp(argv[1], "--log-to-syslog") == 0)) { | |
1972 | log_to_syslog_select_is_first_command_line_argument = 1; | |
1973 | log_to_syslog(); | |
1974 | } else { | |
1975 | log_to_stderr(); | |
1976 | } | |
2e7e5b68 | 1977 | |
593c507d | 1978 | pid = getpid(); |
ca562872 | 1979 | config.log_fd = -1; |
f1d45034 | 1980 | conns = NULL; // no connections active |
67e9b1b6 MB |
1981 | ns_time_at_startup = get_absolute_time_in_ns(); |
1982 | ns_time_at_last_debug_message = ns_time_at_startup; | |
a54569f3 | 1983 | |
e76cfa69 MB |
1984 | #ifdef CONFIG_LIBDAEMON |
1985 | daemon_set_verbosity(LOG_DEBUG); | |
1986 | #else | |
c8b0be30 MB |
1987 | setlogmask(LOG_UPTO(LOG_DEBUG)); |
1988 | openlog(NULL, 0, LOG_DAEMON); | |
e76cfa69 | 1989 | #endif |
4963c65a | 1990 | type_of_exit_cleanup = TOE_normal; // what kind of exit cleanup needed |
e76cfa69 | 1991 | atexit(exit_function); |
6088c244 | 1992 | |
87a0475c | 1993 | // set defaults |
064bd293 | 1994 | |
e1034e11 MB |
1995 | // get a device id -- the first non-local MAC address |
1996 | get_device_id((uint8_t *)&config.hw_addr, 6); | |
1997 | ||
e76cfa69 | 1998 | // get the endianness |
73c3744e | 1999 | union { |
064bd293 MB |
2000 | uint32_t u32; |
2001 | uint8_t arr[4]; | |
73c3744e MB |
2002 | } xn; |
2003 | ||
064bd293 | 2004 | xn.arr[0] = 0x44; /* Lowest-address byte */ |
73c3744e MB |
2005 | xn.arr[1] = 0x33; |
2006 | xn.arr[2] = 0x22; | |
064bd293 MB |
2007 | xn.arr[3] = 0x11; /* Highest-address byte */ |
2008 | ||
2009 | if (xn.u32 == 0x11223344) | |
1d32976d | 2010 | config.endianness = SS_LITTLE_ENDIAN; |
064bd293 | 2011 | else if (xn.u32 == 0x33441122) |
1d32976d | 2012 | config.endianness = SS_PDP_ENDIAN; |
064bd293 | 2013 | else if (xn.u32 == 0x44332211) |
1d32976d | 2014 | config.endianness = SS_BIG_ENDIAN; |
064bd293 MB |
2015 | else |
2016 | die("Can not recognise the endianness of the processor."); | |
3001f39b | 2017 | |
81a02221 MB |
2018 | // set non-zero / non-NULL default values here |
2019 | // but note that audio back ends also have a chance to set defaults | |
064bd293 | 2020 | |
ca562872 MB |
2021 | // get the first output backend in the list and make it the default |
2022 | audio_output *first_backend = audio_get_output(NULL); | |
2023 | if (first_backend == NULL) { | |
2024 | die("No audio backend found! Check your build of Shairport Sync."); | |
2025 | } else { | |
2026 | strncpy(first_backend_name, first_backend->name, sizeof(first_backend_name) - 1); | |
2027 | config.output_name = first_backend_name; | |
2028 | } | |
2029 | ||
e0aa75a8 MB |
2030 | // config.statistics_requested = 0; // don't print stats in the log |
2031 | // config.userSuppliedLatency = 0; // zero means none supplied | |
c8b0be30 | 2032 | |
db8f10cf | 2033 | config.debugger_show_file_and_line = |
54d761ff | 2034 | 1; // by default, log the file and line of the originating message |
c8b0be30 | 2035 | config.debugger_show_relative_time = |
d2ca8c84 | 2036 | 1; // by default, log the time back to the previous debug message |
87a0475c | 2037 | config.timeout = 120; // this number of seconds to wait for [more] audio before switching to idle. |
87a0475c | 2038 | config.buffer_start_fill = 220; |
d2ca8c84 MB |
2039 | |
2040 | config.resync_threshold = 0.050; // default | |
28f54af2 MB |
2041 | config.resync_recovery_time = 0.1; // drop this amount of frames following the resync delay. |
2042 | config.tolerance = 0.002; | |
2043 | ||
6760bab4 | 2044 | #ifdef CONFIG_AIRPLAY_2 |
3a02b79a MB |
2045 | config.timeout = 0; // disable watchdog |
2046 | config.port = 7000; | |
3a02b79a | 2047 | #else |
87a0475c | 2048 | config.port = 5000; |
3a02b79a | 2049 | #endif |
16235071 | 2050 | |
178af21d | 2051 | #ifdef CONFIG_SOXR |
c8b0be30 MB |
2052 | config.packet_stuffing = ST_auto; // use soxr interpolation by default if support has been |
2053 | // included and if the CPU is fast enough | |
178af21d | 2054 | #else |
87a0475c | 2055 | config.packet_stuffing = ST_basic; // simple interpolation or deletion |
178af21d | 2056 | #endif |
9aa8f91c | 2057 | |
064bd293 MB |
2058 | // char hostname[100]; |
2059 | // gethostname(hostname, 100); | |
2060 | // config.service_name = malloc(20 + 100); | |
2061 | // snprintf(config.service_name, 20 + 100, "Shairport Sync on %s", hostname); | |
2062 | set_requested_connection_state_to_output( | |
2063 | 1); // we expect to be able to connect to the output device | |
7e01da54 | 2064 | config.audio_backend_buffer_desired_length = 0.15; // seconds |
61315e61 | 2065 | config.udp_port_base = 6001; |
093bf1dd | 2066 | config.udp_port_range = 10; |
1d32976d | 2067 | config.output_format = SPS_FORMAT_S16_LE; // default |
83c0405d | 2068 | config.output_format_auto_requested = 1; // default auto select format |
1d32976d | 2069 | config.output_rate = 44100; // default |
83c0405d | 2070 | config.output_rate_auto_requested = 1; // default auto select format |
064bd293 MB |
2071 | config.decoders_supported = |
2072 | 1 << decoder_hammerton; // David Hammerton's decoder supported by default | |
c9b3d2a2 | 2073 | #ifdef CONFIG_APPLE_ALAC |
064bd293 | 2074 | config.decoders_supported += 1 << decoder_apple_alac; |
178af21d | 2075 | config.use_apple_decoder = 1; // use the ALAC decoder by default if support has been included |
064bd293 MB |
2076 | #endif |
2077 | ||
2078 | // initialise random number generator | |
2079 | ||
2080 | r64init(0); | |
cf29625d | 2081 | |
e76cfa69 MB |
2082 | #ifdef CONFIG_LIBDAEMON |
2083 | ||
87a0475c MB |
2084 | /* Reset signal handlers */ |
2085 | if (daemon_reset_sigs(-1) < 0) { | |
2086 | daemon_log(LOG_ERR, "Failed to reset all signal handlers: %s", strerror(errno)); | |
2087 | return 1; | |
2088 | } | |
2089 | ||
2090 | /* Unblock signals */ | |
2091 | if (daemon_unblock_sigs(-1) < 0) { | |
2092 | daemon_log(LOG_ERR, "Failed to unblock all signals: %s", strerror(errno)); | |
2093 | return 1; | |
2094 | } | |
d89e516c | 2095 | |
b9cf91b2 | 2096 | /* Set identification string for the daemon for both syslog and PID file */ |
87a0475c MB |
2097 | daemon_pid_file_ident = daemon_log_ident = daemon_ident_from_argv0(argv[0]); |
2098 | ||
0c977cea | 2099 | daemon_pid_file_proc = pid_file_proc; |
c2e3fa5a | 2100 | |
e76cfa69 | 2101 | #endif |
6195be01 MB |
2102 | // parse arguments into config -- needed to locate pid_dir |
2103 | int audio_arg = parse_options(argc, argv); | |
2104 | ||
e76cfa69 MB |
2105 | // mDNS supports maximum of 63-character names (we append 13). |
2106 | if (strlen(config.service_name) > 50) { | |
0dc34a46 MB |
2107 | warn("The service name \"%s\" is too long (max 50 characters) and has been truncated.", |
2108 | config.service_name); | |
e76cfa69 MB |
2109 | config.service_name[50] = '\0'; // truncate it and carry on... |
2110 | } | |
6e9ebe6e | 2111 | |
3cbf7739 MB |
2112 | if (display_config_selected != 0) { |
2113 | display_config(argc, argv); | |
2114 | if (argc == 2) { | |
5e6e6344 | 2115 | inform(">> Goodbye!"); |
3cbf7739 MB |
2116 | exit(EXIT_SUCCESS); |
2117 | } | |
2118 | } | |
e76cfa69 | 2119 | |
6d088ac4 | 2120 | /* Check if we are called with -k or --kill option */ |
c8b0be30 | 2121 | if (killOption != 0) { |
e76cfa69 | 2122 | #ifdef CONFIG_LIBDAEMON |
87a0475c MB |
2123 | int ret; |
2124 | ||
2125 | /* Kill daemon with SIGTERM */ | |
2126 | /* Check if the new function daemon_pid_file_kill_wait() is available, if it is, use it. */ | |
827504a3 | 2127 | if ((ret = daemon_pid_file_kill_wait(SIGTERM, 5)) < 0) { |
05beeaab | 2128 | if (errno == ENOENT) |
63e0dfda MB |
2129 | warn("Failed to kill the %s daemon. The PID file was not found.", config.appName); |
2130 | // daemon_log(LOG_WARNING, "Failed to kill %s daemon: PID file not found.", config.appName); | |
827504a3 | 2131 | else |
63e0dfda MB |
2132 | warn("Failed to kill the %s daemon. Error: \"%s\", errno %u.", config.appName, |
2133 | strerror(errno), errno); | |
2134 | // daemon_log(LOG_WARNING, "Failed to kill %s daemon: \"%s\", errno %u.", config.appName, | |
2135 | // strerror(errno), errno); | |
593c507d | 2136 | } |
87a0475c | 2137 | return ret < 0 ? 1 : 0; |
e76cfa69 | 2138 | #else |
63e0dfda MB |
2139 | warn("%s was built without libdaemon, so it does not support the -k or --kill option.", |
2140 | config.appName); | |
e76cfa69 MB |
2141 | return 1; |
2142 | #endif | |
87a0475c | 2143 | } |
d89e516c | 2144 | |
e76cfa69 | 2145 | #ifdef CONFIG_LIBDAEMON |
6195be01 MB |
2146 | /* If we are going to daemonise, check that the daemon is not running already.*/ |
2147 | if ((config.daemonise) && ((pid = daemon_pid_file_is_running()) >= 0)) { | |
63e0dfda MB |
2148 | warn("The %s deamon is already running with process ID (PID) %u.", config.appName, pid); |
2149 | // daemon_log(LOG_ERR, "The %s daemon is already running as PID %u", config.appName, pid); | |
87a0475c MB |
2150 | return 1; |
2151 | } | |
2152 | ||
87a0475c MB |
2153 | /* here, daemonise with libdaemon */ |
2154 | ||
2155 | if (config.daemonise) { | |
2156 | /* Prepare for return value passing from the initialization procedure of the daemon process */ | |
2157 | if (daemon_retval_init() < 0) { | |
63e0dfda | 2158 | die("Failed to create pipe."); |
d89e516c MB |
2159 | } |
2160 | ||
87a0475c MB |
2161 | /* Do the fork */ |
2162 | if ((pid = daemon_fork()) < 0) { | |
2163 | ||
2164 | /* Exit on error */ | |
2165 | daemon_retval_done(); | |
2166 | return 1; | |
2167 | ||
2168 | } else if (pid) { /* The parent */ | |
2169 | int ret; | |
2170 | ||
2171 | /* Wait for 20 seconds for the return value passed from the daemon process */ | |
2172 | if ((ret = daemon_retval_wait(20)) < 0) { | |
63e0dfda | 2173 | die("Could not receive return value from daemon process: %s", strerror(errno)); |
d89e516c MB |
2174 | } |
2175 | ||
825b418b MB |
2176 | switch (ret) { |
2177 | case 0: | |
2178 | break; | |
2179 | case 1: | |
63e0dfda MB |
2180 | warn("The %s daemon failed to launch: could not close open file descriptors after forking.", |
2181 | config.appName); | |
825b418b MB |
2182 | break; |
2183 | case 2: | |
63e0dfda | 2184 | warn("The %s daemon failed to launch: could not create PID file.", config.appName); |
825b418b MB |
2185 | break; |
2186 | case 3: | |
63e0dfda MB |
2187 | warn("The %s daemon failed to launch: could not create or access PID directory.", |
2188 | config.appName); | |
825b418b MB |
2189 | break; |
2190 | default: | |
63e0dfda | 2191 | warn("The %s daemon failed to launch, error %i.", config.appName, ret); |
825b418b | 2192 | } |
87a0475c | 2193 | return ret; |
593c507d | 2194 | } else { /* pid == 0 means we are the daemon */ |
53715857 | 2195 | |
63e0dfda MB |
2196 | this_is_the_daemon_process = 1; |
2197 | if (log_to_default != 0) // if a specific logging mode has not been selected | |
2198 | log_to_syslog(); // automatically send logs to the daemon_log | |
87a0475c MB |
2199 | |
2200 | /* Close FDs */ | |
2201 | if (daemon_close_all(-1) < 0) { | |
63e0dfda | 2202 | warn("Failed to close all file descriptors while daemonising. Error: %s", strerror(errno)); |
87a0475c MB |
2203 | /* Send the error condition to the parent process */ |
2204 | daemon_retval_send(1); | |
c8c70b60 MB |
2205 | daemon_signal_done(); |
2206 | return 0; | |
d89e516c MB |
2207 | } |
2208 | ||
d022c8b4 MB |
2209 | /* Create the PID file if required */ |
2210 | if (config.daemonise_store_pid) { | |
5d5802db | 2211 | /* Create the PID directory if required -- we don't really care about the result */ |
63e0dfda | 2212 | debug(1, "PID directory is \"%s\".", config.computed_piddir); |
825b418b MB |
2213 | int result = mkpath(config.computed_piddir, 0700); |
2214 | if ((result != 0) && (result != -EEXIST)) { | |
2215 | // error creating or accessing the PID file directory | |
63e0dfda MB |
2216 | warn("Failed to create the directory \"%s\" for the PID file. Error: %s.", |
2217 | config.computed_piddir, strerror(errno)); | |
825b418b | 2218 | daemon_retval_send(3); |
c8c70b60 MB |
2219 | daemon_signal_done(); |
2220 | return 0; | |
825b418b | 2221 | } |
c8b0be30 | 2222 | |
d022c8b4 | 2223 | if (daemon_pid_file_create() < 0) { |
63e0dfda MB |
2224 | // daemon_log(LOG_ERR, "Could not create PID file (%s).", strerror(errno)); |
2225 | warn("Failed to create the PID file. Error: %s.", strerror(errno)); | |
d022c8b4 | 2226 | daemon_retval_send(2); |
c8c70b60 MB |
2227 | daemon_signal_done(); |
2228 | return 0; | |
d022c8b4 | 2229 | } |
d89e516c | 2230 | } |
b70505fd | 2231 | |
87a0475c MB |
2232 | /* Send OK to parent process */ |
2233 | daemon_retval_send(0); | |
24d81c04 | 2234 | } |
87a0475c MB |
2235 | /* end libdaemon stuff */ |
2236 | } | |
24d81c04 | 2237 | |
e76cfa69 | 2238 | #endif |
53715857 | 2239 | |
3a02b79a | 2240 | #ifdef CONFIG_AIRPLAY_2 |
58fdb135 | 2241 | |
0c169208 | 2242 | if (has_fltp_capable_aac_decoder() == 0) { |
0dc34a46 MB |
2243 | die("Shairport Sync can not run on this system. Run \"shairport-sync -h\" for more " |
2244 | "information."); | |
ed629037 | 2245 | } |
58fdb135 | 2246 | |
f79222f7 MB |
2247 | uint64_t apf = config.airplay_features; |
2248 | uint64_t apfh = config.airplay_features; | |
2249 | apfh = apfh >> 32; | |
2250 | uint32_t apf32 = apf; | |
2251 | uint32_t apfh32 = apfh; | |
63e0dfda | 2252 | debug(1, "Startup in AirPlay 2 mode, with features 0x%" PRIx32 ",0x%" PRIx32 " on device \"%s\".", |
f79222f7 | 2253 | apf32, apfh32, config.airplay_device_id); |
3a02b79a | 2254 | #else |
63e0dfda | 2255 | debug(1, "Startup in classic Airplay (aka \"AirPlay 1\") mode."); |
3a02b79a MB |
2256 | #endif |
2257 | ||
2258 | // control-c (SIGINT) cleanly | |
2259 | struct sigaction act; | |
2260 | memset(&act, 0, sizeof(struct sigaction)); | |
2261 | act.sa_handler = intHandler; | |
2262 | sigaction(SIGINT, &act, NULL); | |
2263 | ||
2264 | // terminate (SIGTERM) | |
2265 | struct sigaction act2; | |
2266 | memset(&act2, 0, sizeof(struct sigaction)); | |
2267 | act2.sa_handler = termHandler; | |
2268 | sigaction(SIGTERM, &act2, NULL); | |
2269 | ||
ca562872 MB |
2270 | // stop a pipe signal from killing the program |
2271 | signal(SIGPIPE, SIG_IGN); | |
e8ef0e93 | 2272 | |
53715857 MB |
2273 | // install a zombie process reaper |
2274 | // see: http://www.microhowto.info/howto/reap_zombie_processes_using_a_sigchld_handler.html | |
2275 | struct sigaction sa; | |
2276 | sa.sa_handler = &handle_sigchld; | |
2277 | sigemptyset(&sa.sa_mask); | |
2278 | sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; | |
2279 | if (sigaction(SIGCHLD, &sa, 0) == -1) { | |
2280 | perror(0); | |
2281 | exit(1); | |
2282 | } | |
2283 | ||
87a0475c MB |
2284 | // make sure the program can create files that group and world can read |
2285 | umask(S_IWGRP | S_IWOTH); | |
2286 | ||
1b1348e1 MB |
2287 | /* print out version */ |
2288 | ||
2289 | char *version_dbs = get_version_string(); | |
2290 | if (version_dbs) { | |
63e0dfda | 2291 | debug(1, "Version String: \"%s\"", version_dbs); |
1b1348e1 MB |
2292 | free(version_dbs); |
2293 | } else { | |
63e0dfda | 2294 | debug(1, "Can't print the version information!"); |
1b1348e1 | 2295 | } |
63e0dfda | 2296 | |
abc0e093 MB |
2297 | // print command line |
2298 | ||
2299 | if (argc != 0) { | |
2300 | char result[1024]; | |
2301 | char *obfp = result; | |
2302 | int i; | |
2303 | for (i = 0; i < argc - 1; i++) { | |
2304 | snprintf(obfp, strlen(argv[i]) + 2, "%s ", argv[i]); | |
2305 | obfp += strlen(argv[i]) + 1; | |
2306 | } | |
2307 | snprintf(obfp, strlen(argv[i]) + 1, "%s", argv[i]); | |
2308 | obfp += strlen(argv[i]); | |
2309 | *obfp = 0; | |
63e0dfda | 2310 | debug(1, "Command Line: \"%s\".", result); |
73c3744e | 2311 | } |
064bd293 | 2312 | |
3a02b79a MB |
2313 | #ifdef CONFIG_AIRPLAY_2 |
2314 | if (sodium_init() < 0) { | |
2315 | debug(1, "Can't initialise libsodium!"); | |
2316 | } else { | |
07211e7e | 2317 | debug(2, "libsodium initialised."); |
3a02b79a | 2318 | } |
1032cad6 | 2319 | |
0cc97d50 MB |
2320 | // this code is based on |
2321 | // https://www.gnupg.org/documentation/manuals/gcrypt/Initializing-the-library.html | |
2322 | ||
2323 | /* Version check should be the very first call because it | |
2324 | makes sure that important subsystems are initialized. | |
2325 | #define NEED_LIBGCRYPT_VERSION to the minimum required version. */ | |
2326 | ||
2327 | #define NEED_LIBGCRYPT_VERSION "1.5.4" | |
2328 | ||
2329 | if (!gcry_check_version(NEED_LIBGCRYPT_VERSION)) { | |
2330 | die("libgcrypt is too old (need %s, have %s).", NEED_LIBGCRYPT_VERSION, | |
2331 | gcry_check_version(NULL)); | |
2332 | } | |
2333 | ||
2334 | /* Disable secure memory. */ | |
2335 | gcry_control(GCRYCTL_DISABLE_SECMEM, 0); | |
2336 | ||
2337 | /* ... If required, other initialization goes here. */ | |
2338 | ||
2339 | /* Tell Libgcrypt that initialization has completed. */ | |
2340 | gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); | |
2341 | ||
07211e7e | 2342 | debug(2, "libgcrypt initialised."); |
63e0dfda | 2343 | |
3a02b79a MB |
2344 | #endif |
2345 | ||
63e0dfda MB |
2346 | debug(1, "Log Verbosity is %d.", debuglev); |
2347 | ||
2348 | config.output = audio_get_output(config.output_name); | |
2349 | if (!config.output) { | |
2350 | die("Invalid audio backend \"%s\" selected!", | |
2351 | config.output_name == NULL ? "<unspecified>" : config.output_name); | |
2352 | } | |
2353 | config.output->init(argc - audio_arg, argv + audio_arg); | |
2354 | ||
2355 | // pthread_cleanup_push(main_cleanup_handler, NULL); | |
2356 | ||
2357 | // daemon_log(LOG_NOTICE, "startup"); | |
2358 | ||
2359 | switch (config.endianness) { | |
2360 | case SS_LITTLE_ENDIAN: | |
2361 | debug(2, "The processor is running little-endian."); | |
2362 | break; | |
2363 | case SS_BIG_ENDIAN: | |
2364 | debug(2, "The processor is running big-endian."); | |
2365 | break; | |
2366 | case SS_PDP_ENDIAN: | |
2367 | debug(2, "The processor is running pdp-endian."); | |
2368 | break; | |
2369 | } | |
2370 | ||
440b592f | 2371 | /* Mess around with the latency options */ |
e0aa75a8 MB |
2372 | // Basically, we expect the source to set the latency and add a fixed offset of 11025 frames to |
2373 | // it, which sounds right | |
2374 | // If this latency is outside the max and min latensies that may be set by the source, clamp it to | |
2375 | // fit. | |
2376 | ||
064bd293 MB |
2377 | // If they specify a non-standard latency, we suggest the user to use the |
2378 | // audio_backend_latency_offset instead. | |
2379 | ||
440b592f | 2380 | if (config.userSuppliedLatency) { |
b9d3a036 MB |
2381 | inform("The fixed latency setting is deprecated, as Shairport Sync gets the correct " |
2382 | "latency automatically from the source."); | |
2383 | inform("Use the audio_backend_latency_offset_in_seconds setting " | |
2384 | "instead to compensate for timing issues."); | |
2385 | if ((config.userSuppliedLatency != 0) && | |
2386 | ((config.userSuppliedLatency < 4410) || | |
2387 | (config.userSuppliedLatency > BUFFER_FRAMES * 352 - 22050))) | |
2388 | die("An out-of-range fixed latency has been specified. It must be between 4410 and %d (at " | |
2389 | "44100 frames per second).", | |
2390 | BUFFER_FRAMES * 352 - 22050); | |
440b592f | 2391 | } |
064bd293 | 2392 | |
38281dd1 | 2393 | /* Print out options */ |
63e0dfda | 2394 | debug(1, "disable_resend_requests is %s.", config.disable_resend_requests ? "on" : "off"); |
54d761ff MB |
2395 | debug(1, |
2396 | "diagnostic_drop_packet_fraction is %f. A value of 0.0 means no packets will be dropped " | |
2397 | "deliberately.", | |
2737222b | 2398 | config.diagnostic_drop_packet_fraction); |
6f93f55d | 2399 | debug(1, "statistics_requester status is %d.", config.statistics_requested); |
e76cfa69 | 2400 | #if CONFIG_LIBDAEMON |
6f93f55d | 2401 | debug(1, "daemon status is %d.", config.daemonise); |
b9cf91b2 | 2402 | debug(1, "daemon pid file path is \"%s\".", pid_file_proc()); |
e76cfa69 | 2403 | #endif |
6f93f55d MB |
2404 | debug(1, "rtsp listening port is %d.", config.port); |
2405 | debug(1, "udp base port is %d.", config.udp_port_base); | |
2406 | debug(1, "udp port range is %d.", config.udp_port_range); | |
5663858c MB |
2407 | debug(1, "player name is \"%s\".", config.service_name); |
2408 | debug(1, "backend is \"%s\".", config.output_name); | |
3b91065c AL |
2409 | debug(1, "run_this_before_play_begins action is \"%s\".", strnull(config.cmd_start)); |
2410 | debug(1, "run_this_after_play_ends action is \"%s\".", strnull(config.cmd_stop)); | |
6f93f55d | 2411 | debug(1, "wait-cmd status is %d.", config.cmd_blocking); |
550f22a3 | 2412 | debug(1, "run_this_before_play_begins may return output is %d.", config.cmd_start_returns_output); |
966d68bd MB |
2413 | debug(1, "run_this_if_an_unfixable_error_is_detected action is \"%s\".", |
2414 | strnull(config.cmd_unfixable)); | |
2415 | debug(1, "run_this_before_entering_active_state action is \"%s\".", | |
2416 | strnull(config.cmd_active_start)); | |
2417 | debug(1, "run_this_after_exiting_active_state action is \"%s\".", | |
2418 | strnull(config.cmd_active_stop)); | |
5af61798 | 2419 | debug(1, "active_state_timeout is %f seconds.", config.active_state_timeout); |
3b91065c | 2420 | debug(1, "mdns backend \"%s\".", strnull(config.mdns_name)); |
38281dd1 | 2421 | debug(2, "userSuppliedLatency is %d.", config.userSuppliedLatency); |
c8b0be30 | 2422 | debug(1, "interpolation setting is \"%s\".", |
8201903a MB |
2423 | config.packet_stuffing == ST_basic ? "basic" |
2424 | : config.packet_stuffing == ST_soxr ? "soxr" | |
2425 | : "auto"); | |
9aa8f91c | 2426 | debug(1, "interpolation soxr_delay_threshold is %d.", config.soxr_delay_threshold); |
28f54af2 MB |
2427 | debug(1, "resync time is %f seconds.", config.resync_threshold); |
2428 | debug(1, "resync recovery time is %f seconds.", config.resync_recovery_time); | |
6f93f55d MB |
2429 | debug(1, "allow a session to be interrupted: %d.", config.allow_session_interruption); |
2430 | debug(1, "busy timeout time is %d.", config.timeout); | |
ae84366e | 2431 | debug(1, "drift tolerance is %f seconds.", config.tolerance); |
3b91065c | 2432 | debug(1, "password is \"%s\".", strnull(config.password)); |
17c39797 MB |
2433 | debug(1, "default airplay volume is: %.6f.", config.default_airplay_volume); |
2434 | debug(1, "high threshold airplay volume is: %.6f.", config.high_threshold_airplay_volume); | |
72f1554c MB |
2435 | if (config.limit_to_high_volume_threshold_time_in_minutes == 0) |
2436 | debug(1, "check for higher-than-threshold volume for new play session is disabled."); | |
2437 | else | |
2438 | debug(1, | |
17c39797 MB |
2439 | "suggest default airplay volume for new play sessions instead of higher-than-threshold " |
2440 | "airplay volume after: %d minutes.", | |
72f1554c | 2441 | config.limit_to_high_volume_threshold_time_in_minutes); |
6f93f55d | 2442 | debug(1, "ignore_volume_control is %d.", config.ignore_volume_control); |
7e831237 MB |
2443 | if (config.volume_max_db_set) |
2444 | debug(1, "volume_max_db is %d.", config.volume_max_db); | |
2445 | else | |
2446 | debug(1, "volume_max_db is not set"); | |
3d1ee2e0 MB |
2447 | debug(1, "volume range in dB (zero means use the range specified by the mixer): %u.", |
2448 | config.volume_range_db); | |
54d761ff MB |
2449 | debug(1, |
2450 | "volume_range_combined_hardware_priority (1 means hardware mixer attenuation is used " | |
2451 | "first) is %d.", | |
c8b0be30 | 2452 | config.volume_range_hw_priority); |
c80d59b3 MB |
2453 | debug(1, "playback_mode is %d (0-stereo, 1-mono, 1-reverse_stereo, 2-both_left, 3-both_right).", |
2454 | config.playback_mode); | |
6f93f55d | 2455 | debug(1, "disable_synchronization is %d.", config.no_sync); |
c1e07b98 | 2456 | debug(1, "use_mmap_if_available is %d.", config.no_mmap ? 0 : 1); |
c8b0be30 MB |
2457 | debug(1, "output_format automatic selection is %sabled.", |
2458 | config.output_format_auto_requested ? "en" : "dis"); | |
83c0405d | 2459 | if (config.output_format_auto_requested == 0) |
c8b0be30 MB |
2460 | debug(1, "output_format is \"%s\".", sps_format_description_string(config.output_format)); |
2461 | debug(1, "output_rate automatic selection is %sabled.", | |
2462 | config.output_rate_auto_requested ? "en" : "dis"); | |
83c0405d | 2463 | if (config.output_rate_auto_requested == 0) |
c8b0be30 | 2464 | debug(1, "output_rate is %d.", config.output_rate); |
ae84366e | 2465 | debug(1, "audio backend desired buffer length is %f seconds.", |
38281dd1 | 2466 | config.audio_backend_buffer_desired_length); |
c8b0be30 MB |
2467 | debug(1, "audio_backend_buffer_interpolation_threshold_in_seconds is %f seconds.", |
2468 | config.audio_backend_buffer_interpolation_threshold_in_seconds); | |
ae84366e | 2469 | debug(1, "audio backend latency offset is %f seconds.", config.audio_backend_latency_offset); |
a4edc649 MB |
2470 | if (config.audio_backend_silent_lead_in_time_auto == 1) |
2471 | debug(1, "audio backend silence lead-in time is \"auto\"."); | |
2472 | else | |
54d761ff MB |
2473 | debug(1, "audio backend silence lead-in time is %f seconds.", |
2474 | config.audio_backend_silent_lead_in_time); | |
bfadbf38 | 2475 | debug(1, "zeroconf regtype is \"%s\".", config.regtype); |
945483d9 MB |
2476 | debug(1, "decoders_supported field is %d.", config.decoders_supported); |
2477 | debug(1, "use_apple_decoder is %d.", config.use_apple_decoder); | |
e66a4794 | 2478 | debug(1, "alsa_use_hardware_mute is %d.", config.alsa_use_hardware_mute); |
2cab35cd | 2479 | if (config.interface) |
cf29625d | 2480 | debug(1, "mdns service interface \"%s\" requested.", config.interface); |
2cab35cd MB |
2481 | else |
2482 | debug(1, "no special mdns service interface was requested."); | |
064bd293 | 2483 | char *realConfigPath = realpath(config.configfile, NULL); |
b365f16b | 2484 | if (realConfigPath) { |
064bd293 MB |
2485 | debug(1, "configuration file name \"%s\" resolves to \"%s\".", config.configfile, |
2486 | realConfigPath); | |
b365f16b MB |
2487 | free(realConfigPath); |
2488 | } else { | |
064bd293 MB |
2489 | debug(1, "configuration file name \"%s\" can not be resolved.", config.configfile); |
2490 | } | |
38281dd1 | 2491 | #ifdef CONFIG_METADATA |
c081623a | 2492 | debug(1, "metadata enabled is %d.", config.metadata_enabled); |
6f93f55d | 2493 | debug(1, "metadata pipename is \"%s\".", config.metadata_pipename); |
064bd293 MB |
2494 | debug(1, "metadata socket address is \"%s\" port %d.", config.metadata_sockaddr, |
2495 | config.metadata_sockport); | |
fd567805 | 2496 | debug(1, "metadata socket packet size is \"%d\".", config.metadata_sockmsglength); |
6f93f55d | 2497 | debug(1, "get-coverart is %d.", config.get_coverart); |
38281dd1 | 2498 | #endif |
60b9347a | 2499 | #ifdef CONFIG_MQTT |
90f34a71 TZ |
2500 | debug(1, "mqtt is %sabled.", config.mqtt_enabled ? "en" : "dis"); |
2501 | debug(1, "mqtt hostname is %s, port is %d.", config.mqtt_hostname, config.mqtt_port); | |
60b9347a | 2502 | debug(1, "mqtt topic is %s.", config.mqtt_topic); |
9fd4a397 MB |
2503 | debug(1, "mqtt will%s publish raw metadata.", config.mqtt_publish_raw ? "" : " not"); |
2504 | debug(1, "mqtt will%s publish parsed metadata.", config.mqtt_publish_parsed ? "" : " not"); | |
2505 | debug(1, "mqtt will%s publish cover Art.", config.mqtt_publish_cover ? "" : " not"); | |
90f34a71 | 2506 | debug(1, "mqtt remote control is %sabled.", config.mqtt_enable_remote ? "en" : "dis"); |
60b9347a | 2507 | #endif |
edc5fc13 | 2508 | |
7b9cd28e YP |
2509 | #ifdef CONFIG_CONVOLUTION |
2510 | debug(1, "convolution is %d.", config.convolution); | |
2511 | debug(1, "convolution IR file is \"%s\"", config.convolution_ir_file); | |
2512 | debug(1, "convolution max length %d", config.convolution_max_length); | |
2513 | debug(1, "convolution gain is %f", config.convolution_gain); | |
2514 | #endif | |
2515 | debug(1, "loudness is %d.", config.loudness); | |
2516 | debug(1, "loudness reference level is %f", config.loudness_reference_volume_db); | |
e513e533 | 2517 | |
9aa8f91c | 2518 | #ifdef CONFIG_SOXR |
c8b0be30 | 2519 | pthread_create(&soxr_time_check_thread, NULL, &soxr_time_check, NULL); |
62fca43f | 2520 | soxr_time_check_thread_started = 1; |
9aa8f91c MB |
2521 | #endif |
2522 | ||
c924387a MB |
2523 | // In AirPlay 2 mode, the AP1 prefix is the same as the device ID less the colons |
2524 | // In AirPlay 1 mode, the AP1 prefix is calculated by hashing the service name. | |
2525 | #ifndef CONFIG_AIRPLAY_2 | |
2526 | ||
03d291f4 MB |
2527 | uint8_t ap_md5[16]; |
2528 | ||
17c39797 | 2529 | // debug(1, "size of hw_addr is %u.", sizeof(config.hw_addr)); |
03d291f4 | 2530 | #ifdef CONFIG_OPENSSL |
78dbfe6a MB |
2531 | EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); |
2532 | EVP_DigestInit_ex(mdctx, EVP_md5(), NULL); | |
2533 | EVP_DigestUpdate(mdctx, config.service_name, strlen(config.service_name)); | |
2534 | EVP_DigestUpdate(mdctx, config.hw_addr, sizeof(config.hw_addr)); | |
2535 | unsigned int md5_digest_len = EVP_MD_size(EVP_md5()); | |
48b433ef MB |
2536 | EVP_DigestFinal_ex(mdctx, ap_md5, &md5_digest_len); |
2537 | EVP_MD_CTX_free(mdctx); | |
2538 | ||
03d291f4 MB |
2539 | #endif |
2540 | ||
2541 | #ifdef CONFIG_MBEDTLS | |
2542 | #if MBEDTLS_VERSION_MINOR >= 7 | |
2543 | mbedtls_md5_context tctx; | |
2544 | mbedtls_md5_starts_ret(&tctx); | |
2545 | mbedtls_md5_update_ret(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); | |
2546 | mbedtls_md5_update_ret(&tctx, (unsigned char *)config.hw_addr, sizeof(config.hw_addr)); | |
2547 | mbedtls_md5_finish_ret(&tctx, ap_md5); | |
2548 | #else | |
2549 | mbedtls_md5_context tctx; | |
2550 | mbedtls_md5_starts(&tctx); | |
2551 | mbedtls_md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); | |
2552 | mbedtls_md5_update(&tctx, (unsigned char *)config.hw_addr, sizeof(config.hw_addr)); | |
2553 | mbedtls_md5_finish(&tctx, ap_md5); | |
2554 | #endif | |
2555 | #endif | |
2556 | ||
2557 | #ifdef CONFIG_POLARSSL | |
2558 | md5_context tctx; | |
2559 | md5_starts(&tctx); | |
2560 | md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); | |
2561 | md5_update(&tctx, (unsigned char *)config.hw_addr, sizeof(config.hw_addr)); | |
2562 | md5_finish(&tctx, ap_md5); | |
2563 | #endif | |
2564 | ||
2565 | memcpy(config.ap1_prefix, ap_md5, sizeof(config.ap1_prefix)); | |
c924387a | 2566 | #endif |
03d291f4 | 2567 | |
75f3f912 | 2568 | #ifdef CONFIG_METADATA |
87a0475c | 2569 | metadata_init(); // create the metadata pipe if necessary |
75f3f912 | 2570 | #endif |
1e07e1e0 | 2571 | |
69642bb7 | 2572 | #ifdef CONFIG_METADATA_HUB |
14523eb3 | 2573 | // debug(1, "Initialising metadata hub"); |
0801290a | 2574 | metadata_hub_init(); |
6af24f7c MB |
2575 | #endif |
2576 | ||
69642bb7 | 2577 | #ifdef CONFIG_DACP_CLIENT |
14523eb3 | 2578 | // debug(1, "Requesting DACP Monitor"); |
88c55066 MB |
2579 | dacp_monitor_start(); |
2580 | #endif | |
2581 | ||
c9b3d2a2 | 2582 | #if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE) |
1e07e1e0 | 2583 | // Start up DBUS services after initial settings are all made |
14523eb3 | 2584 | // debug(1, "Starting up D-Bus services"); |
d343a851 | 2585 | pthread_create(&dbus_thread, NULL, &dbus_thread_func, NULL); |
c9b3d2a2 | 2586 | #ifdef CONFIG_DBUS_INTERFACE |
d343a851 | 2587 | start_dbus_service(); |
74f41a17 | 2588 | #endif |
c9b3d2a2 | 2589 | #ifdef CONFIG_MPRIS_INTERFACE |
74f41a17 MB |
2590 | start_mpris_service(); |
2591 | #endif | |
1e07e1e0 MB |
2592 | #endif |
2593 | ||
c9b3d2a2 | 2594 | #ifdef CONFIG_MQTT |
c2e3fa5a | 2595 | if (config.mqtt_enabled) { |
02694948 TZ |
2596 | initialise_mqtt(); |
2597 | } | |
2598 | #endif | |
2599 | ||
c6fd0fbf | 2600 | #ifdef CONFIG_AIRPLAY_2 |
dabfee5b MB |
2601 | ptp_send_control_message_string( |
2602 | "T"); // send this message to get nqptp to create the named shm interface | |
2603 | uint64_t nqptp_start_waiting_time = get_absolute_time_in_ns(); | |
2604 | int continue_waiting = 0; | |
2605 | int response = 0; | |
2606 | int64_t time_spent_waiting = 0; | |
e8fec7fa | 2607 | do { |
dabfee5b MB |
2608 | continue_waiting = 0; |
2609 | response = ptp_shm_interface_open(); | |
2610 | if ((response == -1) && (errno == ENOENT)) { | |
2611 | time_spent_waiting = get_absolute_time_in_ns() - nqptp_start_waiting_time; | |
2612 | if (time_spent_waiting < 10000000000L) { | |
2613 | continue_waiting = 1; | |
2614 | usleep(50000); | |
2615 | } | |
2616 | } | |
2617 | } while (continue_waiting != 0); | |
2618 | ||
2619 | if ((response == -1) && (errno == ENOENT)) { | |
2620 | die("Shairport Sync can not find the nqptp service on this system. Is nqptp installed and " | |
2621 | "running?"); | |
2622 | } else if ((response == -1) && (errno == EACCES)) { | |
2623 | die("Shairport Sync must have read access to the nqptp shared memory file in /dev/shm/."); | |
2624 | } else if (response != 0) { | |
2625 | die("an error occurred accessing the nqptp service."); | |
2626 | } | |
2627 | ||
2628 | int ptp_clock_version = ptp_get_clock_version(); | |
2629 | if (ptp_clock_version == 0) { | |
2630 | die("The nqptp service on this system, which is required for Shairport Sync to operate, does " | |
2631 | "not seem to be initialised."); | |
2632 | } else if (ptp_clock_version < NQPTP_SHM_STRUCTURES_VERSION) { | |
2633 | die("The nqptp service (SMI Version %d) on this system is too old for this version of " | |
2634 | "Shairport Sync, which requires SMI Version %d. Please update.", | |
2635 | ptp_clock_version, NQPTP_SHM_STRUCTURES_VERSION); | |
2636 | } else if (ptp_clock_version > NQPTP_SHM_STRUCTURES_VERSION) { | |
2637 | die("This version of Shairport Sync (SMI Version %d) is too old for the version of nqptp (SMI " | |
2638 | "Version %d) on this system. Please update.", | |
2639 | NQPTP_SHM_STRUCTURES_VERSION, ptp_clock_version); | |
322f0772 | 2640 | } |
dabfee5b MB |
2641 | |
2642 | if (time_spent_waiting == 0) | |
2643 | debug(1, "NQPTP is online."); | |
2644 | else | |
2645 | debug(1, "NQPTP came online after %.3f milliseconds.", 0.000001 * time_spent_waiting); | |
c6fd0fbf MB |
2646 | #endif |
2647 | ||
3e4b67e9 MB |
2648 | #ifdef CONFIG_METADATA |
2649 | send_ssnc_metadata('svna', config.service_name, strlen(config.service_name), 1); | |
d67909b8 | 2650 | char buffer[256] = ""; |
fc117e73 | 2651 | snprintf(buffer, sizeof(buffer), "%d", config.output_rate); |
d67909b8 | 2652 | send_ssnc_metadata('ofps', buffer, strlen(buffer), 1); |
fc117e73 MB |
2653 | snprintf(buffer, sizeof(buffer), "%s", sps_format_description_string(config.output_format)); |
2654 | send_ssnc_metadata('ofmt', buffer, strlen(buffer), 1); | |
3e4b67e9 MB |
2655 | #endif |
2656 | ||
3a02b79a MB |
2657 | activity_monitor_start(); // not yet for AP2 |
2658 | pthread_create(&rtsp_listener_thread, NULL, &rtsp_listen_loop, NULL); | |
2659 | atexit(exit_rtsp_listener); | |
2660 | pthread_join(rtsp_listener_thread, NULL); | |
175ae0a6 | 2661 | return 0; |
df4a540d | 2662 | } |