forked from abrasive/shairport
-
-
Notifications
You must be signed in to change notification settings - Fork 575
/
shairport.c
2708 lines (2389 loc) · 102 KB
/
shairport.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Shairport, an Apple Airplay receiver
* Copyright (c) James Laird 2013
* All rights reserved.
* Modifications and additions (c) Mike Brady 2014--2023
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <libconfig.h>
#include <libgen.h>
#include <memory.h>
#include <net/if.h>
#include <popt.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include "config.h"
#ifdef CONFIG_AIRPLAY_2
#include "ptp-utilities.h"
#include <gcrypt.h>
#include <libavcodec/avcodec.h>
#include <sodium.h>
#include <uuid/uuid.h>
#endif
#ifdef CONFIG_MBEDTLS
#include <mbedtls/md5.h>
#include <mbedtls/version.h>
#endif
#ifdef CONFIG_POLARSSL
#include <polarssl/md5.h>
#endif
#ifdef CONFIG_OPENSSL
#include <openssl/evp.h>
#include <openssl/md5.h>
#endif
#if defined(CONFIG_DBUS_INTERFACE)
#include <glib.h>
#endif
#include "activity_monitor.h"
#include "audio.h"
#include "common.h"
#include "rtp.h"
#include "rtsp.h"
#if defined(CONFIG_DACP_CLIENT)
#include "dacp.h"
#endif
#if defined(CONFIG_METADATA_HUB)
#include "metadata_hub.h"
#endif
#ifdef CONFIG_DBUS_INTERFACE
#include "dbus-service.h"
#endif
#ifdef CONFIG_MQTT
#include "mqtt.h"
#endif
#ifdef CONFIG_MPRIS_INTERFACE
#include "mpris-service.h"
#endif
#ifdef CONFIG_LIBDAEMON
#include <libdaemon/dexec.h>
#include <libdaemon/dfork.h>
#include <libdaemon/dlog.h>
#include <libdaemon/dpid.h>
#include <libdaemon/dsignal.h>
#else
#include <syslog.h>
#endif
#ifdef CONFIG_SOXR
#include <math.h>
#include <soxr.h>
#endif
#ifdef CONFIG_CONVOLUTION
#include <FFTConvolver/convolver.h>
#endif
pid_t pid;
#ifdef CONFIG_LIBDAEMON
int this_is_the_daemon_process = 0;
#endif
#ifndef UUID_STR_LEN
#define UUID_STR_LEN 36
#endif
#define strnull(s) ((s) ? (s) : "(null)")
pthread_t rtsp_listener_thread;
int killOption = 0;
int daemonisewith = 0;
int daemonisewithout = 0;
int log_to_syslog_selected = 0;
#ifdef CONFIG_LIBDAEMON
int log_to_default = 1; // needed if libdaemon used
#endif
int display_config_selected = 0;
int log_to_syslog_select_is_first_command_line_argument = 0;
char configuration_file_path[4096 + 1];
char *config_file_real_path = NULL;
char first_backend_name[256];
void print_version(void) {
char *version_string = get_version_string();
if (version_string) {
printf("%s\n", version_string);
free(version_string);
} else {
debug(1, "Can't print version string!");
}
}
#ifdef CONFIG_AIRPLAY_2
int has_fltp_capable_aac_decoder(void) {
// return 1 if the AAC decoder advertises fltp decoding capability, which
// is needed for decoding Buffered Audio streams
int has_capability = 0;
const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_AAC);
if (codec != NULL) {
const enum AVSampleFormat *p = codec->sample_fmts;
if (p != NULL) {
while ((has_capability == 0) && (*p != AV_SAMPLE_FMT_NONE)) {
if (*p == AV_SAMPLE_FMT_FLTP)
has_capability = 1;
p++;
}
}
}
return has_capability;
}
#endif
#ifdef CONFIG_SOXR
pthread_t soxr_time_check_thread;
int soxr_time_check_thread_started = 0;
void *soxr_time_check(__attribute__((unused)) void *arg) {
const int buffer_length = 352;
int32_t inbuffer[buffer_length * 2];
int32_t outbuffer[(buffer_length + 1) * 2];
// int32_t *outbuffer = (int32_t*)malloc((buffer_length+1)*2*sizeof(int32_t));
// int32_t *inbuffer = (int32_t*)malloc((buffer_length)*2*sizeof(int32_t));
// generate a sample signal
const double frequency = 440; //
int i;
int number_of_iterations = 0;
uint64_t soxr_start_time = get_absolute_time_in_ns();
uint64_t loop_until_time =
(uint64_t)1500000000 + soxr_start_time; // loop for a second and a half, max -- no need to be
// able to cancel it, do _don't even try_!
while (get_absolute_time_in_ns() < loop_until_time) {
number_of_iterations++;
for (i = 0; i < buffer_length; i++) {
double w = sin(i * (frequency + number_of_iterations * 2) * 2 * M_PI / 44100);
int32_t wint = (int32_t)(w * INT32_MAX);
inbuffer[i * 2] = wint;
inbuffer[i * 2 + 1] = wint;
}
soxr_io_spec_t io_spec;
io_spec.itype = SOXR_INT32_I;
io_spec.otype = SOXR_INT32_I;
io_spec.scale = 1.0; // this seems to crash if not = 1.0
io_spec.e = NULL;
io_spec.flags = 0;
size_t odone;
soxr_oneshot(buffer_length, buffer_length + 1, 2, // Rates and # of chans.
inbuffer, buffer_length, NULL, // Input.
outbuffer, buffer_length + 1, &odone, // Output.
&io_spec, // Input, output and transfer spec.
NULL, NULL); // Default configuration.
io_spec.itype = SOXR_INT32_I;
io_spec.otype = SOXR_INT32_I;
io_spec.scale = 1.0; // this seems to crash if not = 1.0
io_spec.e = NULL;
io_spec.flags = 0;
soxr_oneshot(buffer_length, buffer_length - 1, 2, // Rates and # of chans.
inbuffer, buffer_length, NULL, // Input.
outbuffer, buffer_length - 1, &odone, // Output.
&io_spec, // Input, output and transfer spec.
NULL, NULL); // Default configuration.
}
int64_t soxr_execution_time =
get_absolute_time_in_ns() - soxr_start_time; // this must be zero or positive
int soxr_execution_time_int = soxr_execution_time; // must be in or around 1500000000
// free(outbuffer);
// free(inbuffer);
if (number_of_iterations != 0) {
config.soxr_delay_index = soxr_execution_time_int / number_of_iterations;
} else {
debug(1, "No soxr-timing iterations performed, so \"basic\" iteration will be used.");
config.soxr_delay_index = 0; // used as a flag
}
debug(2, "soxr_delay: %d nanoseconds, soxr_delay_threshold: %d milliseconds.",
config.soxr_delay_index, config.soxr_delay_threshold / 1000000);
if ((config.packet_stuffing == ST_soxr) &&
(config.soxr_delay_index > config.soxr_delay_threshold))
inform("Note: this device may be too slow for \"soxr\" interpolation. Consider choosing the "
"\"basic\" or \"auto\" interpolation setting.");
if (config.packet_stuffing == ST_auto)
debug(
1, "\"%s\" interpolation has been chosen.",
((config.soxr_delay_index != 0) && (config.soxr_delay_index <= config.soxr_delay_threshold))
? "soxr"
: "basic");
pthread_exit(NULL);
}
#endif
void usage(char *progname) {
#ifdef CONFIG_AIRPLAY_2
if (has_fltp_capable_aac_decoder() == 0) {
printf("\nIMPORTANT NOTE: Shairport Sync can not run on this system.\n");
printf("A Floating Planar (\"fltp\") AAC decoder is required, ");
printf("but the system's ffmpeg library does not seem to include one.\n");
printf("See: "
"https://github.com/mikebrady/shairport-sync/blob/development/"
"TROUBLESHOOTING.md#aac-decoder-issues-airplay-2-only\n\n");
} else {
#endif
// clang-format off
printf("Please use the configuration file for settings where possible.\n");
printf("Many more settings are available in the configuration file.\n");
printf("\n");
printf("Usage: %s [options...]\n", progname);
printf(" or: %s [options...] -- [audio output-specific options]\n", progname);
printf("\n");
printf("Options:\n");
printf(" -h, --help Show this help.\n");
printf(" -V, --version Show version information -- the version string.\n");
printf(" -X, --displayConfig Output OS information, version string, command line, configuration file and active settings to the log.\n");
printf(" --statistics Print some interesting statistics. More will be printed if -v / -vv / -vvv are also chosen.\n");
printf(" -v, --verbose Print debug information; -v some; -vv more; -vvv lots -- generally too much.\n");
printf(" -c, --configfile=FILE Read configuration settings from FILE. Default is %s.\n", configuration_file_path);
printf(" -a, --name=NAME Set service name. Default is the hostname with first letter capitalised.\n");
printf(" --password=PASSWORD Require PASSWORD to connect. Default is no password. (Classic AirPlay only.)\n");
printf(" -p, --port=PORT Set RTSP listening port. Default 5000; 7000 for AirPlay 2.\n");
printf(" -L, --latency=FRAMES [Deprecated] Set the latency for audio sent from an unknown device.\n");
printf(" The default is to set it automatically.\n");
printf(" -S, --stuffing=MODE Set how to adjust current latency to match desired latency, where:\n");
printf(" \"basic\" inserts or deletes audio frames from packet frames with low processor overhead, and\n");
printf(" \"soxr\" uses libsoxr to minimally resample packet frames -- moderate processor overhead.\n");
printf(" The default \"auto\" setting chooses basic or soxr depending on processor capability.\n");
printf(" The \"soxr\" option is only available if built with soxr support.\n");
printf(" -B, --on-start=PROGRAM Run PROGRAM when playback is about to begin.\n");
printf(" -E, --on-stop=PROGRAM Run PROGRAM when playback has ended.\n");
printf(" For -B and -E options, specify the full path to the program and arguments, e.g. \"/usr/bin/logger\".\n");
printf(" Executable scripts work, but the file must be marked executable have the appropriate shebang (#!/bin/sh) on the first line.\n");
printf(" -w, --wait-cmd Wait until the -B or -E programs finish before continuing.\n");
printf(" -o, --output=BACKEND Select audio backend. They are listed at the end of this text. The first one is the default.\n");
printf(" -m, --mdns=BACKEND Use the mDNS backend named BACKEND to advertise the AirPlay service through Bonjour/ZeroConf.\n");
printf(" They are listed at the end of this text.\n");
printf(" If no mdns backend is specified, they are tried in order until one works.\n");
printf(" -r, --resync=THRESHOLD [Deprecated] resync if error exceeds this number of frames. Set to 0 to stop resyncing.\n");
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");
printf(" --tolerance=TOLERANCE [Deprecated] Allow a synchronization error of TOLERANCE frames (default 88) before trying to correct it.\n");
printf(" --logOutputLevel Log the output level setting -- a debugging option, useful for determining the optimum maximum volume.\n");
#ifdef CONFIG_LIBDAEMON
printf(" -d, --daemon Daemonise.\n");
printf(" -j, --justDaemoniseNoPIDFile Daemonise without a PID file.\n");
printf(" -k, --kill Kill the existing shairport daemon.\n");
#endif
#ifdef CONFIG_METADATA
printf(" -M, --metadata-enable Ask for metadata from the source and process it. Much more flexibility with configuration file settings.\n");
printf(" --metadata-pipename=PIPE send metadata to PIPE, e.g. --metadata-pipename=/tmp/%s-metadata.\n", config.appName);
printf(" The default is /tmp/%s-metadata.\n", config.appName);
printf(" -g, --get-coverart Include cover art in the metadata to be gathered and sent.\n");
#endif
printf(" --log-to-syslog Send debug and statistics information through syslog\n");
printf(" If used, this should be the first command line argument.\n");
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");
printf("\n");
mdns_ls_backends();
printf("\n");
audio_ls_outputs();
// clang-format on
#ifdef CONFIG_AIRPLAY_2
}
#endif
}
int parse_options(int argc, char **argv) {
// there are potential memory leaks here -- it's called a second time, previously allocated
// strings will dangle.
char *raw_service_name = NULL; /* Used to pick up the service name before possibly expanding it */
char *stuffing = NULL; /* used for picking up the stuffing option */
signed char c; /* used for argument parsing */
// int i = 0; /* used for tracking options */
int resync_threshold_in_frames = 0;
int tolerance_in_frames = 0;
poptContext optCon; /* context for parsing command-line options */
struct poptOption optionsTable[] = {
{"verbose", 'v', POPT_ARG_NONE, NULL, 'v', NULL, NULL},
{"kill", 'k', POPT_ARG_NONE, &killOption, 0, NULL, NULL},
{"daemon", 'd', POPT_ARG_NONE, &daemonisewith, 0, NULL, NULL},
{"justDaemoniseNoPIDFile", 'j', POPT_ARG_NONE, &daemonisewithout, 0, NULL, NULL},
{"configfile", 'c', POPT_ARG_STRING, &config.configfile, 0, NULL, NULL},
{"statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL, NULL},
{"logOutputLevel", 0, POPT_ARG_NONE, &config.logOutputLevel, 0, NULL, NULL},
{"version", 'V', POPT_ARG_NONE, NULL, 0, NULL, NULL},
{"displayConfig", 'X', POPT_ARG_NONE, &display_config_selected, 0, NULL, NULL},
{"port", 'p', POPT_ARG_INT, &config.port, 0, NULL, NULL},
{"name", 'a', POPT_ARG_STRING, &raw_service_name, 0, NULL, NULL},
{"output", 'o', POPT_ARG_STRING, &config.output_name, 0, NULL, NULL},
{"on-start", 'B', POPT_ARG_STRING, &config.cmd_start, 0, NULL, NULL},
{"on-stop", 'E', POPT_ARG_STRING, &config.cmd_stop, 0, NULL, NULL},
{"wait-cmd", 'w', POPT_ARG_NONE, &config.cmd_blocking, 0, NULL, NULL},
{"mdns", 'm', POPT_ARG_STRING, &config.mdns_name, 0, NULL, NULL},
{"latency", 'L', POPT_ARG_INT, &config.userSuppliedLatency, 0, NULL, NULL},
{"stuffing", 'S', POPT_ARG_STRING, &stuffing, 'S', NULL, NULL},
{"resync", 'r', POPT_ARG_INT, &resync_threshold_in_frames, 'r', NULL, NULL},
{"timeout", 't', POPT_ARG_INT, &config.timeout, 't', NULL, NULL},
{"password", 0, POPT_ARG_STRING, &config.password, 0, NULL, NULL},
{"tolerance", 'z', POPT_ARG_INT, &tolerance_in_frames, 'z', NULL, NULL},
{"use-stderr", 'u', POPT_ARG_NONE, NULL, 'u', NULL, NULL},
{"log-to-syslog", 0, POPT_ARG_NONE, &log_to_syslog_selected, 0, NULL, NULL},
#ifdef CONFIG_METADATA
{"metadata-enable", 'M', POPT_ARG_NONE, &config.metadata_enabled, 'M', NULL, NULL},
{"metadata-pipename", 0, POPT_ARG_STRING, &config.metadata_pipename, 0, NULL, NULL},
{"get-coverart", 'g', POPT_ARG_NONE, &config.get_coverart, 'g', NULL, NULL},
#endif
POPT_AUTOHELP{NULL, 0, 0, NULL, 0, NULL, NULL}};
// we have to parse the command line arguments to look for a config file
int optind;
optind = argc;
int j;
for (j = 0; j < argc; j++)
if (strcmp(argv[j], "--") == 0)
optind = j;
optCon = poptGetContext(NULL, optind, (const char **)argv, optionsTable, 0);
if (optCon == NULL)
die("Can not get a secondary popt context.");
poptSetOtherOptionHelp(optCon, "[OPTIONS]* ");
/* Now do options processing just to get a debug log destination and level */
debuglev = 0;
while ((c = poptGetNextOpt(optCon)) >= 0) {
switch (c) {
case 'v':
debuglev++;
break;
case 'u':
inform("Warning: the option -u is no longer needed and is deprecated. Debug and statistics "
"output to STDERR is now the default. Use \"--log-to-syslog\" to revert.");
break;
case 'D':
inform("Warning: the option -D or --disconnectFromOutput is deprecated.");
break;
case 'R':
inform("Warning: the option -R or --reconnectToOutput is deprecated.");
break;
case 'A':
inform("Warning: the option -A or --AirPlayLatency is deprecated and ignored. This setting "
"is now "
"automatically received from the AirPlay device.");
break;
case 'i':
inform("Warning: the option -i or --iTunesLatency is deprecated and ignored. This setting is "
"now "
"automatically received from iTunes");
break;
case 'f':
inform(
"Warning: the option --forkedDaapdLatency is deprecated and ignored. This setting is now "
"automatically received from forkedDaapd");
break;
case 'r':
config.resync_threshold = (resync_threshold_in_frames * 1.0) / 44100;
inform("Warning: the option -r or --resync is deprecated. Please use the "
"\"resync_threshold_in_seconds\" setting in the config file instead.");
break;
case 'z':
config.tolerance = (tolerance_in_frames * 1.0) / 44100;
inform("Warning: the option --tolerance is deprecated. Please use the "
"\"drift_tolerance_in_seconds\" setting in the config file instead.");
break;
}
}
if (c < -1) {
die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c));
}
poptFreeContext(optCon);
if (log_to_syslog_selected) {
// if this was the first command line argument, it'll already have been chosen
if (log_to_syslog_select_is_first_command_line_argument == 0) {
inform("Suggestion: make \"--log-to-syslog\" the first command line argument to ensure "
"messages go to the syslog right from the beginning.");
}
#ifdef CONFIG_LIBDAEMON
log_to_default = 0; // a specific log output modality has been selected.
#endif
log_to_syslog();
}
#ifdef CONFIG_LIBDAEMON
if ((daemonisewith) && (daemonisewithout))
die("Select either daemonize_with_pid_file or daemonize_without_pid_file -- you have selected "
"both!");
if ((daemonisewith) || (daemonisewithout)) {
config.daemonise = 1;
if (daemonisewith)
config.daemonise_store_pid = 1;
};
#endif
config.audio_backend_silent_lead_in_time_auto =
1; // start outputting silence as soon as packets start arriving
config.default_airplay_volume = -24.0;
config.high_threshold_airplay_volume =
-16.0; // if the volume exceeds this, reset to the default volume if idle for the
// limit_to_high_volume_threshold_time_in_minutes time
config.limit_to_high_volume_threshold_time_in_minutes =
0; // after this time in minutes, if the volume is higher, use the default_airplay_volume
// volume for new play sessions.
config.fixedLatencyOffset = 11025; // this sounds like it works properly.
config.diagnostic_drop_packet_fraction = 0.0;
config.active_state_timeout = 10.0;
config.soxr_delay_threshold = 30 * 1000000; // the soxr measurement time (nanoseconds) of two
// oneshots must not exceed this if soxr interpolation
// is to be chosen automatically.
config.volume_range_hw_priority =
0; // if combining software and hardware volume control, give the software priority
// i.e. when reducing volume, reduce the sw first before reducing the software.
// this is because some hw mixers mute at the bottom of their range, and they don't always
// advertise this fact
config.resend_control_first_check_time =
0.10; // wait this many seconds before requesting the resending of a missing packet
config.resend_control_check_interval_time =
0.25; // wait this many seconds before again requesting the resending of a missing packet
config.resend_control_last_check_time =
0.10; // give up if the packet is still missing this close to when it's needed
config.missing_port_dacp_scan_interval_seconds =
2.0; // check at this interval if no DACP port number is known
config.minimum_free_buffer_headroom = 125; // leave approximately one second's worth of buffers
// free after calculating the effective latency.
// e.g. if we have 1024 buffers or 352 frames = 8.17 seconds and we have a nominal latency of 2.0
// seconds then we can add an offset of 5.17 seconds and still leave a second's worth of buffers
// for unexpected circumstances
#ifdef CONFIG_METADATA
/* Get the metadata setting. */
config.metadata_enabled = 1; // if metadata support is included, then enable it by default
config.get_coverart = 1; // if metadata support is included, then enable it by default
#endif
#ifdef CONFIG_CONVOLUTION
config.convolution_max_length = 8192;
#endif
config.loudness_reference_volume_db = -20;
#ifdef CONFIG_METADATA_HUB
config.cover_art_cache_dir = "/tmp/shairport-sync/.cache/coverart";
config.scan_interval_when_active =
1; // number of seconds between DACP server scans when playing something
config.scan_interval_when_inactive =
1; // number of seconds between DACP server scans when playing nothing
config.scan_max_bad_response_count =
5; // number of successive bad results to ignore before giving up
// config.scan_max_inactive_count =
// (365 * 24 * 60 * 60) / config.scan_interval_when_inactive; // number of scans to do before
// stopping if
// not made active again (not used)
#endif
#ifdef CONFIG_AIRPLAY_2
// the features code is a 64-bit number, but in the mDNS advertisement, the least significant 32
// bit are given first for example, if the features number is 0x1C340405F4A00, it will be given as
// features=0x405F4A00,0x1C340 in the mDNS string, and in a signed decimal number in the plist:
// 496155702020608 this setting here is the source of both the plist features response and the
// mDNS string.
// note: 0x300401F4A00 works but with weird delays and stuff
// config.airplay_features = 0x1C340405FCA00;
uint64_t mask =
((uint64_t)1 << 17) | ((uint64_t)1 << 16) | ((uint64_t)1 << 15) | ((uint64_t)1 << 50);
config.airplay_features =
0x1C340405D4A00 & (~mask); // APX + Authentication4 (b14) with no metadata (see below)
// Advertised with mDNS and returned with GET /info, see
// https://openairplay.github.io/airplay-spec/status_flags.html 0x4: Audio cable attached, no PIN
// required (transient pairing), 0x204: Audio cable attached, OneTimePairingRequired 0x604: Audio
// cable attached, OneTimePairingRequired, device was setup for Homekit access control
config.airplay_statusflags = 0x04;
// Set to NULL to work with transient pairing
config.airplay_pin = NULL;
// use the MAC address placed in config.hw_addr to generate the default airplay_device_id
uint64_t temporary_airplay_id = nctoh64(config.hw_addr);
temporary_airplay_id =
temporary_airplay_id >> 16; // we only use the first 6 bytes but have imported 8.
// now generate a UUID
// from https://stackoverflow.com/questions/51053568/generating-a-random-uuid-in-c
// with thanks
uuid_t binuuid;
uuid_generate_random(binuuid);
char *uuid = malloc(UUID_STR_LEN + 1); // leave space for the NUL at the end
// Produces a UUID string at uuid consisting of lower-case letters
uuid_unparse_lower(binuuid, uuid);
config.airplay_pi = uuid;
#endif
// config_setting_t *setting;
const char *str = 0;
int value = 0;
double dvalue = 0.0;
// debug(1, "Looking for the configuration file \"%s\".", config.configfile);
config_init(&config_file_stuff);
config_file_real_path = realpath(config.configfile, NULL);
if (config_file_real_path == NULL) {
debug(2, "can't resolve the configuration file \"%s\".", config.configfile);
} else {
debug(2, "looking for configuration file at full path \"%s\"", config_file_real_path);
/* Read the file. If there is an error, report it and exit. */
if (config_read_file(&config_file_stuff, config_file_real_path)) {
config_set_auto_convert(&config_file_stuff,
1); // allow autoconversion from int/float to int/float
// make config.cfg point to it
config.cfg = &config_file_stuff;
/* Get the Service Name. */
if (config_lookup_string(config.cfg, "general.name", &str)) {
raw_service_name = (char *)str;
}
#ifdef CONFIG_LIBDAEMON
/* Get the Daemonize setting. */
config_set_lookup_bool(config.cfg, "sessioncontrol.daemonize_with_pid_file", &daemonisewith);
/* Get the Just_Daemonize setting. */
config_set_lookup_bool(config.cfg, "sessioncontrol.daemonize_without_pid_file",
&daemonisewithout);
/* Get the directory path for the pid file created when the program is daemonised. */
if (config_lookup_string(config.cfg, "sessioncontrol.daemon_pid_dir", &str))
config.piddir = (char *)str;
#endif
/* Get the mdns_backend setting. */
if (config_lookup_string(config.cfg, "general.mdns_backend", &str))
config.mdns_name = (char *)str;
/* Get the output_backend setting. */
if (config_lookup_string(config.cfg, "general.output_backend", &str))
config.output_name = (char *)str;
/* Get the port setting. */
if (config_lookup_int(config.cfg, "general.port", &value)) {
if ((value < 0) || (value > 65535))
#ifdef CONFIG_AIRPLAY_2
die("Invalid port number \"%sd\". It should be between 0 and 65535, default is 7000",
value);
#else
die("Invalid port number \"%sd\". It should be between 0 and 65535, default is 5000",
value);
#endif
else
config.port = value;
}
/* Get the udp port base setting. */
if (config_lookup_int(config.cfg, "general.udp_port_base", &value)) {
if ((value < 0) || (value > 65535))
die("Invalid port number \"%sd\". It should be between 0 and 65535, default is 6001",
value);
else
config.udp_port_base = value;
}
/* Get the udp port range setting. This is number of ports that will be tried for free ports ,
* starting at the port base. Only three ports are needed. */
if (config_lookup_int(config.cfg, "general.udp_port_range", &value)) {
if ((value < 3) || (value > 65535))
die("Invalid port range \"%sd\". It should be between 3 and 65535, default is 10",
value);
else
config.udp_port_range = value;
}
/* Get the password setting. */
if (config_lookup_string(config.cfg, "general.password", &str))
config.password = (char *)str;
if (config_lookup_string(config.cfg, "general.interpolation", &str)) {
if (strcasecmp(str, "basic") == 0)
config.packet_stuffing = ST_basic;
else if (strcasecmp(str, "auto") == 0)
config.packet_stuffing = ST_auto;
else if (strcasecmp(str, "soxr") == 0)
#ifdef CONFIG_SOXR
config.packet_stuffing = ST_soxr;
#else
warn("The soxr option not available because this version of shairport-sync was built "
"without libsoxr "
"support. Change the \"general/interpolation\" setting in the configuration file.");
#endif
else
die("Invalid interpolation option choice \"%s\". It should be \"auto\", \"basic\" or "
"\"soxr\"",
str);
}
#ifdef CONFIG_SOXR
/* Get the soxr_delay_threshold setting. */
/* Convert between the input, given in milliseconds, and the stored values in nanoseconds. */
if (config_lookup_int(config.cfg, "general.soxr_delay_threshold", &value)) {
if ((value >= 1) && (value <= 100))
config.soxr_delay_threshold = value * 1000000;
else
warn("Invalid general soxr_delay_threshold setting option choice \"%d\". It should be "
"between 1 and 100, "
"inclusive. Default is %d (milliseconds).",
value, config.soxr_delay_threshold / 1000000);
}
#endif
/* Get the statistics setting. */
if (config_set_lookup_bool(config.cfg, "general.statistics",
&(config.statistics_requested))) {
warn("The \"general\" \"statistics\" setting is deprecated. Please use the \"diagnostics\" "
"\"statistics\" setting instead.");
}
/* The old drift tolerance setting. */
if (config_lookup_int(config.cfg, "general.drift", &value)) {
inform("The drift setting is deprecated. Use "
"drift_tolerance_in_seconds instead");
config.tolerance = 1.0 * value / 44100;
}
/* The old resync setting. */
if (config_lookup_int(config.cfg, "general.resync_threshold", &value)) {
inform("The resync_threshold setting is deprecated. Use "
"resync_threshold_in_seconds instead");
config.resync_threshold = 1.0 * value / 44100;
}
/* Get the drift tolerance setting. */
if (config_lookup_float(config.cfg, "general.drift_tolerance_in_seconds", &dvalue))
config.tolerance = dvalue;
/* Get the resync setting. */
if (config_lookup_float(config.cfg, "general.resync_threshold_in_seconds", &dvalue))
config.resync_threshold = dvalue;
/* Get the resync recovery time setting. */
if (config_lookup_float(config.cfg, "general.resync_recovery_time_in_seconds", &dvalue))
config.resync_recovery_time = dvalue;
/* Get the verbosity setting. */
if (config_lookup_int(config.cfg, "general.log_verbosity", &value)) {
warn("The \"general\" \"log_verbosity\" setting is deprecated. Please use the "
"\"diagnostics\" \"log_verbosity\" setting instead.");
if ((value >= 0) && (value <= 3))
debuglev = value;
else
die("Invalid log verbosity setting option choice \"%d\". It should be between 0 and 3, "
"inclusive.",
value);
}
/* Get the verbosity setting. */
if (config_lookup_int(config.cfg, "diagnostics.log_verbosity", &value)) {
if ((value >= 0) && (value <= 3))
debuglev = value;
else
die("Invalid diagnostics log_verbosity setting option choice \"%d\". It should be "
"between 0 and 3, "
"inclusive.",
value);
}
/* Get the config.debugger_show_file_and_line in debug messages setting. */
if (config_lookup_string(config.cfg, "diagnostics.log_show_file_and_line", &str)) {
if (strcasecmp(str, "no") == 0)
config.debugger_show_file_and_line = 0;
else if (strcasecmp(str, "yes") == 0)
config.debugger_show_file_and_line = 1;
else
die("Invalid diagnostics log_show_file_and_line option choice \"%s\". It should be "
"\"yes\" or \"no\"",
str);
}
/* Get the show elapsed time in debug messages setting. */
if (config_lookup_string(config.cfg, "diagnostics.log_show_time_since_startup", &str)) {
if (strcasecmp(str, "no") == 0)
config.debugger_show_elapsed_time = 0;
else if (strcasecmp(str, "yes") == 0)
config.debugger_show_elapsed_time = 1;
else
die("Invalid diagnostics log_show_time_since_startup option choice \"%s\". It should be "
"\"yes\" or \"no\"",
str);
}
/* Get the show relative time in debug messages setting. */
if (config_lookup_string(config.cfg, "diagnostics.log_show_time_since_last_message", &str)) {
if (strcasecmp(str, "no") == 0)
config.debugger_show_relative_time = 0;
else if (strcasecmp(str, "yes") == 0)
config.debugger_show_relative_time = 1;
else
die("Invalid diagnostics log_show_time_since_last_message option choice \"%s\". It "
"should be \"yes\" or \"no\"",
str);
}
/* Get the statistics setting. */
if (config_lookup_string(config.cfg, "diagnostics.statistics", &str)) {
if (strcasecmp(str, "no") == 0)
config.statistics_requested = 0;
else if (strcasecmp(str, "yes") == 0)
config.statistics_requested = 1;
else
die("Invalid diagnostics statistics option choice \"%s\". It should be \"yes\" or "
"\"no\"",
str);
}
/* Get the disable_resend_requests setting. */
if (config_lookup_string(config.cfg, "diagnostics.disable_resend_requests", &str)) {
config.disable_resend_requests = 0; // this is for legacy -- only set by -t 0
if (strcasecmp(str, "no") == 0)
config.disable_resend_requests = 0;
else if (strcasecmp(str, "yes") == 0)
config.disable_resend_requests = 1;
else
die("Invalid diagnostic disable_resend_requests option choice \"%s\". It should be "
"\"yes\" "
"or \"no\"",
str);
}
/* Get the drop packets setting. */
if (config_lookup_float(config.cfg, "diagnostics.drop_this_fraction_of_audio_packets",
&dvalue)) {
if ((dvalue >= 0.0) && (dvalue <= 3.0))
config.diagnostic_drop_packet_fraction = dvalue;
else
die("Invalid diagnostics drop_this_fraction_of_audio_packets setting \"%d\". It should "
"be "
"between 0.0 and 1.0, "
"inclusive.",
dvalue);
}
/* Get the diagnostics output default. */
if (config_lookup_string(config.cfg, "diagnostics.log_output_to", &str)) {
#ifdef CONFIG_LIBDAEMON
log_to_default = 0; // a specific log output modality has been selected.
#endif
if (strcasecmp(str, "syslog") == 0)
log_to_syslog();
else if (strcasecmp(str, "stdout") == 0) {
log_to_stdout();
} else if (strcasecmp(str, "stderr") == 0) {
log_to_stderr();
} else {
config.log_file_path = (char *)str;
config.log_fd = -1;
log_to_file();
}
}
/* Get the ignore_volume_control setting. */
if (config_lookup_string(config.cfg, "general.ignore_volume_control", &str)) {
if (strcasecmp(str, "no") == 0)
config.ignore_volume_control = 0;
else if (strcasecmp(str, "yes") == 0)
config.ignore_volume_control = 1;
else
die("Invalid ignore_volume_control option choice \"%s\". It should be \"yes\" or \"no\"",
str);
}
/* Get the optional volume_max_db setting. */
if (config_lookup_float(config.cfg, "general.volume_max_db", &dvalue)) {
// debug(1, "Max volume setting of %f dB", dvalue);
config.volume_max_db = dvalue;
config.volume_max_db_set = 1;
}
/* Get the optional default_volume setting. */
if (config_lookup_float(config.cfg, "general.default_airplay_volume", &dvalue)) {
// debug(1, "Default airplay volume setting of %f on the -30.0 to 0 scale", dvalue);
if ((dvalue >= -30.0) && (dvalue <= 0.0)) {
config.default_airplay_volume = dvalue;
} else {
warn("The default airplay volume setting must be between -30.0 and 0.0.");
}
}
/* Get the optional high_volume_threshold setting. */
if (config_lookup_float(config.cfg, "general.high_threshold_airplay_volume", &dvalue)) {
// debug(1, "High threshold airplay volume setting of %f on the -30.0 to 0 scale", dvalue);
if ((dvalue >= -30.0) && (dvalue <= 0.0)) {
config.high_threshold_airplay_volume = dvalue;
} else {
warn("The high threshold airplay volume setting must be between -30.0 and 0.0.");
}
}
/* Get the optional high volume idle tiomeout setting. */
if (config_lookup_float(config.cfg, "general.high_volume_idle_timeout_in_minutes", &dvalue)) {
// debug(1, "High high_volume_idle_timeout_in_minutes setting of %f", dvalue);
if (dvalue >= 0.0) {
config.limit_to_high_volume_threshold_time_in_minutes = dvalue;
} else {
warn("The high volume idle timeout in minutes setting must be 0.0 or greater. A setting "
"of 0.0 disables the high volume check.");
}
}
if (config_lookup_string(config.cfg, "general.run_this_when_volume_is_set", &str)) {
config.cmd_set_volume = (char *)str;
}
/* Get the playback_mode setting */
if (config_lookup_string(config.cfg, "general.playback_mode", &str)) {
if (strcasecmp(str, "stereo") == 0)
config.playback_mode = ST_stereo;
else if (strcasecmp(str, "mono") == 0)
config.playback_mode = ST_mono;
else if (strcasecmp(str, "reverse stereo") == 0)
config.playback_mode = ST_reverse_stereo;
else if (strcasecmp(str, "both left") == 0)
config.playback_mode = ST_left_only;
else if (strcasecmp(str, "both right") == 0)
config.playback_mode = ST_right_only;
else
die("Invalid playback_mode choice \"%s\". It should be \"stereo\" (default), \"mono\", "
"\"reverse stereo\", \"both left\", \"both right\"",
str);
}
/* Get the volume control profile setting -- "standard" or "flat" */
if (config_lookup_string(config.cfg, "general.volume_control_profile", &str)) {
if (strcasecmp(str, "standard") == 0)
config.volume_control_profile = VCP_standard;
else if (strcasecmp(str, "flat") == 0)
config.volume_control_profile = VCP_flat;
else if (strcasecmp(str, "dasl_tapered") == 0)
config.volume_control_profile = VCP_dasl_tapered;
else
die("Invalid volume_control_profile choice \"%s\". It should be \"standard\" (default), "
"\"dasl_tapered\", or \"flat\"",
str);
}
config_set_lookup_bool(config.cfg, "general.volume_control_combined_hardware_priority",
&config.volume_range_hw_priority);
/* Get the interface to listen on, if specified Default is all interfaces */
/* we keep the interface name and the index */
if (config_lookup_string(config.cfg, "general.interface", &str))
config.interface = strdup(str);
if (config_lookup_string(config.cfg, "general.interface", &str)) {
config.interface_index = if_nametoindex(config.interface);
if (config.interface_index == 0) {
inform(
"The mdns service interface \"%s\" was not found, so the setting has been ignored.",
config.interface);
free(config.interface);
config.interface = NULL;
}
}
/* Get the regtype -- the service type and protocol, separated by a dot. Default is
* "_raop._tcp" */
if (config_lookup_string(config.cfg, "general.regtype", &str))
config.regtype = strdup(str);
/* Get the volume range, in dB, that should be used If not set, it means you just use the
* range set by the mixer. */
if (config_lookup_int(config.cfg, "general.volume_range_db", &value)) {
if ((value < 30) || (value > 150))
die("Invalid volume range %d dB. It should be between 30 and 150 dB. Zero means use "
"the mixer's native range. The setting reamins at %d.",
value, config.volume_range_db);
else
config.volume_range_db = value;
}
/* Get the alac_decoder setting. */
if (config_lookup_string(config.cfg, "general.alac_decoder", &str)) {
if (strcasecmp(str, "hammerton") == 0)
config.use_apple_decoder = 0;
else if (strcasecmp(str, "apple") == 0) {
if ((config.decoders_supported & 1 << decoder_apple_alac) != 0)
config.use_apple_decoder = 1;
else
inform("Support for the Apple ALAC decoder has not been compiled into this version of "
"Shairport Sync. The default decoder will be used.");
} else
die("Invalid alac_decoder option choice \"%s\". It should be \"hammerton\" or \"apple\"",
str);
}
/* Get the resend control settings. */
if (config_lookup_float(config.cfg, "general.resend_control_first_check_time", &dvalue)) {
if ((dvalue >= 0.0) && (dvalue <= 3.0))
config.resend_control_first_check_time = dvalue;
else
warn("Invalid general resend_control_first_check_time setting \"%f\". It should "
"be "
"between 0.0 and 3.0, "
"inclusive. The setting remains at %f seconds.",
dvalue, config.resend_control_first_check_time);
}
if (config_lookup_float(config.cfg, "general.resend_control_check_interval_time", &dvalue)) {
if ((dvalue >= 0.0) && (dvalue <= 3.0))
config.resend_control_check_interval_time = dvalue;
else
warn("Invalid general resend_control_check_interval_time setting \"%f\". It should "
"be "
"between 0.0 and 3.0, "
"inclusive. The setting remains at %f seconds.",
dvalue, config.resend_control_check_interval_time);
}
if (config_lookup_float(config.cfg, "general.resend_control_last_check_time", &dvalue)) {
if ((dvalue >= 0.0) && (dvalue <= 3.0))
config.resend_control_last_check_time = dvalue;
else
warn("Invalid general resend_control_last_check_time setting \"%f\". It should "
"be "
"between 0.0 and 3.0, "
"inclusive. The setting remains at %f seconds.",
dvalue, config.resend_control_last_check_time);