From 75f3f912c72e9b5f7ff10f337215a7634471d012 Mon Sep 17 00:00:00 2001 From: Mke Brady Date: Fri, 3 Apr 2015 13:47:58 +0100 Subject: [PATCH 001/220] Make metadata support a compile-time option. Clean up a few definitions. --- common.h | 2 ++ configure.ac | 7 +++++ mdns.h | 5 ++++ mdns_avahi.c | 4 +++ mdns_dns_sd.c | 5 +++- mdns_external.c | 14 ++++++++-- mdns_tinysvcmdns.c | 8 ++++-- player.c | 12 ++++++-- player.h | 5 ---- rtsp.c | 68 +++++++++++++--------------------------------- shairport.c | 15 +++++++++- 11 files changed, 82 insertions(+), 63 deletions(-) diff --git a/common.h b/common.h index 62dd2e18..9a35d187 100644 --- a/common.h +++ b/common.h @@ -41,8 +41,10 @@ enum stuffing_type { typedef struct { char *password; char *apname; +#ifdef CONFIG_METADATA char *meta_dir; int get_coverart; +#endif uint8_t hw_addr[6]; int port; int resyncthreshold; // if it get's out of whack my more than this, resync. Zero means never resync. diff --git a/configure.ac b/configure.ac index f8243023..779baa1a 100644 --- a/configure.ac +++ b/configure.ac @@ -74,6 +74,13 @@ AC_ARG_WITH(soxr, [ --with-soxr = choose libsoxr for high-quality interpolation AC_MSG_RESULT(>>Including support for soxr-based interpolation) AC_CHECK_LIB([soxr],[soxr_create], , AC_MSG_ERROR(soxr support requested but libsoxr not found!))], ) +# Look for metadata flag -- set flag for conditional compilation +AC_ARG_WITH(metadata, [ --with-metadata = include support for a metadata feed], [ + AC_MSG_RESULT(>>Including metadata support) + HAS_METADATA=1 + AC_DEFINE([CONFIG_METADATA], 1, [Needed by the compiler.])], ) +AM_CONDITIONAL([USE_METADATA], [test "x$HAS_METADATA" = "x1"]) + # What follows is a bit messy, because if the relevant library is requested, a compiler flag is defined, a file is included in the compilation # and the relevant link files are added. diff --git a/mdns.h b/mdns.h index a5dc88a4..247b9a68 100644 --- a/mdns.h +++ b/mdns.h @@ -1,6 +1,8 @@ #ifndef _MDNS_H #define _MDNS_H +#include "config.h" + extern int mdns_pid; void mdns_unregister(void); @@ -15,11 +17,14 @@ typedef struct { // text and progress only -- picture feed really buggy from iTunes +#ifdef CONFIG_METADATA + #define METADATA_EXPRESSION config.get_coverart ? "md=0,1,2" : "md=0,2" #define MDNS_RECORD_WITH_METADATA "tp=UDP", "sm=false", "ek=1", "et=0,1", "cn=0,1", "ch=2", METADATA_EXPRESSION , \ "ss=16", "sr=44100", "vn=3", "txtvers=1", \ config.password ? "pw=true" : "pw=false" +#endif #define MDNS_RECORD_WITHOUT_METADATA "tp=UDP", "sm=false", "ek=1", "et=0,1", "cn=0,1", "ch=2", \ "ss=16", "sr=44100", "vn=3", "txtvers=1", \ diff --git a/mdns_avahi.c b/mdns_avahi.c index f1db7c68..6b073115 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -62,6 +62,7 @@ static void register_service(AvahiClient *c) { return; int ret; +#ifdef CONFIG_METADATA if (config.meta_dir) { debug(1,"Avahi with metadata"); ret = avahi_entry_group_add_service(group, @@ -77,6 +78,7 @@ static void register_service(AvahiClient *c) { NULL); } else { +#endif debug(1,"Avahi without metadata"); ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, @@ -89,7 +91,9 @@ static void register_service(AvahiClient *c) { port, MDNS_RECORD_WITHOUT_METADATA, NULL); +#ifdef CONFIG_METADATA } +#endif if (ret < 0) die("avahi_entry_group_add_service failed"); diff --git a/mdns_dns_sd.c b/mdns_dns_sd.c index 1a458ea3..4f586b93 100644 --- a/mdns_dns_sd.c +++ b/mdns_dns_sd.c @@ -33,12 +33,15 @@ static DNSServiceRef service; static int mdns_dns_sd_register(char *apname, int port) { const char *recordwithoutmetadata[] = { MDNS_RECORD_WITHOUT_METADATA, NULL }; +#ifdef CONFIG_METADATA const char *recordwithmetadata[] = { MDNS_RECORD_WITH_METADATA, NULL }; - +#endif char **record; +#ifdef CONFIG_METADATA if (config.meta_dir) record = recordwithmetadata; else +#endif record = recordwithoutmetadata; uint16_t length = 0; diff --git a/mdns_external.c b/mdns_external.c index 912dfec1..832d8e06 100644 --- a/mdns_external.c +++ b/mdns_external.c @@ -92,15 +92,18 @@ static int mdns_external_avahi_register(char *apname, int port) { char *argvwithoutmetadata[] = { NULL, apname, "_raop._tcp", mdns_port, MDNS_RECORD_WITHOUT_METADATA, NULL }; - +#ifdef CONFIG_METADATA char *argvwithmetadata[] = { NULL, apname, "_raop._tcp", mdns_port, MDNS_RECORD_WITH_METADATA, NULL }; - +#endif char **argv; + +#ifdef CONFIG_METADATA if (config.meta_dir) argv=argvwithmetadata; else +#endif argv=argvwithoutmetadata; argv[0] = "avahi-publish-service"; @@ -135,14 +138,19 @@ static int mdns_external_dns_sd_register(char *apname, int port) { NULL, apname, "_raop._tcp", mdns_port, MDNS_RECORD_WITHOUT_METADATA, NULL }; +#ifdef CONFIG_METADATA char *argvwithmetadata[] = { NULL, apname, "_raop._tcp", mdns_port, MDNS_RECORD_WITH_METADATA, NULL }; - +#endif + char **argv; +#ifdef CONFIG_METADATA if (config.meta_dir) argv=argvwithmetadata; else +#endif + argv=argvwithoutmetadata; int pid = fork_execvp(argv[0], argv); diff --git a/mdns_tinysvcmdns.c b/mdns_tinysvcmdns.c index fa258e54..8b79d49c 100644 --- a/mdns_tinysvcmdns.c +++ b/mdns_tinysvcmdns.c @@ -123,13 +123,17 @@ static int mdns_tinysvcmdns_register(char *apname, int port) { freeifaddrs(ifa); char *txtwithoutmetadata[] = { MDNS_RECORD_WITHOUT_METADATA, NULL }; +#ifdef CONFIG_METADATA char *txtwithmetadata[] = { MDNS_RECORD_WITH_METADATA, NULL }; - +#endif char **txt; - + +#ifdef CONFIG_METADATA if (config.meta_dir) txt = txtwithmetadata; else +#endif + txt = txtwithoutmetadata; diff --git a/player.c b/player.c index a002db5b..5be04439 100644 --- a/player.c +++ b/player.c @@ -564,9 +564,11 @@ static abuf_t *buffer_get_frame(void) { // debug(1,"Exact frame gap is %llu; play %d frames of silence. Dac_delay is %d, with %d packets.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read, ab_write)); config.output->play(silence, fs); free(silence); +#ifdef CONFIG_METADATA if (ab_buffering==0) { send_ssnc_metadata('prsm',NULL,0,0); // "resume", but don't wait if the queue is locked } +#endif } } } @@ -1061,14 +1063,14 @@ void player_volume(double f) { software_mixer_volume = linear_volume; fix_volume = 65536.0 * software_mixer_volume; pthread_mutex_unlock(&vol_mutex); - +#ifdef CONFIG_METADATA char *dv = malloc(64); // will be freed in the metadata thread if (dv) { memset(dv,0,64); snprintf(dv,63,"%.2f,%.2f,%.2f,%.2f",audio_information.airplay_volume,audio_information.current_volume_dB/100.0,audio_information.minimum_volume_dB/100.0,audio_information.maximum_volume_dB/100.0); send_ssnc_metadata('pvol',dv,strlen(dv),1); } - +#endif } @@ -1079,7 +1081,9 @@ void player_flush(uint32_t timestamp) { //if (timestamp!=0) flush_rtp_timestamp=timestamp; // flush all packets up to (and including?) this pthread_mutex_unlock(&flush_mutex); +#ifdef CONFIG_METADATA send_ssnc_metadata('pfls',NULL,0,1); +#endif } int player_play(stream_cfg *stream) { @@ -1103,7 +1107,9 @@ int player_play(stream_cfg *stream) { init_buffer(); please_stop = 0; command_start(); +#ifdef CONFIG_METADATA send_ssnc_metadata('pbeg',NULL,0,1); +#endif // set the flowcontrol condition variable to wait on a monotonic clock #ifdef COMPILE_FOR_LINUX @@ -1127,7 +1133,9 @@ void player_stop(void) { please_stop = 1; pthread_cond_signal(&flowcontrol); // tell it to give up pthread_join(player_thread, NULL); +#ifdef CONFIG_METADATA send_ssnc_metadata('pend',NULL,0,1); +#endif config.output->stop(); command_stop(); free_buffer(); diff --git a/player.h b/player.h index 5188ac07..0a9f82a5 100644 --- a/player.h +++ b/player.h @@ -18,11 +18,6 @@ void player_stop(void); void player_volume(double f); void player_flush(uint32_t timestamp); -void player_resync(void); - -void player_metadata(); -void player_cover_image(char *buf, int len, char *ext); -void player_cover_clear(); void player_put_packet(seq_t seqno,uint32_t timestamp, uint8_t *data, int len); diff --git a/rtsp.c b/rtsp.c index a4561c49..91e0728e 100644 --- a/rtsp.c +++ b/rtsp.c @@ -91,7 +91,7 @@ typedef struct { pthread_t thread; } rtsp_conn_info; - +#ifdef CONFIG_METADATA typedef struct { pthread_mutex_t pc_queue_lock; pthread_cond_t pc_queue_item_added_signal; @@ -103,6 +103,7 @@ typedef struct { uint32_t eoq; // free space at end of queue void *items; // a pointer to where the items are actually stored } pc_queue; // producer-consumer queue +#endif typedef struct { uint32_t referenceCount; // we might start using this... @@ -120,6 +121,7 @@ typedef struct { int respcode; } rtsp_message; +#ifdef CONFIG_METADATA typedef struct { uint32_t type; uint32_t code; @@ -143,49 +145,6 @@ int send_ssnc_metadata(uint32_t code,char *data,uint32_t length,int block) { return send_metadata('ssnc',code,data,length,NULL,block); } - -/* -pc_queue* pc_queue_create(size_t new_item_size, uint32_t number_of_items) { - debug(1,"Creating pc_queue"); - pc_queue* the_queue = malloc(sizeof(pc_queue)+number_of_items*new_item_size-sizeof(void*)); - if (the_queue) { - int rc = pthread_mutex_init(&the_queue->pc_queue_lock,NULL); - if (rc) - debug(1,"Error %d creating pc_queue lock",rc); - rc = pthread_cond_init(&the_queue->pc_queue_item_added_signal,NULL); - if (rc) - debug(1,"Error %d creating pc_queue add cond",rc); - rc = pthread_cond_init(&the_queue->pc_queue_item_removed_signal,NULL); - if (rc) - debug(1,"Error %d creating pc_queue remove cond",rc); - the_queue->item_size = new_item_size; - the_queue->count = 0; - the_queue->capacity = number_of_items; - the_queue->toq = 0; - the_queue->eoq = 0; - } - return the_queue; -} - -int pc_queue_delete(pc_queue* the_queue) { - if (the_queue) { - int rc = pthread_mutex_destroy(&the_queue->pc_queue_lock); - if (rc) - debug(1,"Error %d deleting pc_queue lock",rc); - rc = pthread_cond_destroy(&the_queue->pc_queue_item_added_signal); - if (rc) - debug(1,"Error %d deleting pc_queue add cond",rc); - rc = pthread_cond_destroy(&the_queue->pc_queue_item_removed_signal); - if (rc) - debug(1,"Error %d deleting pc_queue remove cond",rc); - free(the_queue); - } else { - debug(1,"Attempting to delete a NULL pc_queue!"); - } - return 0; -} -*/ - int pc_queue_add_item(pc_queue* the_queue,const void* the_stuff, int block) { int rc; if (the_queue) { @@ -263,6 +222,7 @@ int pc_queue_get_item(pc_queue* the_queue,void* the_stuff) { return 0; } +#endif // determine if we are the currently playing thread static inline int rtsp_playing(void) { @@ -551,7 +511,9 @@ static enum rtsp_read_request_response rtsp_read_request(int fd, rtsp_message** uint64_t time_now = get_absolute_time_in_fp(); if (time_now>threshold_time) { // it's taking too long debug(1,"Error receiving metadata from source -- transmission seems to be stalled."); +#ifdef CONFIG_METADATA send_ssnc_metadata('stal',NULL,0,1); +#endif warning_message_sent = 1; } } @@ -785,11 +747,15 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, float volume = atof(cp + 8); debug(2, "volume: %f\n", volume); player_volume(volume); - } else if(!strncmp(cp, "progress: ", 10)) { + } else +#ifdef CONFIG_METADATA + if(!strncmp(cp, "progress: ", 10)) { char *progress = cp + 10; debug(2, "progress: \"%s\"\n", progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per second send_ssnc_metadata('prgr',strdup(progress),strlen(progress),1); - } else { + } else +#endif + { debug(1, "unrecognised parameter: \"%s\" (%d)\n", cp, strlen(cp)); } cp = next; @@ -797,7 +763,7 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, } - +#ifdef CONFIG_METADATA // Metadata is not used by shairport-sync. // Instead we send all metadata to a fifo pipe, so that other apps can listen to the pipe and use the metadata. @@ -1095,6 +1061,8 @@ static void handle_set_parameter_metadata(rtsp_conn_info *conn, send_metadata('ssnc','sndr',strdup(sender_name),strlen(sender_name),NULL,1); } +#endif + static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { //if (!req->contentlength) @@ -1104,7 +1072,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, if (ct) { debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct); - +#ifdef CONFIG_METADATA if (!strncmp(ct, "application/x-dmap-tagged", 25)) { debug(2, "received metadata tags in SET_PARAMETER request."); handle_set_parameter_metadata(conn, req, resp); @@ -1113,7 +1081,9 @@ static void handle_set_parameter(rtsp_conn_info *conn, // note: the image/type tag isn't reliable, so it's not being sent // -- best look at the first few bytes of the image send_metadata('ssnc','PICT',req->content,req->contentlength,req,1); - } else if (!strncmp(ct, "text/parameters", 15)) { + } else +#endif + if (!strncmp(ct, "text/parameters", 15)) { debug(2, "received parameters in SET_PARAMETER request."); handle_set_parameter_parameter(conn, req, resp); } else { diff --git a/shairport.c b/shairport.c index e4d2f534..565ced69 100644 --- a/shairport.c +++ b/shairport.c @@ -133,6 +133,9 @@ void print_version(void) { #endif #ifdef HAVE_LIBSOXR strcat(version_string,"-soxr"); +#endif +#ifdef CONFIG_METADATA + strcat(version_string,"-metadata"); #endif printf("%s\n",version_string); } @@ -180,8 +183,10 @@ void usage(char *progname) { printf(" --statistics print some interesting statistics -- output to the logfile if running as a daemon.\n"); printf(" --tolerance=TOLERANCE allow a synchronization error of TOLERANCE frames (default 88) before trying to correct it.\n"); printf(" --password=PASSWORD require PASSWORD to connect. Default is not to require a password.\n"); +#ifdef CONFIG_METADATA printf(" --meta-dir=DIR get metadata from the source and pipe it to DIR/shairport-sync-metadata, e.g. --meta-dir=/tmp.\n"); printf(" --get-coverart get cover art from the source and pipe it to DIR/shairport-sync-metadata, e.g. --meta-dir=/tmp.\n"); +#endif printf("\n"); mdns_ls_backends(); printf("\n"); @@ -217,8 +222,10 @@ int parse_options(int argc, char **argv) { { "timeout", 't', POPT_ARG_INT, &config.timeout, 0, NULL } , { "password", 0, POPT_ARG_STRING, &config.password, 0, NULL } , { "tolerance", 0, POPT_ARG_INT, &config.tolerance, 0, NULL } , +#ifdef CONFIG_METADATA { "meta-dir", 'M', POPT_ARG_STRING, &config.meta_dir, 0, NULL } , - { "get-coverart", 'g', POPT_ARG_NONE, &config.get_coverart, 0, NULL }, + { "get-coverart", 'g', POPT_ARG_NONE, &config.get_coverart, 0, NULL } , +#endif POPT_AUTOHELP { NULL, 0, 0, NULL, 0 } }; @@ -238,10 +245,12 @@ int parse_options(int argc, char **argv) { case 'v': debuglev++; break; +#ifdef CONFIG_METADATA case 'g': if (config.meta_dir==0) die("If you want to get cover art, you must also select the --meta-dir option."); break; +#endif case 'S': if (strcmp(stuffing,"basic")==0) config.packet_stuffing = ST_basic; @@ -279,8 +288,10 @@ int parse_options(int argc, char **argv) { debug(2,"busy timeout time is %d.",config.timeout); debug(2,"tolerance is %d frames.",config.tolerance); debug(2,"password is \"%s\".",config.password); +#ifdef CONFIG_METADATA debug(2,"metadata directory is \"%s\".",config.meta_dir); debug(2,"get-coverart is %d.",config.get_coverart); +#endif return optind+1; } @@ -538,7 +549,9 @@ int main(int argc, char **argv) { md5_finish(&tctx, ap_md5); #endif memcpy(config.hw_addr, ap_md5, sizeof(config.hw_addr)); +#ifdef CONFIG_METADATA metadata_init() ; // create the metadata pipe if necessary +#endif rtsp_listen_loop(); From ffc79c85ff15eeb3bf39e1813c5ee2786e4cb42e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 3 Apr 2015 13:54:29 +0100 Subject: [PATCH 002/220] Add note about development branch --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e58e56b..7b75bd14 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ -Shairport Sync +Shairport Sync (Development Branch) ============= +This branch -- 2.3 -- is the development branch of Shairport Sync and is unstable. To access the stable version, please switch to the 2.2 branch. + What is Shairport Sync? ---------- Shairport Sync emulates an AirPort Express for the purpose of streaming audio from iTunes, iPods, iPhones, iPads and AppleTVs. From 55b0e8b97efd82fbf484252bf2510bbd43f0bd20 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 3 Apr 2015 13:57:16 +0100 Subject: [PATCH 003/220] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b75bd14..79ec6b5c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ Shairport Sync (Development Branch) ============= -This branch -- 2.3 -- is the development branch of Shairport Sync and is unstable. To access the stable version, please switch to the 2.2 branch. +This branch -- 2.3 -- is the development branch of Shairport Sync and is unstable. To access the stable branch, please switch to the 2.2 branch. What is Shairport Sync? ---------- From 60431e895ed1bcf28a44d310b86bdb59d4bdde3f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 3 Apr 2015 14:18:41 +0100 Subject: [PATCH 004/220] Bump version number to 2.3 --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 779baa1a..04722753 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.2.3], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From e3e7d328da91c038763c6eb9ca0279485cc3b15f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 3 Apr 2015 14:25:50 +0100 Subject: [PATCH 005/220] Mention 2.3 and compile-time option. --- RELEASENOTES.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 474ef4f5..884f152d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,13 +1,14 @@ -Version 2.2.3 (Work in progress -- not a release yet.) +Version 2.3 (Development branch, work in progress -- not a release yet.) ----- +* Enhancement + * Metadata support is now a compile-time option: `--with-metadata`. + * A very experimental metadata feed has been added. Use the option `-M `, e.g. `-M /tmp`. Shairport Sync will provide metadata in a pipe called `/shairport-sync-metadata`. There's a sample metadata reader at https://github.com/mikebrady/shairport-sync-metadata-reader. The format of the metadata is a mixture of XML-style tags, 4-character codes and base64 data. Please look at `rtsp.c` and `player.c` for examples. Please note that the format of the metadata may change. +Beware: there appears to be a serious bug in iTunes, such that it may stall for a long period when sending large (more than a few hundred kilobytes) coverart images. + * Bugfix * Modify the init script to start after all services are ready. Add in a commented-out sleep command if users find it necessary (thanks to https://github.com/BNoiZe). * Two memory leaks fixed. * An error handling time specifications for flushes was causing an audible glitch when pausing and resuming some tracks. This has been fixed (thanks to https://github.com/Hamster128). -* Enhancement - * A very experimental metadata feed has been added. Use the option `-M `, e.g. `-M /tmp`. Shairport Sync will provide metadata in a pipe called `/shairport-sync-metadata`. There's a sample metadata reader at https://github.com/mikebrady/shairport-sync-metadata-reader. The format of the metadata is a mixture of XML-style tags, 4-character codes and base64 data. Please look at `rtsp.c` and `player.c` for examples. Please note that the format of the metadata may change. -Beware: there appears to be a serious bug in iTunes, such that it may stall for a long period when sending large (more than a few hundred kilobytes) coverart images. - Version 2.2.2 ----- From 8947ec7f4e70db42ef75daf308fcec3cb7cb9f7f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 3 Apr 2015 14:30:02 +0100 Subject: [PATCH 006/220] Add note about metadata --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 79ec6b5c..acec6b3d 100644 --- a/README.md +++ b/README.md @@ -89,10 +89,11 @@ Choose the appropriate `--with-*` options: - `--with-ssl=openssl` or `--with-ssl=polarssl` for encryption and related utilities using either OpenSSL or PolarSSL. - `--with-soxr` for libsoxr-based resampling. - `--with-piddir` for specifying where the PID file should be stored. This directory is normally chosen automatically. The directory must be writable. If you use this option, you may have to edit the init script to search for the PID file in your new location. +- `--with-metadata` to add support for Shairport Sync to pipe metadata to a compatible application of your choice. See https://github.com/mikebrady/shairport-sync-metadata-reader for a sample metadata reader. Here is an example, suitable for most installations: -`$ ./configure --with-alsa --with-avahi --with-ssl=openssl --with-soxr` +`$ ./configure --with-alsa --with-avahi --with-ssl=openssl --with-metadata --with-soxr` Omit the `--with-soxr` if the libsoxr library is not available. From 55d45e8a033277361e0224405c7ebf44024060b8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 4 Apr 2015 09:15:27 +0100 Subject: [PATCH 007/220] Change layout slightly --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index acec6b3d..0cc07c26 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ Shairport Sync (Development Branch) ============= -This branch -- 2.3 -- is the development branch of Shairport Sync and is unstable. To access the stable branch, please switch to the 2.2 branch. - -What is Shairport Sync? ----------- Shairport Sync emulates an AirPort Express for the purpose of streaming audio from iTunes, iPods, iPhones, iPads and AppleTVs. Audio played by a Shairport Sync-powered device stays synchronised with the source and hence with similar devices playing the same source. Thus, for example, synchronised multi-room audio is possible without difficulty. (Hence the name Shairport Sync, BTW.) Shairport Sync does not support AirPlay video or photo streaming. +This branch -- 2.3 -- is the development branch of Shairport Sync and is unstable. To access the stable branch, please switch to the 2.2 branch. + +More Information +---------- Shairport Sync works by using timing information present in the audio data stream to keep in step with the source. It does this by monitoring and controlling the "latency" -- the time between when a sound is time-stamped at the source and when it is played by the audio output device. To measure latency precisely, it keeps its own clock synchronised with the clock used by the source, usually to within a fraction of a millisecond, using a variant of NTP synchronisation protocols. To maintain the exact latency required, if an output device is running slow relative to the source, Shairport Sync will delete frames of audio to allow the device to keep up; if the device is running fast, Shairport Sync will insert frames to keep time. The number of frames inserted or deleted is so small as to be almost inaudible. Frames are inserted or deleted as necessary at pseudorandom intervals. Alternatively, with `libsoxr` support, Shairport Sync can resample the audio feed to ensure the output device can keep up. This is even less obtrusive than insertion and deletion but requires a good deal of processing power -- most embedded devices probably can't support it. From 3360d914720fc4fb5beeb65e4eeb1d455ea70afb Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 4 Apr 2015 23:01:55 +0100 Subject: [PATCH 008/220] Add stuff about meta-dir and get-coverart. --- man/shairport-sync.7.xml | 368 +-------------------------------------- 1 file changed, 2 insertions(+), 366 deletions(-) diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml index 062ff2b8..965be177 100644 --- a/man/shairport-sync.7.xml +++ b/man/shairport-sync.7.xml @@ -41,9 +41,11 @@ [-B command] [-E command] [--forkedDaapdLatency=latency] + [--get-cover-art] [-i latency] [-L latency] [-m backend] + [--meta-dir=directory] [-o backend] [--password=secret] [-r threshold] @@ -61,370 +63,4 @@ shairport-sync -V - -

shairport-sync plays audio streamed from iTunes or from an AirPlay - device to an audio device connected via an audio back end. At present, - the only fully-implemented back end is for ALSA.

- -

A feature of shairport-sync is that the audio is played synchronously. - This means that if many devices are playing the same stream at the same - time, all the outputs will stay in step with one another. - This allows multiple devices play the same source without getting out of phase with one another, - enabling, for example, simultaneous multi-room operation. -

- -
- - - -

Many of the options take sensible default values, so you can normally - ignore most of them. See the EXAMPLES section for typical usages.

- -

The command line for shairport-sync can take two kinds of options: - regular program options and audio backend options. - Program options are - always listed first, followed by any audio backend options, preceded by - a -- symbol.

- -
-

These options are used by shairport-sync itself.

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

These options are passed to the chosen audio backend. (At present, the - only backend implemented is for ALSA.) The audio backend options are - preceded by a -- symbol to introduce them and to separate them from any - program options. In this way, option letters can be used as program - options and also as audio backend options without ambiguity.

-

In the ALSA backend, audio is sent to an output device - which you can specify using the -d option. - The output level (the "volume") is controlled using a level control associated with a mixer. - By default, the mixer is implemented in shairport-sync itself, i.e. the type of - the mixer is "software". - To use a level control on a mixer on the sound card, you must (a) - specify that the mixer's type is "hardware" with the -t option; - (b) you must specify where the mixer is to be found using the -m option - and finally (c) you must specify the name of the level control you are using - with the -c option.

-
- - - - - - - - -
- -
-

Here is a slightly contrived but typical example:

- shairport-sync -d - -a "Joe's Stereo" - -S soxr - -- - -d hw:1,0 - -m hw:1 - -t hardware - -c PCM - -

The program will run in daemon mode ( -d ), will be visible as - "Joe's Stereo" ( -a "Joe's Stereo" ) and will use the SoX Resampler - Library-based stuffing ( -S soxr ). - The audio backend options following the -- separator specify - that the audio will be output on output 0 of soundcard 1 ( - -d hw:1,0 ) and will take advantage of the same sound card's hardware - (-t hardware) mixer ( -m hw:1 ) - using the level control named "PCM" ( -c "PCM" ). -

-

The example above is slightly contrived in order to show the use of the -m option. - Typically, output 0 is the default output of a card, - so the output device could be written -d hw:1 and - then the mixer option would be unnecessary, giving the following, simpler, command:

- shairport-sync -d - -a "Joe's Stereo" - -S soxr - -- - -d hw:1 - -t hardware - -c PCM - - -
- -
-

Mike Brady developed shairport-sync from the original shairport by James Laird.

-

shairport-sync can be found at

-

shairport can be found at

-
- -
-

This man page was written using by Oliver Kurth.

-
- From 0fcc1e8f0ce39a1e9651f700ee009b61d8d7dc4d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 4 Apr 2015 23:07:19 +0100 Subject: [PATCH 009/220] Add in the rest of the file. --- man/shairport-sync.7.xml | 390 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 390 insertions(+) diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml index 965be177..da57360a 100644 --- a/man/shairport-sync.7.xml +++ b/man/shairport-sync.7.xml @@ -63,4 +63,394 @@ shairport-sync -V + +

shairport-sync plays audio streamed from iTunes or from an AirPlay + device to an audio device connected via an audio back end. At present, + the only fully-implemented back end is for ALSA.

+ +

A feature of shairport-sync is that the audio is played synchronously. + This means that if many devices are playing the same stream at the same + time, all the outputs will stay in step with one another. + This allows multiple devices play the same source without getting out of phase with one another, + enabling, for example, simultaneous multi-room operation. +

+ +
+ + + +

Many of the options take sensible default values, so you can normally + ignore most of them. See the EXAMPLES section for typical usages.

+ +

The command line for shairport-sync can take two kinds of options: + regular program options and audio backend options. + Program options are + always listed first, followed by any audio backend options, preceded by + a -- symbol.

+ +
+

These options are used by shairport-sync itself.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

These options are passed to the chosen audio backend. (At present, the + only backend implemented is for ALSA.) The audio backend options are + preceded by a -- symbol to introduce them and to separate them from any + program options. In this way, option letters can be used as program + options and also as audio backend options without ambiguity.

+

In the ALSA backend, audio is sent to an output device + which you can specify using the -d option. + The output level (the "volume") is controlled using a level control associated with a mixer. + By default, the mixer is implemented in shairport-sync itself, i.e. the type of + the mixer is "software". + To use a level control on a mixer on the sound card, you must (a) + specify that the mixer's type is "hardware" with the -t option; + (b) you must specify where the mixer is to be found using the -m option + and finally (c) you must specify the name of the level control you are using + with the -c option.

+
+ + + + + + + + +
+ +
+

Here is a slightly contrived but typical example:

+ shairport-sync -d + -a "Joe's Stereo" + -S soxr + -- + -d hw:1,0 + -m hw:1 + -t hardware + -c PCM + +

The program will run in daemon mode ( -d ), will be visible as + "Joe's Stereo" ( -a "Joe's Stereo" ) and will use the SoX Resampler + Library-based stuffing ( -S soxr ). + The audio backend options following the -- separator specify + that the audio will be output on output 0 of soundcard 1 ( + -d hw:1,0 ) and will take advantage of the same sound card's hardware + (-t hardware) mixer ( -m hw:1 ) + using the level control named "PCM" ( -c "PCM" ). +

+

The example above is slightly contrived in order to show the use of the -m option. + Typically, output 0 is the default output of a card, + so the output device could be written -d hw:1 and + then the mixer option would be unnecessary, giving the following, simpler, command:

+ shairport-sync -d + -a "Joe's Stereo" + -S soxr + -- + -d hw:1 + -t hardware + -c PCM + + +
+ +
+

Mike Brady developed shairport-sync from the original shairport by James Laird.

+

shairport-sync can be found at

+

shairport can be found at

+
+ +
+

This man page was written using by Oliver Kurth.

+
+ From 3ff946eb3cb6515cc7379172853f3679bef46377 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 4 Apr 2015 23:19:25 +0100 Subject: [PATCH 010/220] Fix error in the xml and recompile man and htmp files --- man/shairport-sync.7 | 12 ++++++++++-- man/shairport-sync.7.xml | 6 +++--- man/shairport-sync.html | 28 +++++++++++++++++++++++++++- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/man/shairport-sync.7 b/man/shairport-sync.7 index 1acbadef..d7c05777 100644 --- a/man/shairport-sync.7 +++ b/man/shairport-sync.7 @@ -2,7 +2,7 @@ .SH NAME shairport-sync \- Synchronised Audio Player for iTunes / AirPlay .SH SYNOPSIS -\fBshairport-sync [-dvw]\fB [-a \fB\fIname\fB]\fB [-A \fB\fIlatency\fB]\fB [-B \fB\fIcommand\fB]\fB [-E \fB\fIcommand\fB]\fB [--forkedDaapdLatency=\fB\fIlatency\fB]\fB [-i \fB\fIlatency\fB]\fB [-L \fB\fIlatency\fB]\fB [-m \fB\fIbackend\fB]\fB [-o \fB\fIbackend\fB]\fB [--password=\fB\fIsecret\fB]\fB [-r \fB\fIthreshold\fB]\fB [--statistics]\fB [-S \fB\fImode\fB]\fB [-t \fB\fItimeout\fB]\fB [--tolerance=\fB\fIframes\fB]\fB [-- \fB\fIaudio_backend_options\fB]\fB +\fBshairport-sync [-dvw]\fB [-a \fB\fIname\fB]\fB [-A \fB\fIlatency\fB]\fB [-B \fB\fIcommand\fB]\fB [-E \fB\fIcommand\fB]\fB [--forkedDaapdLatency=\fB\fIlatency\fB]\fB [--get-cover-art]\fB [-i \fB\fIlatency\fB]\fB [-L \fB\fIlatency\fB]\fB [-m \fB\fIbackend\fB]\fB [--meta-dir=\fB\fIdirectory\fB]\fB [-o \fB\fIbackend\fB]\fB [--password=\fB\fIsecret\fB]\fB [-r \fB\fIthreshold\fB]\fB [--statistics]\fB [-S \fB\fImode\fB]\fB [-t \fB\fItimeout\fB]\fB [--tolerance=\fB\fIframes\fB]\fB [-- \fB\fIaudio_backend_options\fB]\fB shairport-sync -D\fB @@ -52,6 +52,11 @@ If you want shairport-sync to wait until the command has completed before contin \fB--forkedDaapdLatency=\f1\fIlatency\f1 Use this \fIlatency\f1, in frames, for audio streamed from a forked-daapd based source. The default is 99,400 frames, where there are 44,100 frames to the second. .TP +\fB--get-coverart\f1 +This option requires the \fB--meta-dir\f1 option to be set, and enables shairport-sync to request cover art from the source and to transmit it through the metadata pipe. + +Please note that cover art data may be very large, and may place too great a burden on your network. Also, at the time of writing, there appears to be a serious bug in iTunes which stalls transmission of large cover art items, stalling and potentially crashing shairport-sync. +.TP \fB-h | --help\f1 Print brief help message and exit. .TP @@ -64,6 +69,9 @@ Kill the shairport-sync daemon and exit. (Requires that the daemon has written i \fB-L | --latency=\f1\fIlatency\f1 Use this to set the \fIdefault latency\f1, in frames, for audio coming from an unidentified source or from an iTunes Version 9 or earlier source. The standard value for the \fIdefault latency\f1 is 88,200 frames, where there are 44,100 frames to the second. .TP +\fB--meta-dir=\f1\fIdirectory\f1 +Listen for metadata coming from the source and send it, along with metadata from shairport-sync itself, to a pipe called \fIshairport-sync-metadata\f1 in the \fIdirectory\f1 you specify. If you add the \fB--get-cover-art\f1 then cover art will be sent through the pipe too. See https://github.com/mikebrady/shairport-sync-metadata-reader for a sample metadata reader. +.TP \fB-m \f1\fImdnsbackend\f1\fB | --mdns=\f1\fImdnsbackend\f1 Force the use of the specified mDNS backend to advertise the player on the network. The default is to try all mDNS backends until one works. .TP @@ -91,7 +99,7 @@ Stuff the audio stream using the \fImode\f1. "Stuffing" refers to the process of \fB-t \f1\fItimeout\f1\fB | --timeout=\f1\fItimeout\f1 Exit play mode if the stream disappears for more than \fItimeout\f1 seconds. -When shairport-sync plays an audio stream, it startes a play session and will return a busy signal to any other sources that attempt to use it. If the audio stream disappears for longer than \fItimeout\f1 seconds, the play session will be terminated. If you specify a timeout time of \fB0\f1, shairport-sync will never signal that it is busy and will not prevent other sources from "barging in" on an existing play session. The default value is 120 seconds. +When shairport-sync plays an audio stream, it starts a play session and will return a busy signal to any other sources that attempt to use it. If the audio stream disappears for longer than \fItimeout\f1 seconds, the play session will be terminated. If you specify a timeout time of \fB0\f1, shairport-sync will never signal that it is busy and will not prevent other sources from "barging in" on an existing play session. The default value is 120 seconds. .TP \fB--tolerance=\f1\fIframes\f1 Allow playback to be up to \fIframes\f1 out of exact synchronization before attempting to correct it. The default is 88 frames, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the \fB--statistics\f1 option to monitor correction levels. Corrections should not greatly exceed net corrections. diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml index da57360a..8b7685ed 100644 --- a/man/shairport-sync.7.xml +++ b/man/shairport-sync.7.xml @@ -2,9 +2,9 @@ - + --> diff --git a/man/shairport-sync.html b/man/shairport-sync.html index 5cc6f563..16629683 100644 --- a/man/shairport-sync.html +++ b/man/shairport-sync.html @@ -12,9 +12,11 @@ [-B command] [-E command] [--forkedDaapdLatency=latency] + [--get-cover-art] [-i latency] [-L latency] [-m backend] + [--meta-dir=directory] [-o backend] [--password=secret] [-r threshold] @@ -144,6 +146,19 @@ frames to the second.

+ + +

--get-coverart

+

+ This option requires the --meta-dir option to be set, and enables + shairport-sync to request cover art from the source and to transmit it through + the metadata pipe.

+

Please note that cover art data may be very large, and may place too great a + burden on your network. Also, at the time of writing, there appears to be a + serious bug in iTunes which stalls transmission of large cover art items, + stalling and potentially crashing shairport-sync. +

+

-h | --help

@@ -178,6 +193,17 @@ +

--meta-dir=directory

+

+ Listen for metadata coming from the source and send it, along with metadata from + shairport-sync itself, to a pipe called shairport-sync-metadata + in the directory you specify. If you add the --get-cover-art then + cover art will be sent through the pipe too. See https://github.com/mikebrady/shairport-sync-metadata-reader + for a sample metadata reader. +

+ + +

-m mdnsbackend | --mdns=mdnsbackend

Force the use of the specified mDNS backend to advertise the @@ -258,7 +284,7 @@

Exit play mode if the stream disappears for more than timeout seconds.

-

When shairport-sync plays an audio stream, it startes a play +

When shairport-sync plays an audio stream, it starts a play session and will return a busy signal to any other sources that attempt to use it. If the audio stream disappears for longer than timeout seconds, the play session will be terminated. From f7fc75c0a27680a0705ba7d82656a7bc11d25c23 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 4 Apr 2015 23:23:46 +0100 Subject: [PATCH 011/220] Point to 2.3 assets --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0cc07c26..49edb8b5 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ Assuming the usual build essentials and git, Debian, Ubuntu and Raspian users ca - `apt-get install avahi-daemon libavahi-client-dev` if you want to use Avahi (recommended). - `apt-get install libssl-dev` if you want to use OpenSSL and libcrypto, or use PolarSSL otherwise. - `apt-get install libpolarssl-dev` if you want to use PolarSSL, or use OpenSSL/libcrypto otherwise. -- `apt-get install libsoxr-dev` if you want support for libsoxr-based resampling. This library is not yet part of Raspian; instructions for how to build it from source are available at [LIBSOXR.md](https://github.com/mikebrady/shairport-sync/blob/2.2/LIBSOXR.md). +- `apt-get install libsoxr-dev` if you want support for libsoxr-based resampling. This library is not yet part of Raspian; instructions for how to build it from source are available at [LIBSOXR.md](https://github.com/mikebrady/shairport-sync/blob/2.3/LIBSOXR.md). Download Shairport Sync: @@ -103,7 +103,7 @@ Run `$sudo make install` to install `shairport-sync` along with an init script w Man Page -------- -You can see a web version of the man page here: http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/2.2/man/shairport-sync.html +You can see a web version of the man page here: http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/2.3/man/shairport-sync.html Configuring Shairport Sync From e530e5346041c909f649e1936f91e7e8b7db168e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 4 Apr 2015 23:27:39 +0100 Subject: [PATCH 012/220] Make a hyperlink of the reference to the metadata reader. --- man/shairport-sync.7.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml index 8b7685ed..235eeb18 100644 --- a/man/shairport-sync.7.xml +++ b/man/shairport-sync.7.xml @@ -214,7 +214,7 @@ Listen for metadata coming from the source and send it, along with metadata from shairport-sync itself, to a pipe called shairport-sync-metadata in the directory you specify. If you add the --get-cover-art then - cover art will be sent through the pipe too. See https://github.com/mikebrady/shairport-sync-metadata-reader + cover art will be sent through the pipe too. See for a sample metadata reader.

From 4ec9fd5b9be1862a02a6a56c620c403a6188c03e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 6 Apr 2015 13:52:13 +0100 Subject: [PATCH 013/220] Begin to support a settings file -- not working yet. --- common.h | 1 + configure.ac | 4 +++ scripts/shairport-sync.conf | 52 +++++++++++++++++++++++++++++++++++++ shairport.c | 29 +++++++++++++++++++++ 4 files changed, 86 insertions(+) create mode 100644 scripts/shairport-sync.conf diff --git a/common.h b/common.h index 9a35d187..5707da31 100644 --- a/common.h +++ b/common.h @@ -39,6 +39,7 @@ enum stuffing_type { typedef struct { + char *configuration_file; // if used, all other command-line arguments are ignored char *password; char *apname; #ifdef CONFIG_METADATA diff --git a/configure.ac b/configure.ac index 04722753..1b1ffd21 100644 --- a/configure.ac +++ b/configure.ac @@ -35,6 +35,10 @@ PKG_CHECK_MODULES( [POPT], [popt], [LIBS="${POPT_LIBS} ${LIBS}" AC_DEFINE([HAVE_LIBPOPT],[1],[Define to 1 if you have popt])]) +PKG_CHECK_MODULES( + [LIBCONFIG], [libconfig], + [LIBS="${LIBCONFIG_LIBS} ${LIBS}" + AC_DEFINE([HAVE_LIBCONFIG],[1],[Define to 1 if you have libconfig])]) # Look for piddir flag AC_ARG_WITH(piddir, [ --with-piddir= Specify a pathname to a directory in which to write the PID file.], [ diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf new file mode 100644 index 00000000..101d44ef --- /dev/null +++ b/scripts/shairport-sync.conf @@ -0,0 +1,52 @@ +// Sample Configuration File for Shairport Sync + +// General Settings +general = +{ +// name = "Music Player"; // default is "Shairport Sync on " +// daemonize = no; +// mdns_backend = avahi; // not used, not tested +// output_backend = alsa; // setting is not used, alsa is default +// port = 5000; +// password = "secret"; // default is no password needed +// interpolation = basic; // aka "stuffing". Default is basic, alternative is soxr +// statistics = no; // print statistics in the log +// tolerance = 88; // allow this number of frames of drift before correcting it +// resync_threshold = 2205; a synchronisation error greater than this will cause resynchronisation +// log_verbosity = 0; // "0" means no verbosity, "3" is highest. +}; + +latency = +{ +// default = 88200; +// itunes = 99400; +// airplay = 88200; +// forkedDaapd = 99400; +}; + +metadata = +{ +// metadata_feed_enabled = no; +// include_cover_art = no; +// metadata_pipe = "/tmp/shairport-sync-coverart"; +}; + +sessioncontrol = +{ +// run_before_play_begins = "/path/to/application"; +// run_after_play_ends = "/path/to/application"; +// wait_for_completion = no; +// allow_session_interruption = no; +// session_timeout = 120; +}; + +alsa = +{ +// device_type = software; +// device = "default"; +// mixer = "default"; // default is that the device is also the mixer +// mixer_control_name = "PCM" +}; + + + diff --git a/shairport.c b/shairport.c index 565ced69..ff8f690c 100644 --- a/shairport.c +++ b/shairport.c @@ -36,6 +36,7 @@ #include #include #include +#include #include "config.h" @@ -199,6 +200,7 @@ int parse_options(int argc, char **argv) { char *stuffing = NULL; /* used for picking up the stuffing option */ poptContext optCon; /* context for parsing command-line options */ struct poptOption optionsTable[] = { + { "config-file", 0, POPT_ARG_STRING, &config.configuration_file, 0, NULL } , { "statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL}, { "version", 'V', POPT_ARG_NONE, NULL, 0, NULL}, { "verbose", 'v', POPT_ARG_NONE, NULL, 'v', NULL }, @@ -268,6 +270,32 @@ int parse_options(int argc, char **argv) { if (c < -1) { die("%s: %s",poptBadOption(optCon, POPT_BADOPTION_NOALIAS),poptStrerror(c)); } + + // now, die if the -c option was chosen and there were more than two arguments, which should be the app name and the single -c option. + + if (config.configuration_file!=NULL) { + if ((argc!=2) && !((argc==3) && (debuglev!=0))) + die("If you choose the -c or --config-file option, no other command-line arguments are permitted -- %d arguments seen",argc); + // parse the arguments + config_t cfg; + config_setting_t *setting; + const char *str; + + config_init(&cfg); + + /* Read the file. If there is an error, report it and exit. */ + if(config_read_file(&cfg, config.configuration_file)) { + /* Get the store name. */ + if(config_lookup_string(&cfg, "name", &str)) + debug(1,"Store name: %s", str); + else + debug(1, "No 'name' setting in configuration file."); + } else { + debug(1,"Couldn't open configuration file."); + } + + config_destroy(&cfg); + } /* Print out options */ debug(2,"statistics_requester status is %d.",config.statistics_requested); @@ -292,6 +320,7 @@ int parse_options(int argc, char **argv) { debug(2,"metadata directory is \"%s\".",config.meta_dir); debug(2,"get-coverart is %d.",config.get_coverart); #endif + return optind+1; } From 816c2a8814d570733e6adb397bd2e17b9731d0b8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 10 Apr 2015 11:28:51 +0100 Subject: [PATCH 014/220] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 49edb8b5..15a2e581 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Shairport Sync works by using timing information present in the audio data strea To maintain the exact latency required, if an output device is running slow relative to the source, Shairport Sync will delete frames of audio to allow the device to keep up; if the device is running fast, Shairport Sync will insert frames to keep time. The number of frames inserted or deleted is so small as to be almost inaudible. Frames are inserted or deleted as necessary at pseudorandom intervals. Alternatively, with `libsoxr` support, Shairport Sync can resample the audio feed to ensure the output device can keep up. This is even less obtrusive than insertion and deletion but requires a good deal of processing power -- most embedded devices probably can't support it. -There are two default latency settings, chosen automatically. One latency matches the latency used by recent versions of iTunes when playing audio and the other matches the latency used by older version of iTunes, by iOS devices and by iTunes and Quicktime Player when playing video. +There are three default latency settings, chosen automatically. One latency matches the latency used by recent versions of iTunes when playing audio and the other matches the latency used by older version of iTunes, by iOS devices and by iTunes and Quicktime Player when playing video. A third default latency is used when the audio source is `forked-daapd`. Shairport Sync is a pretty substantial rewrite of Shairport 1.0 by James Laird and others -- please see https://github.com/abrasive/shairport/blob/master/README.md#contributors-to-version-1x for a list of the contributors to Shairport 1.x and Shairport 0.x. From a "heritage" point of view, Shairport Sync is a fork of Shairport 1.0 and the active branch is Shairport Sync 2.2. From 40de286b56646d9590dd5731c166437541486685 Mon Sep 17 00:00:00 2001 From: "Joseph C. Lehner" Date: Thu, 16 Apr 2015 11:53:39 +0200 Subject: [PATCH 015/220] Fix deadlocks in RTSP code If the ANNOUNCE and/or SETUP method fails, the play_lock mutex is never unlocked, thus blocking other clients from connecting. This patch unlocks play_lock if an error occurs in either ANNOUNCE or SETUP. --- rtsp.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/rtsp.c b/rtsp.c index 91e0728e..4d4c4811 100644 --- a/rtsp.c +++ b/rtsp.c @@ -681,25 +681,25 @@ static void handle_setup(rtsp_conn_info *conn, } char *hdr = msg_get_header(req, "Transport"); if (!hdr) - return; + goto error; char *p; p = strstr(hdr, "control_port="); if (!p) - return; + goto error; p = strchr(p, '=') + 1; cport = atoi(p); p = strstr(hdr, "timing_port="); if (!p) - return; + goto error; p = strchr(p, '=') + 1; tport = atoi(p); rtsp_take_player(); rtp_setup(&conn->remote, cport, tport, active_remote, &lsport,&lcport,<port); if (!lsport) - return; + goto error; char *q; p = strstr(hdr,"control_port="); if (p) { @@ -727,6 +727,12 @@ static void handle_setup(rtsp_conn_info *conn, msg_add_header(resp, "Session", "1"); resp->respcode = 200; + return; + +error: + warn("Error in setup request."); + pthread_mutex_unlock(&play_lock); + resp->respcode = 451; // invalid arguments } static void handle_ignore(rtsp_conn_info *conn, @@ -1124,7 +1130,7 @@ static void handle_announce(rtsp_conn_info *conn, if (!paesiv || !prsaaeskey || !pfmtp) { warn("required params missing from announce"); - return; + goto out; } int len, keylen; @@ -1132,7 +1138,7 @@ static void handle_announce(rtsp_conn_info *conn, if (len != 16) { warn("client announced aeskey of %d bytes, wanted 16", len); free(aesiv); - return; + goto out; } memcpy(conn->stream.aesiv, aesiv, 16); free(aesiv); @@ -1143,7 +1149,7 @@ static void handle_announce(rtsp_conn_info *conn, if (keylen != 16) { warn("client announced rsaaeskey of %d bytes, wanted 16", keylen); free(aeskey); - return; + goto out; } memcpy(conn->stream.aeskey, aeskey, 16); free(aeskey); @@ -1169,6 +1175,11 @@ static void handle_announce(rtsp_conn_info *conn, resp->respcode = 453; debug(1,"Already playing."); } + +out: + if (resp->respcode != 200 && resp->respcode != 453) { + pthread_mutex_unlock(&play_lock); + } } From 397aa2776d8a31b12bbba60ea2eb9e254025d548 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 19 Apr 2015 11:53:25 +0100 Subject: [PATCH 016/220] Add note about RTSP code bug fix. --- RELEASENOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 884f152d..9087bcf0 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -6,6 +6,7 @@ Version 2.3 (Development branch, work in progress -- not a release yet.) Beware: there appears to be a serious bug in iTunes, such that it may stall for a long period when sending large (more than a few hundred kilobytes) coverart images. * Bugfix + * Fix a bug whereby if the ANNOUNCE and/or SETUP method fails, the play_lock mutex is never unlocked, thus blocking other clients from connecting. This can affect all types of users, but particularly Pulseaudio users. (Thanks to https://github.com/jclehner.) * Modify the init script to start after all services are ready. Add in a commented-out sleep command if users find it necessary (thanks to https://github.com/BNoiZe). * Two memory leaks fixed. * An error handling time specifications for flushes was causing an audible glitch when pausing and resuming some tracks. This has been fixed (thanks to https://github.com/Hamster128). From 32b1402b370ca717119657bfed154d83f7142826 Mon Sep 17 00:00:00 2001 From: Mke Brady Date: Mon, 20 Apr 2015 18:46:47 +0100 Subject: [PATCH 017/220] Add libcrypto to libssl when choosing openssl. This bug is evident only with Arch Linux. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 1b1ffd21..a21f5c5f 100644 --- a/configure.ac +++ b/configure.ac @@ -62,7 +62,7 @@ AC_ARG_WITH(ssl, [ choose --with-ssl=openssl or --with-ssl=polarssl for encrypti fi if test "x${with_ssl}" = xopenssl ; then PKG_CHECK_MODULES( - [SSL], [libssl], + [SSL], [libssl,libcrypto], [LIBS="${SSL_LIBS} ${LIBS}" AC_DEFINE([HAVE_LIBCRYPTO],[1],[Define to 1 if you have libcrypto]) AC_DEFINE([HAVE_LIBSSL],[1],[Define to 1 if you have libssl])]) From d058f5644b10df67cc83fd8e1ea385a881d5c409 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 20 Apr 2015 18:48:06 +0100 Subject: [PATCH 018/220] Update RELEASENOTES.md --- RELEASENOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9087bcf0..c7fd6096 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -6,6 +6,7 @@ Version 2.3 (Development branch, work in progress -- not a release yet.) Beware: there appears to be a serious bug in iTunes, such that it may stall for a long period when sending large (more than a few hundred kilobytes) coverart images. * Bugfix + * Fix a bug when compiling for Arch Linux on Raspberry Pi 2 (thanks to https://github.com/joaodriessen). * Fix a bug whereby if the ANNOUNCE and/or SETUP method fails, the play_lock mutex is never unlocked, thus blocking other clients from connecting. This can affect all types of users, but particularly Pulseaudio users. (Thanks to https://github.com/jclehner.) * Modify the init script to start after all services are ready. Add in a commented-out sleep command if users find it necessary (thanks to https://github.com/BNoiZe). * Two memory leaks fixed. From d8d4c88b3a6e32c614626650d64d9f1d820e000d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 20 Apr 2015 21:42:05 +0100 Subject: [PATCH 019/220] Update note about memory leaks --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c7fd6096..c42bd786 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -9,7 +9,7 @@ Beware: there appears to be a serious bug in iTunes, such that it may stall for * Fix a bug when compiling for Arch Linux on Raspberry Pi 2 (thanks to https://github.com/joaodriessen). * Fix a bug whereby if the ANNOUNCE and/or SETUP method fails, the play_lock mutex is never unlocked, thus blocking other clients from connecting. This can affect all types of users, but particularly Pulseaudio users. (Thanks to https://github.com/jclehner.) * Modify the init script to start after all services are ready. Add in a commented-out sleep command if users find it necessary (thanks to https://github.com/BNoiZe). - * Two memory leaks fixed. + * Two memory leaks fixed (thanks to https://github.com/pdgendt). * An error handling time specifications for flushes was causing an audible glitch when pausing and resuming some tracks. This has been fixed (thanks to https://github.com/Hamster128). Version 2.2.2 From 2e7910326ef49535ea84d6492b8c4df3201beebd Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 20 Apr 2015 21:44:49 +0100 Subject: [PATCH 020/220] Make 2.3 a release --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c42bd786..f3228894 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,4 +1,4 @@ -Version 2.3 (Development branch, work in progress -- not a release yet.) +Version 2.3 ----- * Enhancement * Metadata support is now a compile-time option: `--with-metadata`. From 85ca93176d20c55ae23003020c1eb7e77ce8fb13 Mon Sep 17 00:00:00 2001 From: Mke Brady Date: Wed, 22 Apr 2015 21:07:15 +0100 Subject: [PATCH 021/220] Add --with-initscript to allow us to omit the initscript. --- Makefile.am | 3 +++ configure.ac | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/Makefile.am b/Makefile.am index eced4e63..0186d011 100644 --- a/Makefile.am +++ b/Makefile.am @@ -38,6 +38,9 @@ if USE_DNS_SD shairport_sync_SOURCES += mdns_dns_sd.c endif +if INSTALL_INITSCRIPT install-exec-hook: [ -f /etc/init.d/shairport-sync ] || cp scripts/shairport-sync /etc/init.d/ update-rc.d shairport-sync defaults 90 10 +endif + diff --git a/configure.ac b/configure.ac index a21f5c5f..2ca893b5 100644 --- a/configure.ac +++ b/configure.ac @@ -39,6 +39,12 @@ PKG_CHECK_MODULES( [LIBCONFIG], [libconfig], [LIBS="${LIBCONFIG_LIBS} ${LIBS}" AC_DEFINE([HAVE_LIBCONFIG],[1],[Define to 1 if you have libconfig])]) + +# Check to see if we should include the initscript + +AC_ARG_WITH([initscript], +[ --with-initscript = include a startup script], ,[with_initscript=1]) +AM_CONDITIONAL([INSTALL_INITSCRIPT], [test "x$with_initscript" = "x1"]) # Look for piddir flag AC_ARG_WITH(piddir, [ --with-piddir= Specify a pathname to a directory in which to write the PID file.], [ From 916d4c806fdd1a2abce4a4a9f6865b9726ab7109 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 22 Apr 2015 21:11:37 +0100 Subject: [PATCH 022/220] =?UTF-8?q?Add=20mention=20of=20=E2=80=93without-i?= =?UTF-8?q?nitscript?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- RELEASENOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f3228894..aff094c2 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,6 +1,7 @@ Version 2.3 ----- * Enhancement + * Adding the System V startup script (the "initscript") is now a configuration option. The default is to include it, so if you want to omit the installation of the initscript, add the configuration option `--without-initscript`. * Metadata support is now a compile-time option: `--with-metadata`. * A very experimental metadata feed has been added. Use the option `-M `, e.g. `-M /tmp`. Shairport Sync will provide metadata in a pipe called `/shairport-sync-metadata`. There's a sample metadata reader at https://github.com/mikebrady/shairport-sync-metadata-reader. The format of the metadata is a mixture of XML-style tags, 4-character codes and base64 data. Please look at `rtsp.c` and `player.c` for examples. Please note that the format of the metadata may change. Beware: there appears to be a serious bug in iTunes, such that it may stall for a long period when sending large (more than a few hundred kilobytes) coverart images. From 69e0610519379e53961ed632cd5902981595273e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 25 Apr 2015 17:06:13 +0100 Subject: [PATCH 023/220] Change branch names Change branch names --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 15a2e581..c005669d 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Audio played by a Shairport Sync-powered device stays synchronised with the sour Shairport Sync does not support AirPlay video or photo streaming. -This branch -- 2.3 -- is the development branch of Shairport Sync and is unstable. To access the stable branch, please switch to the 2.2 branch. +This branch -- "development" -- is the development branch of Shairport Sync and is unstable. To access the stable branch, please switch to the "master" branch. More Information ---------- From 33d5215d736578aa92b88edd1b999f60e3f39adf Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 27 Apr 2015 16:51:04 +0000 Subject: [PATCH 024/220] Add install-exec-hook script to install /etc/shairport-sync.conf.example and shairport-sync.conf if it's not already there. --- Makefile.am | 2 ++ scripts/shairport-sync.conf | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 0186d011..1c701e06 100644 --- a/Makefile.am +++ b/Makefile.am @@ -40,6 +40,8 @@ endif if INSTALL_INITSCRIPT install-exec-hook: + cp scripts/shairport-sync.conf /etc/shairport-sync.conf.sample + [ -f /etc/shairport-sync.conf ] || cp scripts/shairport-sync.conf /etc/shairport-sync.conf [ -f /etc/init.d/shairport-sync ] || cp scripts/shairport-sync /etc/init.d/ update-rc.d shairport-sync defaults 90 10 endif diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 101d44ef..70365f0f 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -16,7 +16,7 @@ general = // log_verbosity = 0; // "0" means no verbosity, "3" is highest. }; -latency = +latencies = { // default = 88200; // itunes = 99400; From e73657d6edc8dcd65856f003c4a1450625d7859c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 29 Apr 2015 15:22:47 +0100 Subject: [PATCH 025/220] Add support for conditional compilation of support for command-line args and-or configuration files --- Makefile.am | 2 ++ common.h | 4 ++- configure.ac | 32 ++++++++++++++---- shairport.c | 91 +++++++++++++++++++++++++++++++++++++--------------- 4 files changed, 96 insertions(+), 33 deletions(-) diff --git a/Makefile.am b/Makefile.am index 1c701e06..f9ac8ef8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -40,8 +40,10 @@ endif if INSTALL_INITSCRIPT install-exec-hook: +if INSTALL_CONFIG_FILES cp scripts/shairport-sync.conf /etc/shairport-sync.conf.sample [ -f /etc/shairport-sync.conf ] || cp scripts/shairport-sync.conf /etc/shairport-sync.conf +endif [ -f /etc/init.d/shairport-sync ] || cp scripts/shairport-sync /etc/init.d/ update-rc.d shairport-sync defaults 90 10 endif diff --git a/common.h b/common.h index 5707da31..4adc6d83 100644 --- a/common.h +++ b/common.h @@ -3,6 +3,8 @@ #include #include +#include + #include "config.h" #include "audio.h" #include "mdns.h" @@ -39,7 +41,7 @@ enum stuffing_type { typedef struct { - char *configuration_file; // if used, all other command-line arguments are ignored + config_t cfg; // if used, all other command-line arguments are ignored char *password; char *apname; #ifdef CONFIG_METADATA diff --git a/configure.ac b/configure.ac index 2ca893b5..7607f749 100644 --- a/configure.ac +++ b/configure.ac @@ -31,21 +31,41 @@ fi AC_CHECK_LIB([daemon],[daemon_log], , AC_MSG_ERROR(libdaemon needed)) AC_CHECK_LIB([pthread],[pthread_create], , AC_MSG_ERROR(pthread library needed)) AC_CHECK_LIB([m],[exp], , AC_MSG_ERROR(maths library needed)) + +AC_MSG_RESULT(>>Including libpopt) PKG_CHECK_MODULES( [POPT], [popt], [LIBS="${POPT_LIBS} ${LIBS}" AC_DEFINE([HAVE_LIBPOPT],[1],[Define to 1 if you have popt])]) -PKG_CHECK_MODULES( - [LIBCONFIG], [libconfig], - [LIBS="${LIBCONFIG_LIBS} ${LIBS}" - AC_DEFINE([HAVE_LIBCONFIG],[1],[Define to 1 if you have libconfig])]) - -# Check to see if we should include the initscript AC_ARG_WITH([initscript], [ --with-initscript = include a startup script], ,[with_initscript=1]) AM_CONDITIONAL([INSTALL_INITSCRIPT], [test "x$with_initscript" = "x1"]) +# Check if we want to support reading arguments from the command line +AC_ARG_WITH([command_line_argument_support], +[ --with-command-line-argument-support = read configuration from command line arguments], ,[with_command_line_argument_support=1]) + +if test "x${with_command_line_argument_support}" = x1 ; then + AC_MSG_RESULT(>>Including command line argument support) + AC_DEFINE([COMMAND_LINE_ARGUMENT_SUPPORT],[1],[Define to 1 if you want to support command line arguments]) +fi + +# Check if we want to support reading from a configuration file chosen -- i.e. take parameters from /etc/shairport-sync.conf +AC_ARG_WITH([configfile_support], +[ --with-configfile-support = read configuration from a configuration file], ,[with_configfile_support=1]) + +if test "x${with_configfile_support}" = x1 ; then + AC_MSG_RESULT(>>Including configuration file support) + PKG_CHECK_MODULES( + [LIBCONFIG], [libconfig], + [LIBS="${LIBCONFIG_LIBS} ${LIBS}"]) + AC_DEFINE([SUPPORT_CONFIG_FILES],[1],[Define to 1 if you want to support configuration files]) +fi +AM_CONDITIONAL([INSTALL_CONFIG_FILES], [test "x${with_configfile_support}" = "x1"]) + +# Check to see if we should include the initscript + # Look for piddir flag AC_ARG_WITH(piddir, [ --with-piddir= Specify a pathname to a directory in which to write the PID file.], [ AC_MSG_CHECKING(--with-piddir argument) diff --git a/shairport.c b/shairport.c index ff8f690c..bc5a7d5a 100644 --- a/shairport.c +++ b/shairport.c @@ -137,6 +137,9 @@ void print_version(void) { #endif #ifdef CONFIG_METADATA strcat(version_string,"-metadata"); +#endif +#ifdef SUPPORT_CONFIG_FILES + strcat(version_string,"-configfile"); #endif printf("%s\n",version_string); } @@ -144,14 +147,15 @@ void print_version(void) { void usage(char *progname) { printf("Usage: %s [options...]\n", progname); printf(" or: %s [options...] -- [audio output-specific options]\n", progname); - - printf("\n"); - printf("Mandatory arguments to long options are mandatory for short options too.\n"); - printf("\n"); printf("Options:\n"); printf(" -h, --help show this help\n"); printf(" -V, --version show version information\n"); + printf(" -k, --kill kill the existing shairport daemon.\n"); + printf(" -D, --disconnectFromOutput disconnect immediately from the output device.\n"); + printf(" -R, --reconnectToOutput reconnect to the output device.\n"); + +#ifdef COMMAND_LINE_ARGUMENT_SUPPORT printf(" -v, --verbose -v print debug information; -vv more; -vvv lots\n"); printf(" -p, --port=PORT set RTSP listening port\n"); printf(" -a, --name=NAME set advertised name\n"); @@ -167,9 +171,6 @@ void usage(char *progname) { printf(" \"soxr\" uses libsoxr to minimally resample packet frames -- moderate processor overhead.\n"); printf(" \"soxr\" option only available if built with soxr support.\n"); printf(" -d, --daemon daemonise.\n"); - printf(" -k, --kill kill the existing shairport daemon.\n"); - printf(" -D, --disconnectFromOutput disconnect immediately from the output device.\n"); - printf(" -R, --reconnectToOutput reconnect to the output device.\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, e.g. /usr/bin/logger.\n"); @@ -187,6 +188,10 @@ void usage(char *progname) { #ifdef CONFIG_METADATA printf(" --meta-dir=DIR get metadata from the source and pipe it to DIR/shairport-sync-metadata, e.g. --meta-dir=/tmp.\n"); printf(" --get-coverart get cover art from the source and pipe it to DIR/shairport-sync-metadata, e.g. --meta-dir=/tmp.\n"); +#endif +#endif +#ifdef SUPPORT_CONFIG_FILES + printf("All other settings are in the configuration file at /etc/%s.conf\n",progname); #endif printf("\n"); mdns_ls_backends(); @@ -195,12 +200,13 @@ void usage(char *progname) { } int parse_options(int argc, char **argv) { + char *stuffing = NULL; /* used for picking up the stuffing option */ + +#ifdef COMMAND_LINE_ARGUMENT_SUPPORT signed char c; /* used for argument parsing */ int i = 0; /* used for tracking options */ - char *stuffing = NULL; /* used for picking up the stuffing option */ poptContext optCon; /* context for parsing command-line options */ struct poptOption optionsTable[] = { - { "config-file", 0, POPT_ARG_STRING, &config.configuration_file, 0, NULL } , { "statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL}, { "version", 'V', POPT_ARG_NONE, NULL, 0, NULL}, { "verbose", 'v', POPT_ARG_NONE, NULL, 'v', NULL }, @@ -270,32 +276,65 @@ int parse_options(int argc, char **argv) { if (c < -1) { die("%s: %s",poptBadOption(optCon, POPT_BADOPTION_NOALIAS),poptStrerror(c)); } - - // now, die if the -c option was chosen and there were more than two arguments, which should be the app name and the single -c option. - - if (config.configuration_file!=NULL) { - if ((argc!=2) && !((argc==3) && (debuglev!=0))) - die("If you choose the -c or --config-file option, no other command-line arguments are permitted -- %d arguments seen",argc); - // parse the arguments +#endif + + +#ifdef SUPPORT_CONFIG_FILES + char configuration_file_path[4096]; + strcpy(configuration_file_path,"/etc/"); + strcat(configuration_file_path,argv[0]); + strcat(configuration_file_path,".conf"); + debug(1,"Looking for file \"%s\"",configuration_file_path); config_t cfg; config_setting_t *setting; const char *str; + int value; config_init(&cfg); - + debug(1,"config stuff initialised"); /* Read the file. If there is an error, report it and exit. */ - if(config_read_file(&cfg, config.configuration_file)) { - /* Get the store name. */ - if(config_lookup_string(&cfg, "name", &str)) - debug(1,"Store name: %s", str); - else - debug(1, "No 'name' setting in configuration file."); + if(config_read_file(&cfg,configuration_file_path)) { + debug(1,"Configuration file opened."); + + debug(1,"name"); + /* Get the Service Name. */ + if(config_lookup_string(&cfg, "general.name", &str)) + config.apname=strdup(str); + + debug(1,"daemonize"); + /* Get the Daemonize setting. */ + if(config_lookup_string(&cfg, "general.daemonize", &str)) { + if (strcasecmp(str,"no")==0) + config.daemonise=0; + else if (strcasecmp(str,"yes")==0) + config.daemonise=1; + else + die("Invalid daemonize option choice \"%\". It should be \"yes\" or \"no\""); + } + + debug(1,"mdns_backend"); + /* Get the mdns_backend setting. */ + if(config_lookup_string(&cfg, "general.mdns_backend", &str)) + config.mdns_name=strdup(str); + + debug(1,"output_backend"); + /* Get the output_backend setting. */ + if(config_lookup_string(&cfg, "general.output_backend", &str)) + config.output_name=strdup(str); + + debug(1,"port"); + /* Get the port setting. */ + if(config_lookup_int(&cfg, "general.port", &value)) + config.port=value; + debug(1,"done"); + } else { - debug(1,"Couldn't open configuration file."); + debug(1,"Line %d of the configuration file \"%s\":\n%s",config_error_line(&cfg),config_error_file(&cfg),config_error_text(&cfg)); } - config_destroy(&cfg); - } +// config_destroy(&cfg); +#endif + /* Print out options */ debug(2,"statistics_requester status is %d.",config.statistics_requested); From e02be8a6ae1766c4c61a874ce74a86507bdc2bcc Mon Sep 17 00:00:00 2001 From: Mke Brady Date: Wed, 29 Apr 2015 19:52:56 +0100 Subject: [PATCH 026/220] Add atexit functionality, continue to add config file support. --- common.h | 2 +- shairport.c | 45 ++++++++++++++++++++++++++++++--------------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/common.h b/common.h index 4adc6d83..8c50edf3 100644 --- a/common.h +++ b/common.h @@ -41,7 +41,7 @@ enum stuffing_type { typedef struct { - config_t cfg; // if used, all other command-line arguments are ignored + config_t cfg; char *password; char *apname; #ifdef CONFIG_METADATA diff --git a/shairport.c b/shairport.c index bc5a7d5a..141c7d56 100644 --- a/shairport.c +++ b/shairport.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include "config.h" @@ -61,6 +62,7 @@ static int shutting_down = 0; +static char* appName = NULL; void shairport_shutdown() { if (shutting_down) @@ -191,7 +193,7 @@ void usage(char *progname) { #endif #endif #ifdef SUPPORT_CONFIG_FILES - printf("All other settings are in the configuration file at /etc/%s.conf\n",progname); + printf("\nGeneral options can be configured in /etc/%s.conf.\n",appName); #endif printf("\n"); mdns_ls_backends(); @@ -282,7 +284,7 @@ int parse_options(int argc, char **argv) { #ifdef SUPPORT_CONFIG_FILES char configuration_file_path[4096]; strcpy(configuration_file_path,"/etc/"); - strcat(configuration_file_path,argv[0]); + strcat(configuration_file_path,appName); strcat(configuration_file_path,".conf"); debug(1,"Looking for file \"%s\"",configuration_file_path); config_t cfg; @@ -290,20 +292,20 @@ int parse_options(int argc, char **argv) { const char *str; int value; - config_init(&cfg); + config_init(&config.cfg); debug(1,"config stuff initialised"); /* Read the file. If there is an error, report it and exit. */ - if(config_read_file(&cfg,configuration_file_path)) { + if(config_read_file(&config.cfg,configuration_file_path)) { debug(1,"Configuration file opened."); debug(1,"name"); /* Get the Service Name. */ - if(config_lookup_string(&cfg, "general.name", &str)) - config.apname=strdup(str); + if(config_lookup_string(&config.cfg, "general.name", &str)) + config.apname=str; debug(1,"daemonize"); /* Get the Daemonize setting. */ - if(config_lookup_string(&cfg, "general.daemonize", &str)) { + if(config_lookup_string(&config.cfg, "general.daemonize", &str)) { if (strcasecmp(str,"no")==0) config.daemonise=0; else if (strcasecmp(str,"yes")==0) @@ -314,25 +316,25 @@ int parse_options(int argc, char **argv) { debug(1,"mdns_backend"); /* Get the mdns_backend setting. */ - if(config_lookup_string(&cfg, "general.mdns_backend", &str)) - config.mdns_name=strdup(str); + if(config_lookup_string(&config.cfg, "general.mdns_backend", &str)) + config.mdns_name=str; debug(1,"output_backend"); /* Get the output_backend setting. */ - if(config_lookup_string(&cfg, "general.output_backend", &str)) - config.output_name=strdup(str); + if(config_lookup_string(&config.cfg, "general.output_backend", &str)) + config.output_name=str; debug(1,"port"); /* Get the port setting. */ - if(config_lookup_int(&cfg, "general.port", &value)) + if(config_lookup_int(&config.cfg, "general.port", &value)) config.port=value; debug(1,"done"); } else { - debug(1,"Line %d of the configuration file \"%s\":\n%s",config_error_line(&cfg),config_error_file(&cfg),config_error_text(&cfg)); + debug(1,"Line %d of the configuration file \"%s\":\n%s", + config_error_line(&config.cfg),config_error_file(&config.cfg),config_error_text(&config.cfg)); } -// config_destroy(&cfg); #endif /* Print out options */ @@ -423,11 +425,18 @@ const char *pid_file_proc(void) { } #endif +void exit_function() { + debug(1,"atexit function called..."); +#ifdef SUPPORT_CONFIG_FILES + config_destroy(&config.cfg); +#endif +} + int main(int argc, char **argv) { daemon_set_verbosity(LOG_DEBUG); - memset(&config, 0, sizeof(config)); // also clears all strings, BTW + atexit(exit_function); // set defaults config.statistics_requested - 0; // don't print stats in the log @@ -448,6 +457,12 @@ int main(int argc, char **argv) { snprintf(config.apname, 20 + 100, "Shairport Sync on %s", hostname); set_requested_connection_state_to_output(1); // we expect to be able to connect to the output device + // this is a bit weird, but apparently necessary + char* basec = strdup(argv[0]); + char* bname = basename(basec); + appName = strdup(bname); + free(basec); + /* Check if we are called with -V or --version parameter */ if (argc >= 2 && ((strcmp(argv[1], "-V")==0) || (strcmp(argv[1], "--version")==0))) { print_version(); From 3a93b4ae065a96cc105c2acd23cb0f486fc1d72b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 4 May 2015 17:11:13 +0100 Subject: [PATCH 027/220] Separate installin an initscript from installing configuration files. --- Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index f9ac8ef8..c016d98a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -38,12 +38,12 @@ if USE_DNS_SD shairport_sync_SOURCES += mdns_dns_sd.c endif -if INSTALL_INITSCRIPT install-exec-hook: if INSTALL_CONFIG_FILES cp scripts/shairport-sync.conf /etc/shairport-sync.conf.sample [ -f /etc/shairport-sync.conf ] || cp scripts/shairport-sync.conf /etc/shairport-sync.conf endif +if INSTALL_INITSCRIPT [ -f /etc/init.d/shairport-sync ] || cp scripts/shairport-sync /etc/init.d/ update-rc.d shairport-sync defaults 90 10 endif From 9cfc61790e7b7762501eb4d54dffb860701a98e1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 4 May 2015 17:14:15 +0100 Subject: [PATCH 028/220] Pass in libconfig info during init. --- audio.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/audio.h b/audio.h index 96b56d61..9d9ac05a 100644 --- a/audio.h +++ b/audio.h @@ -2,6 +2,7 @@ #define _AUDIO_H #include +#include typedef struct { double airplay_volume; @@ -18,7 +19,7 @@ typedef struct { char *name; // start of program - int (*init)(int argc, char **argv); + int (*init)(int argc, char **argv, config_t* cfgp); // at end of program void (*deinit)(void); From d1b06891af18c3432e727b63f86980d05ad60deb Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 4 May 2015 17:15:19 +0100 Subject: [PATCH 029/220] Parse libconfig info during init. --- audio_alsa.c | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 9d29381d..42661a95 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -35,7 +35,7 @@ #include "audio.h" static void help(void); -static int init(int argc, char **argv); +static int init(int argc, char **argv, config_t* cfgp); static void deinit(void); static void start(int sample_rate); static void play(short buf[], int samples); @@ -93,7 +93,10 @@ static void help(void) { ); } -static int init(int argc, char **argv) { +static int init(int argc, char **argv, config_t *cfgp) { + const char *str; + int value; + int hardware_mixer = 0; optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour @@ -127,6 +130,34 @@ static int init(int argc, char **argv) { if (optind < argc) die("Invalid audio argument: %s", argv[optind]); + + if (cfgp!=NULL) { + /* Get the Output Device Name. */ + if(config_lookup_string(cfgp, "alsa.output_device", &str)) { + alsa_out_dev = (char*)str; + } + + /* Get the Mixer Type setting. */ + if(config_lookup_string(cfgp, "alsa.mixer_type", &str)) { + if (strcasecmp(str,"software")==0) + hardware_mixer=0; + else if (strcasecmp(str,"hardware")==0) + hardware_mixer=1; + else + die("Invalid alsa mixer option choice \"%s\". It should be \"software\" or \"hardware\""); + } + + /* Get the Mixer Device Name. */ + if(config_lookup_string(cfgp, "alsa.mixer_device", &str)) { + alsa_mix_dev = (char*)str; + } + + /* Get the Mixer Control Name. */ + if(config_lookup_string(cfgp, "alsa.mixer_control_name", &str)) { + alsa_mix_ctrl = (char*)str; + } + + } if (!hardware_mixer) return 0; From df7a046b63efbe2924f0c22aab29a8325542afef Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 4 May 2015 17:16:53 +0100 Subject: [PATCH 030/220] Pass in libconfig info during init. --- audio_ao.c | 2 +- audio_dummy.c | 2 +- audio_pulse.c | 2 +- audio_sndio.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/audio_ao.c b/audio_ao.c index d8b0cbd5..9b7b5d36 100644 --- a/audio_ao.c +++ b/audio_ao.c @@ -42,7 +42,7 @@ static void help(void) { ); } -static int init(int argc, char **argv) { +static int init(int argc, char **argv, config_t *cfgp) { ao_initialize(); int driver = ao_default_driver_id(); ao_option *ao_opts = NULL; diff --git a/audio_dummy.c b/audio_dummy.c index 92129404..d7531a0b 100644 --- a/audio_dummy.c +++ b/audio_dummy.c @@ -32,7 +32,7 @@ int Fs; long long starttime, samples_played; -static int init(int argc, char **argv) { +static int init(int argc, char **argv, config_t *cfgp) { return 0; } diff --git a/audio_pulse.c b/audio_pulse.c index 993130c1..93b0fb0a 100644 --- a/audio_pulse.c +++ b/audio_pulse.c @@ -58,7 +58,7 @@ static void help(void) { ); } -static int init(int argc, char **argv) { +static int init(int argc, char **argv, config_t *cfgp) { pulse_options.apname = config.apname; optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour diff --git a/audio_sndio.c b/audio_sndio.c index 564c7939..2ece8439 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -23,7 +23,7 @@ static struct sio_hdl *sio; static struct sio_par par; -static int init(int argc, char **argv) { +static int init(int argc, char **argv, config_t *cfgp) { sio = sio_open(SIO_DEVANY, SIO_PLAY, 0); if (!sio) die("sndio: cannot connect to sound server"); From 67aa54bd5b7f273fcbab2469b74eb142b090e8ec Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 4 May 2015 17:18:23 +0100 Subject: [PATCH 031/220] Add extra config fields for metadata, ignore volume, allow interruption and checking timeout. --- common.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/common.h b/common.h index 8c50edf3..f04a36e8 100644 --- a/common.h +++ b/common.h @@ -45,13 +45,17 @@ typedef struct { char *password; char *apname; #ifdef CONFIG_METADATA - char *meta_dir; + int metadata_enabled; + char *metadata_pipename; int get_coverart; #endif uint8_t hw_addr[6]; int port; + int ignore_volume_control; int resyncthreshold; // if it get's out of whack my more than this, resync. Zero means never resync. + int allow_session_interruption; int timeout; // while in play mode, exit if no packets of audio come in for more than this number of seconds . Zero means never exit. + int dont_check_timeout; // this is used to maintain backward compatability with the old -t option behaviour; only set by -t 0, cleared by everything else char *output_name; audio_output *output; char *mdns_name; @@ -65,8 +69,8 @@ typedef struct { int daemonise; int statistics_requested; char *cmd_start, *cmd_stop; - int tolerance; // allow this much drift before attempting to correct it int cmd_blocking; + int tolerance; // allow this much drift before attempting to correct it enum stuffing_type packet_stuffing; char *pidfile; char *logfile; From e4804a57e508e7cad5bb4fa4aded8453a0d21ada Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 4 May 2015 17:19:00 +0100 Subject: [PATCH 032/220] Pass in libconfig info during init. --- audio_pipe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_pipe.c b/audio_pipe.c index 46527611..367eb470 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -52,7 +52,7 @@ static void stop(void) { close(fd); } -static int init(int argc, char **argv) { +static int init(int argc, char **argv, config_t *cfgp) { if (argc != 1) die("bad argument(s) to pipe"); From 57431300869eaa2edac8ca5fb36db0d2637449e5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 4 May 2015 17:19:39 +0100 Subject: [PATCH 033/220] Bump version number --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 7607f749..3f84d058 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.1], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 99df6fe537cbe0b3f1d81f94579d7a3cb79f2fdf Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 4 May 2015 17:20:50 +0100 Subject: [PATCH 034/220] Use config.metadata_enabled in place of config.meta-dir. --- mdns_avahi.c | 2 +- mdns_external.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mdns_avahi.c b/mdns_avahi.c index 6b073115..35e397d3 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -63,7 +63,7 @@ static void register_service(AvahiClient *c) { int ret; #ifdef CONFIG_METADATA - if (config.meta_dir) { + if (config.metadata_enabled) { debug(1,"Avahi with metadata"); ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, diff --git a/mdns_external.c b/mdns_external.c index 832d8e06..48284e55 100644 --- a/mdns_external.c +++ b/mdns_external.c @@ -100,7 +100,7 @@ static int mdns_external_avahi_register(char *apname, int port) { char **argv; #ifdef CONFIG_METADATA - if (config.meta_dir) + if (config.metadata_enabled) argv=argvwithmetadata; else #endif @@ -146,7 +146,7 @@ static int mdns_external_dns_sd_register(char *apname, int port) { char **argv; #ifdef CONFIG_METADATA - if (config.meta_dir) + if (config.metadata_enabled) argv=argvwithmetadata; else #endif From ff79745a60c079f00d01bb6be3761e1aa86248a9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 4 May 2015 17:21:54 +0100 Subject: [PATCH 035/220] Set up sample and default settings. --- scripts/shairport-sync.conf | 43 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 70365f0f..c49f141b 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -3,17 +3,18 @@ // General Settings general = { -// name = "Music Player"; // default is "Shairport Sync on " -// daemonize = no; -// mdns_backend = avahi; // not used, not tested -// output_backend = alsa; // setting is not used, alsa is default +// name = "Shairport Sync Player"; // This is the name the service will advertise to iTunes. The default is "Shairport Sync on " +// daemonize = "no"; +// mdns_backend = "avahi"; // not used, not tested +// output_backend = "alsa"; // setting is not used, alsa is default // port = 5000; -// password = "secret"; // default is no password needed -// interpolation = basic; // aka "stuffing". Default is basic, alternative is soxr -// statistics = no; // print statistics in the log -// tolerance = 88; // allow this number of frames of drift before correcting it -// resync_threshold = 2205; a synchronisation error greater than this will cause resynchronisation +// password = "secret"; // default is no password +// interpolation = "basic"; // aka "stuffing". Default is "basic", alternative is "soxr" +// statistics = "no"; // print statistics in the log +// drift = 88; // allow this number of frames of drift before correcting it +// resync_threshold = 2205; // a synchronisation error greater than this will cause resynchronisation; 0 disables it // log_verbosity = 0; // "0" means no verbosity, "3" is highest. +// ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the volume control is set to. }; latencies = @@ -26,27 +27,25 @@ latencies = metadata = { -// metadata_feed_enabled = no; -// include_cover_art = no; -// metadata_pipe = "/tmp/shairport-sync-coverart"; +// enabled = "no"; +// include_cover_art = "no"; +// pipe_name = "/tmp/shairport-sync-metadata"; }; sessioncontrol = { -// run_before_play_begins = "/path/to/application"; -// run_after_play_ends = "/path/to/application"; -// wait_for_completion = no; -// allow_session_interruption = no; +// run_this_before_play_begins = "/path/to/application and args"; +// run_this_after_play_ends = "/path/to/application and args"; +// wait_for_completion = "no"; +// allow_session_interruption = "no"; // session_timeout = 120; }; alsa = { -// device_type = software; -// device = "default"; -// mixer = "default"; // default is that the device is also the mixer -// mixer_control_name = "PCM" +// output_device = "default"; +// mixer_type = "software"; // "software" or "hardware" +// mixer_device = "default"; //actually, the mixer default is whatever the output_device is. Normally you wouldn't have to use this. +// mixer_control_name = "PCM"; // the name of the mixer to use -- there is no default. }; - - From f68fe74cafe2e4a2258203494a77c9dc7b446bb5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 4 May 2015 17:23:21 +0100 Subject: [PATCH 036/220] Allow volume control to be ignored, use new config fields in place of old ones. --- rtsp.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/rtsp.c b/rtsp.c index 4d4c4811..e697a950 100644 --- a/rtsp.c +++ b/rtsp.c @@ -750,9 +750,11 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, cp_left -= next-cp; if (!strncmp(cp, "volume: ", 8)) { + if (config.ignore_volume_control==0) { float volume = atof(cp + 8); debug(2, "volume: %f\n", volume); player_volume(volume); + } } else #ifdef CONFIG_METADATA if(!strncmp(cp, "progress: ", 10)) { @@ -867,14 +869,13 @@ metadata_package metadata_queue_items[metadata_queue_size]; static pthread_t metadata_thread; void metadata_create(void) { - if (!config.meta_dir) + if (config.metadata_enabled==0) return; - const char fn[] = "shairport-sync-metadata"; - size_t pl = strlen(config.meta_dir) + 1 + strlen(fn); + size_t pl = strlen(config.metadata_pipename) + 1; char* path = malloc(pl+1); - snprintf(path, pl+1, "%s/%s", config.meta_dir, fn); + snprintf(path, pl+1, "%s", config.metadata_pipename); if (mkfifo(path, 0644) && errno != EEXIST) die("Could not create metadata FIFO %s", path); @@ -883,14 +884,13 @@ void metadata_create(void) { } void metadata_open(void) { - if (!config.meta_dir) + if (config.metadata_enabled==0) return; - const char fn[] = "shairport-sync-metadata"; - size_t pl = strlen(config.meta_dir) + 1 + strlen(fn); + size_t pl = strlen(config.metadata_pipename) + 1; char* path = malloc(pl+1); - snprintf(path, pl+1, "%s/%s", config.meta_dir, fn); + snprintf(path, pl+1, "%s", config.metadata_pipename); fd = open(path, O_WRONLY | O_NONBLOCK); //if (fd < 0) @@ -983,7 +983,7 @@ void* metadata_thread_function(void *ignore) { metadata_package pack; while (1) { pc_queue_get_item(&metadata_queue, &pack); - if (config.meta_dir) + if (config.metadata_enabled) metadata_process(pack.type,pack.code,pack.data,pack.length); if (pack.carrier) msg_free(pack.carrier); // release the message @@ -1104,8 +1104,8 @@ static void handle_set_parameter(rtsp_conn_info *conn, static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - // allow a session to be interrupted if the timeout is set to zero - if ((config.timeout==0) || (pthread_mutex_trylock(&play_lock) == 0)) { + // interrupt session if permitted + if ((config.allow_session_interruption==1) || (pthread_mutex_trylock(&play_lock) == 0)) { char *paesiv = NULL; char *prsaaeskey = NULL; char *pfmtp = NULL; From 392c03b23be1b72bdfe2236b6bbe4f31a023c4b6 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 4 May 2015 17:24:47 +0100 Subject: [PATCH 037/220] Add support for configuration file, and use new config fields where appropriate. --- shairport.c | 190 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 158 insertions(+), 32 deletions(-) diff --git a/shairport.c b/shairport.c index 141c7d56..70d920ca 100644 --- a/shairport.c +++ b/shairport.c @@ -204,18 +204,18 @@ void usage(char *progname) { int parse_options(int argc, char **argv) { char *stuffing = NULL; /* used for picking up the stuffing option */ -#ifdef COMMAND_LINE_ARGUMENT_SUPPORT signed char c; /* used for argument parsing */ int i = 0; /* used for tracking options */ poptContext optCon; /* context for parsing command-line options */ struct poptOption optionsTable[] = { - { "statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL}, - { "version", 'V', POPT_ARG_NONE, NULL, 0, NULL}, { "verbose", 'v', POPT_ARG_NONE, NULL, 'v', NULL }, - { "daemon", 'd', POPT_ARG_NONE, &config.daemonise, 0, NULL }, { "disconnectFromOutput", 'D', POPT_ARG_NONE, NULL, 0, NULL }, { "reconnectToOutput", 'R', POPT_ARG_NONE, NULL, 0, NULL }, { "kill", 'k', POPT_ARG_NONE, NULL, 0, NULL }, +#ifdef COMMAND_LINE_ARGUMENT_SUPPORT + { "daemon", 'd', POPT_ARG_NONE, &config.daemonise, 0, NULL }, + { "statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL}, + { "version", 'V', POPT_ARG_NONE, NULL, 0, NULL}, { "port", 'p', POPT_ARG_INT, &config.port, 0, NULL } , { "name", 'a', POPT_ARG_STRING, &config.apname, 0, NULL } , { "output", 'o', POPT_ARG_STRING, &config.output_name, 0, NULL } , @@ -229,35 +229,50 @@ int parse_options(int argc, char **argv) { { "forkedDaapdLatency", 0, POPT_ARG_INT, &config.ForkedDaapdLatency, 0, NULL } , { "stuffing", 'S', POPT_ARG_STRING, &stuffing, 'S', NULL } , { "resync", 'r', POPT_ARG_INT, &config.resyncthreshold, 0, NULL } , - { "timeout", 't', POPT_ARG_INT, &config.timeout, 0, NULL } , + { "timeout", 't', POPT_ARG_INT, &config.timeout, 't', NULL } , { "password", 0, POPT_ARG_STRING, &config.password, 0, NULL } , { "tolerance", 0, POPT_ARG_INT, &config.tolerance, 0, NULL } , #ifdef CONFIG_METADATA - { "meta-dir", 'M', POPT_ARG_STRING, &config.meta_dir, 0, NULL } , - { "get-coverart", 'g', POPT_ARG_NONE, &config.get_coverart, 0, NULL } , + { "meta-dir", 'M', POPT_ARG_STRING, &config.metadata_pipename, 'M', NULL } , + { "get-coverart", 'g', POPT_ARG_NONE, &config.get_coverart, 'g', NULL } , #endif POPT_AUTOHELP +#endif { NULL, 0, 0, NULL, 0 } }; + int optind=argc; int j; for (j=0;j= 0) { switch (c) { case 'v': debuglev++; break; + case 't': + if (config.timeout==0) { + config.dont_check_timeout=1; + config.allow_session_interruption=1; + } else { + config.dont_check_timeout=0; + config.allow_session_interruption=0; + } + break; #ifdef CONFIG_METADATA + case 'M': + config.metadata_enabled=1; + break; case 'g': - if (config.meta_dir==0) + if (config.metadata_enabled==0) die("If you want to get cover art, you must also select the --meta-dir option."); break; #endif @@ -278,7 +293,6 @@ int parse_options(int argc, char **argv) { if (c < -1) { die("%s: %s",poptBadOption(optCon, POPT_BADOPTION_NOALIAS),poptStrerror(c)); } -#endif #ifdef SUPPORT_CONFIG_FILES @@ -286,24 +300,18 @@ int parse_options(int argc, char **argv) { strcpy(configuration_file_path,"/etc/"); strcat(configuration_file_path,appName); strcat(configuration_file_path,".conf"); - debug(1,"Looking for file \"%s\"",configuration_file_path); - config_t cfg; + debug(2,"Looking for file \"%s\"",configuration_file_path); config_setting_t *setting; const char *str; int value; config_init(&config.cfg); - debug(1,"config stuff initialised"); /* Read the file. If there is an error, report it and exit. */ if(config_read_file(&config.cfg,configuration_file_path)) { - debug(1,"Configuration file opened."); - - debug(1,"name"); /* Get the Service Name. */ if(config_lookup_string(&config.cfg, "general.name", &str)) - config.apname=str; + config.apname=(char *)str; - debug(1,"daemonize"); /* Get the Daemonize setting. */ if(config_lookup_string(&config.cfg, "general.daemonize", &str)) { if (strcasecmp(str,"no")==0) @@ -311,27 +319,144 @@ int parse_options(int argc, char **argv) { else if (strcasecmp(str,"yes")==0) config.daemonise=1; else - die("Invalid daemonize option choice \"%\". It should be \"yes\" or \"no\""); + die("Invalid daemonize option choice \"%s\". It should be \"yes\" or \"no\""); } - debug(1,"mdns_backend"); /* Get the mdns_backend setting. */ if(config_lookup_string(&config.cfg, "general.mdns_backend", &str)) - config.mdns_name=str; + config.mdns_name=(char *)str; - debug(1,"output_backend"); /* Get the output_backend setting. */ if(config_lookup_string(&config.cfg, "general.output_backend", &str)) - config.output_name=str; + config.output_name=(char *)str; - debug(1,"port"); - /* Get the port setting. */ + /* Get the port setting. */ if(config_lookup_int(&config.cfg, "general.port", &value)) config.port=value; - debug(1,"done"); + + /* 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,"soxr")==0) + config.packet_stuffing=ST_soxr; + else + die("Invalid interpolation option choice \"%s\". It should be \"basic\" or \"soxr\""); + } + + /* Get the statistics setting. */ + if(config_lookup_string(&config.cfg, "general.statistics", &str)) { + if (strcasecmp(str,"no")==0) + config.statistics_requested=0; + else if (strcasecmp(str,"yes")==0) + config.statistics_requested=1; + else + die("Invalid statistics option choice \"%s\". It should be \"yes\" or \"no\""); + } + + /* Get the drift tolerance setting. */ + if(config_lookup_int(&config.cfg, "general.drift", &value)) + config.tolerance=value; + + /* Get the resync setting. */ + if(config_lookup_int(&config.cfg, "general.resync_threshold", &value)) + config.resyncthreshold=value; + + /* Get the verbosity setting. */ + if(config_lookup_int(&config.cfg, "general.log_verbosity", &value)) + 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 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\""); + } + + + + /* Get the default latency. */ + if(config_lookup_int(&config.cfg, "latencies.default", &value)) + config.latency=value; + + /* Get the itunes latency. */ + if(config_lookup_int(&config.cfg, "latencies.itunes", &value)) + config.iTunesLatency=value; + + /* Get the AirPlay latency. */ + if(config_lookup_int(&config.cfg, "latencies.airplay", &value)) + config.AirPlayLatency=value; + + /* Get the forkedDaapd latency. */ + if(config_lookup_int(&config.cfg, "latencies.forkedDaapd", &value)) + config.ForkedDaapdLatency=value; + + /* Get the metadata setting. */ + if(config_lookup_string(&config.cfg, "metadata.enabled", &str)) { + if (strcasecmp(str,"no")==0) + config.metadata_enabled=0; + else if (strcasecmp(str,"yes")==0) + config.metadata_enabled=1; + else + die("Invalid metadata enabled option choice \"%s\". It should be \"yes\" or \"no\""); + } + + if(config_lookup_string(&config.cfg, "metadata.include_cover_art", &str)) { + if (strcasecmp(str,"no")==0) + config.get_coverart=0; + else if (strcasecmp(str,"yes")==0) + config.get_coverart=1; + else + die("Invalid metadata include_cover_art option choice \"%s\". It should be \"yes\" or \"no\""); + } + + if(config_lookup_string(&config.cfg, "metadata.pipe_name", &str)) { + config.metadata_pipename=(char *)str; + } + + if(config_lookup_string(&config.cfg, "sessioncontrol.run_this_before_play_begins", &str)) { + config.cmd_start=(char *)str; + } + + if(config_lookup_string(&config.cfg, "sessioncontrol.run_this_after_play_ends", &str)) { + config.cmd_stop=(char *)str; + } + + if(config_lookup_string(&config.cfg, "sessioncontrol.wait_for_completion", &str)) { + if (strcasecmp(str,"no")==0) + config.cmd_blocking=0; + else if (strcasecmp(str,"yes")==0) + config.cmd_blocking=1; + else + die("Invalid session control wait_for_completion option choice \"%s\". It should be \"yes\" or \"no\""); + } + + if(config_lookup_string(&config.cfg, "sessioncontrol.allow_session_interruption", &str)) { + config.dont_check_timeout=0; // this is for legacy -- only set by -t 0 + if (strcasecmp(str,"no")==0) + config.allow_session_interruption=0; + else if (strcasecmp(str,"yes")==0) + config.allow_session_interruption=1; + else + die("Invalid session control allow_interruption option choice \"%s\". It should be \"yes\" or \"no\""); + } + + if(config_lookup_int(&config.cfg, "sessioncontrol.session_timeout", &value)) { + config.timeout=value; + config.dont_check_timeout=0; // this is for legacy -- only set by -t 0 + } } else { - debug(1,"Line %d of the configuration file \"%s\":\n%s", + die("Line %d of the configuration file \"%s\":\n%s", config_error_line(&config.cfg),config_error_file(&config.cfg),config_error_text(&config.cfg)); } @@ -348,17 +473,19 @@ int parse_options(int argc, char **argv) { debug(2,"on-stop action is \"%s\".",config.cmd_stop); debug(2,"wait-cmd status is %d.",config.cmd_blocking); debug(2,"mdns backend \"%s\".",config.mdns_name); - debug(2,"latency is %d.",config.userSuppliedLatency); + debug(2,"userSuppliedLatency is %d.",config.userSuppliedLatency); debug(2,"AirPlayLatency is %d.",config.AirPlayLatency); debug(2,"iTunesLatency is %d.",config.iTunesLatency); debug(2,"forkedDaapdLatency is %d.",config.ForkedDaapdLatency); - debug(2,"stuffing option is \"%s\".",stuffing); + debug(2,"stuffing option is \"%d\".",config.packet_stuffing); debug(2,"resync time is %d.",config.resyncthreshold); + debug(2,"allow a session to be interrupted: %d.",config.allow_session_interruption); debug(2,"busy timeout time is %d.",config.timeout); debug(2,"tolerance is %d frames.",config.tolerance); debug(2,"password is \"%s\".",config.password); #ifdef CONFIG_METADATA - debug(2,"metadata directory is \"%s\".",config.meta_dir); + debug(2,"metdata enabled is %d.",config.metadata_enabled); + debug(2,"metadata pipename is \"%s\".",config.metadata_pipename); debug(2,"get-coverart is %d.",config.get_coverart); #endif @@ -426,7 +553,6 @@ const char *pid_file_proc(void) { #endif void exit_function() { - debug(1,"atexit function called..."); #ifdef SUPPORT_CONFIG_FILES config_destroy(&config.cfg); #endif @@ -612,7 +738,7 @@ int main(int argc, char **argv) { audio_ls_outputs(); die("Invalid audio output specified!"); } - config.output->init(argc-audio_arg, argv+audio_arg); + config.output->init(argc-audio_arg, argv+audio_arg, &config.cfg); daemon_log(LOG_NOTICE, "startup"); From e3f75d85ceb5880b5ed605074dbb96490437c2b2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 4 May 2015 17:26:18 +0100 Subject: [PATCH 038/220] respond better to timeouts and print stats message when frames are totally missing. --- player.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/player.c b/player.c index 5be04439..21bec3bd 100644 --- a/player.c +++ b/player.c @@ -411,10 +411,11 @@ static abuf_t *buffer_get_frame(void) { // if config.timeout (default 120) seconds have elapsed since the last audio packet was received, then we should stop. // config.timeout of zero means don't check..., but iTunes may be confused by a long gap followed by a resumption... - if ((time_of_last_audio_packet!=0) && (shutdown_requested==0) && (config.timeout!=0)) { + + if ((time_of_last_audio_packet!=0) && (shutdown_requested==0) && (config.dont_check_timeout==0)) { uint64_t ct = config.timeout; // go from int to 64-bit int - if (local_time_now-time_of_last_audio_packet>=ct<<32) { - debug(1,"As Yeats almost said, \"Too long a silence / can make a stone of the heart\""); + if ((local_time_now>time_of_last_audio_packet) && (local_time_now-time_of_last_audio_packet>=ct<<32)) { + debug(1,"As Yeats almost said, \"Too long a silence / can make a stone of the heart\""); rtsp_request_shutdown_stream(); shutdown_requested=1; } @@ -779,11 +780,13 @@ static void *player_thread_func(void *arg) { #define trend_interval 3758 stats_t statistics[trend_interval]; int number_of_statistics,oldest_statistic,newest_statistic; + int at_least_one_frame_seen = 0; int64_t tsum_of_sync_errors,tsum_of_corrections,tsum_of_insertions_and_deletions,tsum_of_drifts; int64_t previous_sync_error,previous_correction; int64_t minimum_dac_queue_size = 1000000; int32_t minimum_buffer_occupancy = BUFFER_FRAMES; int32_t maximum_buffer_occupancy = 0; + audio_information.valid=0; int play_samples; @@ -826,7 +829,9 @@ static void *player_thread_func(void *arg) { // If it's ahead of time, we add one audio frame to this frame to delay a subsequent frame // If it's late, we remove an audio frame from this frame to bring a subsequent frame forward in time - uint32_t reference_timestamp; + at_least_one_frame_seen = 1; + + uint32_t reference_timestamp; uint64_t reference_timestamp_time; get_reference_timestamp_stuff(&reference_timestamp,&reference_timestamp_time); @@ -1012,10 +1017,14 @@ static void *player_thread_func(void *arg) { double moving_average_drift = (1.0*tsum_of_drifts)/number_of_statistics; // if ((play_number/print_interval)%20==0) if (config.statistics_requested) - inform("Sync error: %.1f (frames); net correction: %.1f (ppm); corrections: %.1f (ppm); missing packets %llu; late packets %llu; too late packets %llu; resend requests %llu; min DAC queue size %lli, min and max buffer occupancy %u and %u.", moving_average_sync_error, moving_average_correction*1000000/352, moving_average_insertions_plus_deletions*1000000/352,missing_packets,late_packets,too_late_packets,resend_requests,minimum_dac_queue_size,minimum_buffer_occupancy,maximum_buffer_occupancy); + if (at_least_one_frame_seen) + inform("Sync error: %.1f (frames); net correction: %.1f (ppm); corrections: %.1f (ppm); missing packets %llu; late packets %llu; too late packets %llu; resend requests %llu; min DAC queue size %lli, min and max buffer occupancy %u and %u.", moving_average_sync_error, moving_average_correction*1000000/352, moving_average_insertions_plus_deletions*1000000/352,missing_packets,late_packets,too_late_packets,resend_requests,minimum_dac_queue_size,minimum_buffer_occupancy,maximum_buffer_occupancy); + else + inform("No frames received in the last sampling interval."); minimum_dac_queue_size=1000000; // hack reset maximum_buffer_occupancy = 0; // can't be less than this minimum_buffer_occupancy = BUFFER_FRAMES; // can't be more than this + at_least_one_frame_seen = 0; } } } From 9c94547b104ad1da05deaa52fa4007fd2259e912 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 4 May 2015 17:27:29 +0100 Subject: [PATCH 039/220] Update to reflect move to configuration files and extra features. --- README.md | 26 +++++++++++++++----------- RELEASENOTES.md | 24 ++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index c005669d..443fa204 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,11 @@ This branch -- "development" -- is the development branch of Shairport Sync and More Information ---------- -Shairport Sync works by using timing information present in the audio data stream to keep in step with the source. It does this by monitoring and controlling the "latency" -- the time between when a sound is time-stamped at the source and when it is played by the audio output device. To measure latency precisely, it keeps its own clock synchronised with the clock used by the source, usually to within a fraction of a millisecond, using a variant of NTP synchronisation protocols. +Shairport Sync works by using timing information present in the audio data stream to keep in synchrony with the source. It does this by monitoring and controlling the "latency" -- the time between when a sound is time-stamped at the source and when it is played by the audio output device. To measure latency precisely, it keeps its own clock synchronised with the clock used by the source, usually to within a fraction of a millisecond, using a variant of NTP synchronisation protocols. To maintain the exact latency required, if an output device is running slow relative to the source, Shairport Sync will delete frames of audio to allow the device to keep up; if the device is running fast, Shairport Sync will insert frames to keep time. The number of frames inserted or deleted is so small as to be almost inaudible. Frames are inserted or deleted as necessary at pseudorandom intervals. Alternatively, with `libsoxr` support, Shairport Sync can resample the audio feed to ensure the output device can keep up. This is even less obtrusive than insertion and deletion but requires a good deal of processing power -- most embedded devices probably can't support it. -There are three default latency settings, chosen automatically. One latency matches the latency used by recent versions of iTunes when playing audio and the other matches the latency used by older version of iTunes, by iOS devices and by iTunes and Quicktime Player when playing video. A third default latency is used when the audio source is `forked-daapd`. +There are four default latency settings, chosen automatically. One latency matches the latency used by recent versions of iTunes when playing audio and the other matches the latency used by older version of iTunes, by iOS devices and by iTunes and Quicktime Player when playing video. A third default latency is used when the audio source is `forked-daapd`. The fourth latency is the default if no other latency is chosen. Shairport Sync is a pretty substantial rewrite of Shairport 1.0 by James Laird and others -- please see https://github.com/abrasive/shairport/blob/master/README.md#contributors-to-version-1x for a list of the contributors to Shairport 1.x and Shairport 0.x. From a "heritage" point of view, Shairport Sync is a fork of Shairport 1.0 and the active branch is Shairport Sync 2.2. @@ -23,10 +23,11 @@ For more about the motivation behind Shairport Sync, please see the wiki at http What else? -------------- -* Shairport Sync offers finer control at very top and very bottom of the volume range. See http://tangentsoft.net/audio/atten.html for a good discussion of audio "attenuators", upon which volume control in Shairport Sync is modelled. See also the diagram of the volume transfer function in the documents folder. -* Shairport Sync will mute properly if the hardware supports it. +* Better Volume Control -- Shairport Sync offers finer control at very top and very bottom of the volume range. See http://tangentsoft.net/audio/atten.html for a good discussion of audio "attenuators", upon which volume control in Shairport Sync is modelled. See also the diagram of the volume transfer function in the documents folder. +* Hardware Mute -- Shairport Sync will mute properly if the hardware supports it. * If Shairport Sync has to use software volume and mute controls, the response time is shorter than before -- now it responds in 0.15 seconds. -* Shairport Sync sends back a "busy" signal if it's already playing audio from another source, so other sources can't disrupt an existing Shairport Sync session. (If a source disappears without warning, the session automatically terminates after two minutes and the device becomes available again.) +* Non-Interruptible -- Shairport Sync sends back a "busy" signal if it's already playing audio from another source, so other sources can't disrupt an existing Shairport Sync session. (If a source disappears without warning, the session automatically terminates after two minutes and the device becomes available again.) +* Metadata -- Shairport Sync can be configured to receive metadata, such as Album Name, Artist Name, Cover Art, etc. and deliver it through a pipe to a recipient application program -- see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Status ------ @@ -60,6 +61,7 @@ The following libraries are required: * autoconf * libtool * libpopt +* libconfig Optional: * libsoxr @@ -68,7 +70,7 @@ Many linux distributions have Avahi and OpenSSL already in place, so normally it Assuming the usual build essentials and git, Debian, Ubuntu and Raspian users can get the basics with: -- `apt-get install autoconf libtool libdaemon-dev libasound2-dev libpopt-dev` +- `apt-get install autoconf libtool libdaemon-dev libasound2-dev libpopt-dev libconfig-dev` - `apt-get install avahi-daemon libavahi-client-dev` if you want to use Avahi (recommended). - `apt-get install libssl-dev` if you want to use OpenSSL and libcrypto, or use PolarSSL otherwise. - `apt-get install libpolarssl-dev` if you want to use PolarSSL, or use OpenSSL/libcrypto otherwise. @@ -99,22 +101,24 @@ Omit the `--with-soxr` if the libsoxr library is not available. `$ make` -Run `$sudo make install` to install `shairport-sync` along with an init script which will automatically launch it at startup. The settings in the init script are the most basic defaults, so you will want to edit it -- the file is `/etc/init.d/shairport-sync` -- to give the service a name, use a different card, use the hardware mixer and volume control, etc. -- there are some examples in the script file. +Run `$sudo make install` to install `shairport-sync` along with a default configuration file and startup script to launch it automatically at system startup. The settings are the most basic defaults, so you will want to edit the configuration -- the file is `/etc/shairport-sync.conf` -- to give the service a name, use a different card, use the hardware mixer and volume control, etc. -- there are some examples in the sample configuration file. Man Page -------- -You can see a web version of the man page here: http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/2.3/man/shairport-sync.html +You can view the man page here: http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/2.3/man/shairport-sync.html Configuring Shairport Sync -------- -Shairport Sync installs a default configuration at `/etc/init.d/shairport-sync` (it won't replace an existing one) which should work in almost any system with a sound card. If there is a problem, it will be noted in the logfile, normally `/etc/log/syslog`. However, to get the most out of your software and hardware, you need to adjust some of the settings. +There are two logically distinct parts to getting Shairport Sync to run properly on your machine -- the first part is getting it to start and stop automatically, and this is taken care of using a startup script at `/etc/init.d/shairport-sync`. The second part is giving Shairport Sync the correct settings, e.g. the correct output device to use, the service name that will appear in iTunes, etc. and this is done using the configuration file `/etc/shairport-sync.conf`. -To understand what follows, note that settings and parameters are passed to Shairport Sync through command line arguments. The purpose of the init script at `/etc/init.d/shairport-sync` is to launch or terminate Shairport Sync while passing the correct arguments to it. You are perfectly free to remove the init script and launch and terminate Shairport Sync yourself directly; indeed it is useful when you are troubleshooting the program. If you do launch it directly, make sure it isn't running already! +Shairport Sync reads its configuration from a configuration file at `/etc/shairport-sync.conf`. (While it can also take configuration settings from command line options, it is recommended that you use the configuration file method.) +When you run `$sudo make install`, a default configuration is installed at `/etc/shairport-sync.conf` (it won't replace an existing one) which should work in almost any system with a sound card. A sample configuration file is also installed or updated at `/etc/shairport-sync.conf.sample`. If there is a problem, it will be noted in the logfile, normally `/etc/log/syslog`. However, to get the most out of your software and hardware, you need to adjust some of the settings. + +To understand what follows, note that settings and parameters are given to Shairport Sync via the file `/etc/shairport-sync.conf`. The purpose of the init script at `/etc/init.d/shairport-sync` is to launch and terminate Shairport Sync. You are perfectly free to remove the init script and launch and terminate Shairport Sync yourself directly; indeed it is useful when you are troubleshooting the program. If you do launch it directly, make sure it isn't running already! As well as the man page, don't forget you can launch Shairport Sync with the `-h` option to get some help on the options available. - These are the important options: The `-a` option allows you to specify the service name Shairport Sync will use on the network. If you don't specify a service name, the name `Shairport Sync on ...your computer's hostname...` will be used. diff --git a/RELEASENOTES.md b/RELEASENOTES.md index aff094c2..1f87240f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,9 +1,29 @@ +Version 2.3.1 -- forthcoming +----- +In this release, Shairport Sync gains the ability to read settings from `/etc/shairport-sync.conf`. +The move away from command-line options is to give more flexibility in adding features and to give better compatability across different versions of Linux. +Existing command-line options will continue to work, but some will be deprecated and may disappear in a future version of Shairport Sync. New settings will only be available via the configuration file. + +In moving to the the use of a configuration file, some "housekeeping" is being done -- some logical corrections and other small changes are being made to option names and modes of operations, so the settings in the configuration file do not exactly match command line options. + +When `make install` is executed, a sample configuration is installed or updated at `/etc/shairport-sync.conf.sample`. The same file is also installed as `/etc/shairport-sync.conf` if that file doesn't already exist. + +* Pesky Change You Must Do Something About +If you enable metadata, please note that the option has changed somewhat. The option `-M` has a new long name equivalent: `--metadata-pipename` and the argument you provide must now be the full name of the metadata pipe, e.g. `-M /tmp/shairport-sync-metadata`. + +* Enhancements + * Shairport Sync now reads the configuration file `/etc/shairport-sync.conf`. This has settings for most command-line options and it's where any new settings will go. A default configuration file will be installed if one doesn't exist, and a sample file configuration file is always installed or updated. Details of settings are provided in the sample file. Shairport Sync relies on the `libconfig` library to read configuration files. For the present, you can disable the new feature (and save the space taken up by `libconfig`) by using the configure option `--without-config-file-support`. + * Session Timeout and Allow Session Interruption can now be set independently. This is really some "housekeeping" as referred to above -- it's a kind of a bug fix, where the bug in question is an inappropriate connection of the setting of two parameters. To explain: (1) By default, when a source such as iTunes starts playing to the Shairport Sync device, any other source attempting to start a play session receives a "busy" signal. If a source disappears without warning, Shairport Sync will wait for 120 seconds before dropping the session and allowing another source to start a play session. (2) The command-line option `-t` or `--timeout` allows you to set the wait time before dropping the session. If you set this parameter to `0`, Shairport Sync will not send a "busy" signal, thus allowing another source to interrupt an existing one. (3) The problem is that if you set the parameter to `0`, a session will never be dropped if the source disappears without warning. + The (obvious) fix for this is to separate the setting of the two parameters, and this is now done in the configuration file `/etc/shairport-sync.conf` -- please see the settings `allow_session_interruption` and `session_timeout`. The behaviour of the `-t` and `--timeout` command-line options is unchanged but deprecated. + * New Option -- "Ignore Volume Control". If you set this to "yes", the output from Shairport Sync is always set at 100%. This is useful when you want to set the volume locally. Available via the settings file only. + * Statistics option correctly reports when no frames are received in a sampling interval. + Version 2.3 ----- -* Enhancement +* Enhancements * Adding the System V startup script (the "initscript") is now a configuration option. The default is to include it, so if you want to omit the installation of the initscript, add the configuration option `--without-initscript`. * Metadata support is now a compile-time option: `--with-metadata`. - * A very experimental metadata feed has been added. Use the option `-M `, e.g. `-M /tmp`. Shairport Sync will provide metadata in a pipe called `/shairport-sync-metadata`. There's a sample metadata reader at https://github.com/mikebrady/shairport-sync-metadata-reader. The format of the metadata is a mixture of XML-style tags, 4-character codes and base64 data. Please look at `rtsp.c` and `player.c` for examples. Please note that the format of the metadata may change. + * A metadata feed has been added. Use the option `-M `, e.g. `-M /tmp`. Shairport Sync will provide metadata in a pipe called `/shairport-sync-metadata`. (This is changed in 2.3.1.) There's a sample metadata reader at https://github.com/mikebrady/shairport-sync-metadata-reader. The format of the metadata is a mixture of XML-style tags, 4-character codes and base64 data. Please look at `rtsp.c` and `player.c` for examples. Please note that the format of the metadata may change. Beware: there appears to be a serious bug in iTunes, such that it may stall for a long period when sending large (more than a few hundred kilobytes) coverart images. * Bugfix From 282f79c95a8f79563a412fbbf68fbcf0d6a1f37c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 4 May 2015 17:45:30 +0100 Subject: [PATCH 040/220] Add the option to selectively not use pkg-config during configuration - useful for OpenWrt --- README.md | 2 +- configure.ac | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 443fa204..867be255 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ There are two logically distinct parts to getting Shairport Sync to run properly Shairport Sync reads its configuration from a configuration file at `/etc/shairport-sync.conf`. (While it can also take configuration settings from command line options, it is recommended that you use the configuration file method.) When you run `$sudo make install`, a default configuration is installed at `/etc/shairport-sync.conf` (it won't replace an existing one) which should work in almost any system with a sound card. A sample configuration file is also installed or updated at `/etc/shairport-sync.conf.sample`. If there is a problem, it will be noted in the logfile, normally `/etc/log/syslog`. However, to get the most out of your software and hardware, you need to adjust some of the settings. -To understand what follows, note that settings and parameters are given to Shairport Sync via the file `/etc/shairport-sync.conf`. The purpose of the init script at `/etc/init.d/shairport-sync` is to launch and terminate Shairport Sync. You are perfectly free to remove the init script and launch and terminate Shairport Sync yourself directly; indeed it is useful when you are troubleshooting the program. If you do launch it directly, make sure it isn't running already! +To understand what follows, note that settings and parameters are given to Shairport Sync via the file `/etc/shairport-sync.conf`. The purpose of the init script at `/etc/init.d/shairport-sync` is merely to launch and terminate Shairport Sync. You are perfectly free to remove the init script and launch and terminate Shairport Sync yourself directly; indeed it is useful when you are troubleshooting the program. If you do launch it directly, make sure it isn't running already! As well as the man page, don't forget you can launch Shairport Sync with the `-h` option to get some help on the options available. diff --git a/configure.ac b/configure.ac index 3f84d058..2e552672 100644 --- a/configure.ac +++ b/configure.ac @@ -28,15 +28,25 @@ if test "x${with_os_type}" = xlinux; then AC_CHECK_LIB([rt],[clock_gettime], , AC_MSG_ERROR(librt needed)) fi +##### Some build systems are not fully using pkg-config, so we can use the flag ${with_pkg_config} on a case-by-case basis +##### to control how to deal with them + +AC_ARG_WITH([pkg_config], +[ --with-pkg-config = use pkg-config to find libraries], ,[with_pkg_config=1]) + AC_CHECK_LIB([daemon],[daemon_log], , AC_MSG_ERROR(libdaemon needed)) AC_CHECK_LIB([pthread],[pthread_create], , AC_MSG_ERROR(pthread library needed)) AC_CHECK_LIB([m],[exp], , AC_MSG_ERROR(maths library needed)) AC_MSG_RESULT(>>Including libpopt) -PKG_CHECK_MODULES( - [POPT], [popt], - [LIBS="${POPT_LIBS} ${LIBS}" - AC_DEFINE([HAVE_LIBPOPT],[1],[Define to 1 if you have popt])]) +if test "x${with_pkg_config}" = x1 ; then + PKG_CHECK_MODULES( + [POPT], [popt], + [LIBS="${POPT_LIBS} ${LIBS}" + AC_DEFINE([HAVE_LIBPOPT],[1],[Define to 1 if you have popt])]) +else + AC_CHECK_LIB([popt],[poptGetContext], , AC_MSG_ERROR(libpopt needed)) +fi AC_ARG_WITH([initscript], [ --with-initscript = include a startup script], ,[with_initscript=1]) From 9de5ee8197f09f735e43a885d294a92f9057fc2c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 4 May 2015 23:14:17 +0100 Subject: [PATCH 041/220] Adjust some of the settings and comments --- scripts/shairport-sync | 2 +- scripts/shairport-sync.conf | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/shairport-sync b/scripts/shairport-sync index d1175504..8288d6e7 100755 --- a/scripts/shairport-sync +++ b/scripts/shairport-sync @@ -63,7 +63,7 @@ do_start() # BTW, if you're using software volume control, you may have to use alsamixer or similar to set the output device's volume to its maximum level first # BTW2, you can use alsamixer to find device identifiers (e.g. hw:1) and mixer names (e.g. "Speaker"). No need to change ALSA's defaults. # BTW3, the argument after -a is simply the name the shairport service will be visible as. - start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d || return 2 + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- || return 2 # start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d -a "Topping TP30 or Griffin iMic on Raspberry Pi" -- -d hw:1 -t hardware -c "PCM" || return 2 # start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d -a "'3D Sound' USB Soundcard on Raspberry Pi" -- -d hw:1 -t hardware -c "Speaker" || return 2 # BTW, that "3D Sound" USB soundcard sometimes has the mixer name "Headphone" rather than "Speaker" -- use alsamixer to check. diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index c49f141b..03c44d54 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -1,10 +1,11 @@ // Sample Configuration File for Shairport Sync +// Commented out settings are generally the defaults. // General Settings general = { // name = "Shairport Sync Player"; // This is the name the service will advertise to iTunes. The default is "Shairport Sync on " -// daemonize = "no"; + daemonize = "yes"; // default is "no" // mdns_backend = "avahi"; // not used, not tested // output_backend = "alsa"; // setting is not used, alsa is default // port = 5000; @@ -14,7 +15,7 @@ general = // drift = 88; // allow this number of frames of drift before correcting it // resync_threshold = 2205; // a synchronisation error greater than this will cause resynchronisation; 0 disables it // log_verbosity = 0; // "0" means no verbosity, "3" is highest. -// ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the volume control is set to. +// ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. }; latencies = From b0fd1ae23e6fa739a8e5687cb4015d1f7d5e2586 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 5 May 2015 11:49:00 +0100 Subject: [PATCH 042/220] Add note about -c option --- RELEASENOTES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1f87240f..720d2c7b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -12,7 +12,8 @@ When `make install` is executed, a sample configuration is installed or updated If you enable metadata, please note that the option has changed somewhat. The option `-M` has a new long name equivalent: `--metadata-pipename` and the argument you provide must now be the full name of the metadata pipe, e.g. `-M /tmp/shairport-sync-metadata`. * Enhancements - * Shairport Sync now reads the configuration file `/etc/shairport-sync.conf`. This has settings for most command-line options and it's where any new settings will go. A default configuration file will be installed if one doesn't exist, and a sample file configuration file is always installed or updated. Details of settings are provided in the sample file. Shairport Sync relies on the `libconfig` library to read configuration files. For the present, you can disable the new feature (and save the space taken up by `libconfig`) by using the configure option `--without-config-file-support`. + * Shairport Sync now reads settings from the configuration file `/etc/shairport-sync.conf`. This has settings for most command-line options and it's where any new settings will go. A default configuration file will be installed if one doesn't exist, and a sample file configuration file is always installed or updated. Details of settings are provided in the sample file. Shairport Sync relies on the `libconfig` library to read configuration files. For the present, you can disable the new feature (and save the space taken up by `libconfig`) by using the configure option `--without-config-file-support`. + * New command-line option `-c ` or `--configfile=` allows you to specify a configuration file other than `/etc/shairport-sync.conf`. * Session Timeout and Allow Session Interruption can now be set independently. This is really some "housekeeping" as referred to above -- it's a kind of a bug fix, where the bug in question is an inappropriate connection of the setting of two parameters. To explain: (1) By default, when a source such as iTunes starts playing to the Shairport Sync device, any other source attempting to start a play session receives a "busy" signal. If a source disappears without warning, Shairport Sync will wait for 120 seconds before dropping the session and allowing another source to start a play session. (2) The command-line option `-t` or `--timeout` allows you to set the wait time before dropping the session. If you set this parameter to `0`, Shairport Sync will not send a "busy" signal, thus allowing another source to interrupt an existing one. (3) The problem is that if you set the parameter to `0`, a session will never be dropped if the source disappears without warning. The (obvious) fix for this is to separate the setting of the two parameters, and this is now done in the configuration file `/etc/shairport-sync.conf` -- please see the settings `allow_session_interruption` and `session_timeout`. The behaviour of the `-t` and `--timeout` command-line options is unchanged but deprecated. * New Option -- "Ignore Volume Control". If you set this to "yes", the output from Shairport Sync is always set at 100%. This is useful when you want to set the volume locally. Available via the settings file only. From fb0eab7c14337477f79a8e17625f1a0d323f75cd Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 5 May 2015 11:49:58 +0100 Subject: [PATCH 043/220] Add support for choosing a configuration file --- shairport.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/shairport.c b/shairport.c index 70d920ca..322dcf03 100644 --- a/shairport.c +++ b/shairport.c @@ -156,6 +156,7 @@ void usage(char *progname) { printf(" -k, --kill kill the existing shairport daemon.\n"); printf(" -D, --disconnectFromOutput disconnect immediately from the output device.\n"); printf(" -R, --reconnectToOutput reconnect to the output device.\n"); + printf(" -c, --configfile=FILE read configuration settings from FILE. Default is /etc/shairport-sync.conf.\n"); #ifdef COMMAND_LINE_ARGUMENT_SUPPORT printf(" -v, --verbose -v print debug information; -vv more; -vvv lots\n"); @@ -188,8 +189,8 @@ void usage(char *progname) { printf(" --tolerance=TOLERANCE allow a synchronization error of TOLERANCE frames (default 88) before trying to correct it.\n"); printf(" --password=PASSWORD require PASSWORD to connect. Default is not to require a password.\n"); #ifdef CONFIG_METADATA - printf(" --meta-dir=DIR get metadata from the source and pipe it to DIR/shairport-sync-metadata, e.g. --meta-dir=/tmp.\n"); - printf(" --get-coverart get cover art from the source and pipe it to DIR/shairport-sync-metadata, e.g. --meta-dir=/tmp.\n"); + printf(" --metadata-pipename=FILE get metadata from the source and pipe it to FILE, e.g. --meta-dir=/tmp/shairport-sync-metadata.\n"); + printf(" --get-coverart send cover art through the metadata pipe.\n"); #endif #endif #ifdef SUPPORT_CONFIG_FILES @@ -203,6 +204,9 @@ void usage(char *progname) { int parse_options(int argc, char **argv) { char *stuffing = NULL; /* used for picking up the stuffing option */ + + + signed char c; /* used for argument parsing */ int i = 0; /* used for tracking options */ @@ -212,6 +216,9 @@ int parse_options(int argc, char **argv) { { "disconnectFromOutput", 'D', POPT_ARG_NONE, NULL, 0, NULL }, { "reconnectToOutput", 'R', POPT_ARG_NONE, NULL, 0, NULL }, { "kill", 'k', POPT_ARG_NONE, NULL, 0, NULL }, +#ifdef SUPPORT_CONFIG_FILES + { "configfile", 'c', POPT_ARG_STRING, &config.configfile, 0, NULL }, +#endif #ifdef COMMAND_LINE_ARGUMENT_SUPPORT { "daemon", 'd', POPT_ARG_NONE, &config.daemonise, 0, NULL }, { "statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL}, @@ -233,7 +240,7 @@ int parse_options(int argc, char **argv) { { "password", 0, POPT_ARG_STRING, &config.password, 0, NULL } , { "tolerance", 0, POPT_ARG_INT, &config.tolerance, 0, NULL } , #ifdef CONFIG_METADATA - { "meta-dir", 'M', POPT_ARG_STRING, &config.metadata_pipename, 'M', NULL } , + { "metadata-pipename", 'M', POPT_ARG_STRING, &config.metadata_pipename, 'M', NULL } , { "get-coverart", 'g', POPT_ARG_NONE, &config.get_coverart, 'g', NULL } , #endif POPT_AUTOHELP @@ -273,7 +280,7 @@ int parse_options(int argc, char **argv) { break; case 'g': if (config.metadata_enabled==0) - die("If you want to get cover art, you must also select the --meta-dir option."); + die("If you want to get cover art, you must also select the --metadata-pipename option."); break; #endif case 'S': @@ -294,7 +301,6 @@ int parse_options(int argc, char **argv) { die("%s: %s",poptBadOption(optCon, POPT_BADOPTION_NOALIAS),poptStrerror(c)); } - #ifdef SUPPORT_CONFIG_FILES char configuration_file_path[4096]; strcpy(configuration_file_path,"/etc/"); @@ -306,8 +312,12 @@ int parse_options(int argc, char **argv) { int value; config_init(&config.cfg); + char *cfp = configuration_file_path; + // use the configuration file path given in the -c option, if any + if (config.configfile) + cfp = config.configfile; /* Read the file. If there is an error, report it and exit. */ - if(config_read_file(&config.cfg,configuration_file_path)) { + if(config_read_file(&config.cfg,cfp)) { /* Get the Service Name. */ if(config_lookup_string(&config.cfg, "general.name", &str)) config.apname=(char *)str; @@ -381,9 +391,7 @@ int parse_options(int argc, char **argv) { else die("Invalid ignore_volume_control option choice \"%s\". It should be \"yes\" or \"no\""); } - - - + /* Get the default latency. */ if(config_lookup_int(&config.cfg, "latencies.default", &value)) config.latency=value; From 8f100e6810298bbba910a7bb0660b842132552c3 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 5 May 2015 11:50:19 +0100 Subject: [PATCH 044/220] Add support for choosing a configuration file --- common.h | 1 + 1 file changed, 1 insertion(+) diff --git a/common.h b/common.h index f04a36e8..f5f145ed 100644 --- a/common.h +++ b/common.h @@ -75,6 +75,7 @@ typedef struct { char *pidfile; char *logfile; char *errfile; + char *configfile; } shairport_cfg; //true if Shairport Sync is supposed to be sending output to the output device, false otherwise From f29792d9ba6ce6c13130084797bff8474b5e32ee Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 5 May 2015 11:51:00 +0100 Subject: [PATCH 045/220] Add support for omitting installation of configuration files --- configure.ac | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 2e552672..56dee0e2 100644 --- a/configure.ac +++ b/configure.ac @@ -63,7 +63,7 @@ fi # Check if we want to support reading from a configuration file chosen -- i.e. take parameters from /etc/shairport-sync.conf AC_ARG_WITH([configfile_support], -[ --with-configfile-support = read configuration from a configuration file], ,[with_configfile_support=1]) +[ --with-configfile-support = support reading of configuration from a configuration file], ,[with_configfile_support=1]) if test "x${with_configfile_support}" = x1 ; then AC_MSG_RESULT(>>Including configuration file support) @@ -72,7 +72,11 @@ if test "x${with_configfile_support}" = x1 ; then [LIBS="${LIBCONFIG_LIBS} ${LIBS}"]) AC_DEFINE([SUPPORT_CONFIG_FILES],[1],[Define to 1 if you want to support configuration files]) fi -AM_CONDITIONAL([INSTALL_CONFIG_FILES], [test "x${with_configfile_support}" = "x1"]) + +AC_ARG_WITH([configfiles], +[ --with-configfiles = include configuration files in installation ], ,[with_configfiles=1]) +AM_CONDITIONAL([INSTALL_CONFIG_FILES], [test "x$with_configfiles" = "x1" -a "x$with_configfile_support" = "x1" ]) + # Check to see if we should include the initscript From 312b72e5b77915870a9dfd0a943ed1879d4aba88 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 5 May 2015 12:52:14 +0100 Subject: [PATCH 046/220] Update RELEASENOTES.md --- RELEASENOTES.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 720d2c7b..84b25e47 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,14 +1,15 @@ Version 2.3.1 -- forthcoming ----- In this release, Shairport Sync gains the ability to read settings from `/etc/shairport-sync.conf`. -The move away from command-line options is to give more flexibility in adding features and to give better compatability across different versions of Linux. -Existing command-line options will continue to work, but some will be deprecated and may disappear in a future version of Shairport Sync. New settings will only be available via the configuration file. +This gives more flexibility in adding features and to give better compatability across different versions of Linux. +Existing command-line options continue to work, but some will be deprecated and may disappear in a future version of Shairport Sync. New settings will only be available via the configuration file. In moving to the the use of a configuration file, some "housekeeping" is being done -- some logical corrections and other small changes are being made to option names and modes of operations, so the settings in the configuration file do not exactly match command line options. When `make install` is executed, a sample configuration is installed or updated at `/etc/shairport-sync.conf.sample`. The same file is also installed as `/etc/shairport-sync.conf` if that file doesn't already exist. * Pesky Change You Must Do Something About + If you enable metadata, please note that the option has changed somewhat. The option `-M` has a new long name equivalent: `--metadata-pipename` and the argument you provide must now be the full name of the metadata pipe, e.g. `-M /tmp/shairport-sync-metadata`. * Enhancements From 62dc112a6dc1a5b9e0dec588d78276b6bc788b7e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 5 May 2015 16:49:31 +0000 Subject: [PATCH 047/220] Only read metadat settings if metadata support is included, duh. --- shairport.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shairport.c b/shairport.c index 322dcf03..45cf7b1f 100644 --- a/shairport.c +++ b/shairport.c @@ -408,6 +408,7 @@ int parse_options(int argc, char **argv) { if(config_lookup_int(&config.cfg, "latencies.forkedDaapd", &value)) config.ForkedDaapdLatency=value; +#ifdef CONFIG_METADATA /* Get the metadata setting. */ if(config_lookup_string(&config.cfg, "metadata.enabled", &str)) { if (strcasecmp(str,"no")==0) @@ -430,7 +431,8 @@ int parse_options(int argc, char **argv) { if(config_lookup_string(&config.cfg, "metadata.pipe_name", &str)) { config.metadata_pipename=(char *)str; } - +#endif + if(config_lookup_string(&config.cfg, "sessioncontrol.run_this_before_play_begins", &str)) { config.cmd_start=(char *)str; } From 460f8448247447e851acacdddaffedf101422187 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 6 May 2015 13:55:58 +0000 Subject: [PATCH 048/220] Add new option to set the desired DAC buffer length. Quieten a few debug messages. --- RELEASENOTES.md | 3 ++- common.h | 1 + player.c | 11 ++++------- rtsp.c | 2 +- scripts/shairport-sync.conf | 1 + shairport.c | 19 +++++++++++++++++-- 6 files changed, 26 insertions(+), 11 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 84b25e47..7e7d404a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -17,7 +17,8 @@ If you enable metadata, please note that the option has changed somewhat. The op * New command-line option `-c ` or `--configfile=` allows you to specify a configuration file other than `/etc/shairport-sync.conf`. * Session Timeout and Allow Session Interruption can now be set independently. This is really some "housekeeping" as referred to above -- it's a kind of a bug fix, where the bug in question is an inappropriate connection of the setting of two parameters. To explain: (1) By default, when a source such as iTunes starts playing to the Shairport Sync device, any other source attempting to start a play session receives a "busy" signal. If a source disappears without warning, Shairport Sync will wait for 120 seconds before dropping the session and allowing another source to start a play session. (2) The command-line option `-t` or `--timeout` allows you to set the wait time before dropping the session. If you set this parameter to `0`, Shairport Sync will not send a "busy" signal, thus allowing another source to interrupt an existing one. (3) The problem is that if you set the parameter to `0`, a session will never be dropped if the source disappears without warning. The (obvious) fix for this is to separate the setting of the two parameters, and this is now done in the configuration file `/etc/shairport-sync.conf` -- please see the settings `allow_session_interruption` and `session_timeout`. The behaviour of the `-t` and `--timeout` command-line options is unchanged but deprecated. - * New Option -- "Ignore Volume Control". If you set this to "yes", the output from Shairport Sync is always set at 100%. This is useful when you want to set the volume locally. Available via the settings file only. + * New Option -- "Ignore Volume Control" ('ignore_volume_control'). If you set this to "yes", the output from Shairport Sync is always set at 100%. This is useful when you want to set the volume locally. Available via the settings file only. + * New Option -- "Set DAC Buffer Desired Length" ('dac_buffer_desired_length'). Default is 6615 frames, or 0.15 seconds. On some slower machines, particularly with metadata processing going on, the DAC buffer can underflow on this setting,. so it might be worth making the buffer larger. A problem on software mixers only is that changes to volume control settings have to propagate through the buffer to be heard, so the larger the buffer, the longer the response time. This is only a problem if you're using a software mixer; hardware mixers are unaffected. Available via the settings file only. * Statistics option correctly reports when no frames are received in a sampling interval. Version 2.3 diff --git a/common.h b/common.h index f5f145ed..fdbe32b0 100644 --- a/common.h +++ b/common.h @@ -76,6 +76,7 @@ typedef struct { char *logfile; char *errfile; char *configfile; + uint dac_buffer_queue_desired_length; } shairport_cfg; //true if Shairport Sync is supposed to be sending output to the output device, false otherwise diff --git a/player.c b/player.c index 21bec3bd..8c168128 100644 --- a/player.c +++ b/player.c @@ -92,8 +92,6 @@ static int late_packet_message_sent; static uint64_t packet_count = 0; static int32_t last_seqno_read; - - // interthread variables static double software_mixer_volume = 1.0; static int fix_volume = 0x10000; @@ -105,7 +103,6 @@ static pthread_mutex_t vol_mutex = PTHREAD_MUTEX_INITIALIZER; #define MAX_PACKET 2048 // DAC buffer occupancy stuff -#define DAC_BUFFER_QUEUE_DESIRED_LENGTH 6615 #define DAC_BUFFER_QUEUE_MINIMUM_LENGTH 5000 typedef struct audio_buffer_entry { // decoded audio packets @@ -517,7 +514,7 @@ static abuf_t *buffer_get_frame(void) { first_packet_time_to_play = reference_timestamp_time+((delta+(int64_t)config.latency)<<32)/44100; // using the latency requested... if (local_time_now>=first_packet_time_to_play) { - debug(2,"First packet is late! It should have played before now. Flushing 0.1 seconds"); + debug(1,"First packet is late! It should have played before now. Flushing 0.1 seconds"); player_flush(first_packet_timestamp+4410); } } @@ -526,7 +523,7 @@ static abuf_t *buffer_get_frame(void) { if (first_packet_time_to_play!=0) { uint32_t filler_size = frame_size; - uint32_t max_dac_delay = DAC_BUFFER_QUEUE_DESIRED_LENGTH; + uint32_t max_dac_delay = config.dac_buffer_queue_desired_length; // if (dac_delay==0) // i.e. if this is the first fill filler_size = 4410; // 0.1 second -- the maximum we'll add to the DAC @@ -576,7 +573,7 @@ static abuf_t *buffer_get_frame(void) { } } } - wait = (ab_buffering || (dac_delay>=DAC_BUFFER_QUEUE_DESIRED_LENGTH) || (!ab_synced)) && (!please_stop); + wait = (ab_buffering || (dac_delay>=config.dac_buffer_queue_desired_length) || (!ab_synced)) && (!please_stop); // wait = (ab_buffering || (seq_diff(ab_read, ab_write) < (config.latency-22000)/(352)) || (!ab_synced)) && (!please_stop); if (wait) { uint64_t time_to_wait_for_wakeup_fp = ((uint64_t)1<<32)/44100; // this is time period of one frame @@ -901,7 +898,7 @@ static void *player_thread_func(void *arg) { // only allow stuffing if there is enough time to do it -- check DAC buffer... if (current_delayrespcode = 200; } diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 03c44d54..781e2b90 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -16,6 +16,7 @@ general = // resync_threshold = 2205; // a synchronisation error greater than this will cause resynchronisation; 0 disables it // log_verbosity = 0; // "0" means no verbosity, "3" is highest. // ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. +// dac_buffer_desired_length = 6615; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. }; latencies = diff --git a/shairport.c b/shairport.c index 45cf7b1f..7948eb06 100644 --- a/shairport.c +++ b/shairport.c @@ -341,8 +341,12 @@ int parse_options(int argc, char **argv) { config.output_name=(char *)str; /* Get the port setting. */ - if(config_lookup_int(&config.cfg, "general.port", &value)) - config.port=value; + if(config_lookup_int(&config.cfg, "general.port", &value)) { + if ((value<0) || (value>65535)) + die("Invalid port number \"%sd\". It should be between 0 and 65535, default is 5000",value); + else + config.port=value; + } /* Get the password setting. */ if(config_lookup_string(&config.cfg, "general.password", &str)) @@ -391,6 +395,14 @@ int parse_options(int argc, char **argv) { else die("Invalid ignore_volume_control option choice \"%s\". It should be \"yes\" or \"no\""); } + + /* Get the dac buffer size setting. */ + if(config_lookup_int(&config.cfg, "general.dac_buffer_desired_length", &value)) { + if ((value<0) || (value>66150)) + die("Invalid DAC buffer length \"%sd\". It should be between 0 and 66150, default is 6615",value); + else + config.dac_buffer_queue_desired_length=value; + } /* Get the default latency. */ if(config_lookup_int(&config.cfg, "latencies.default", &value)) @@ -493,6 +505,8 @@ int parse_options(int argc, char **argv) { debug(2,"busy timeout time is %d.",config.timeout); debug(2,"tolerance is %d frames.",config.tolerance); debug(2,"password is \"%s\".",config.password); + debug(2,"ignore_volume_contorl is %d.",config.ignore_volume_control); + debug(2,"dac desired buffer length is %d.",config.dac_buffer_queue_desired_length); #ifdef CONFIG_METADATA debug(2,"metdata enabled is %d.",config.metadata_enabled); debug(2,"metadata pipename is \"%s\".",config.metadata_pipename); @@ -592,6 +606,7 @@ int main(int argc, char **argv) { config.apname = malloc(20 + 100); snprintf(config.apname, 20 + 100, "Shairport Sync on %s", hostname); set_requested_connection_state_to_output(1); // we expect to be able to connect to the output device + config.dac_buffer_queue_desired_length = 6615; // 0.15 seconds. // this is a bit weird, but apparently necessary char* basec = strdup(argv[0]); From 54f955b522e674f020222a56e6a91414c7b990fc Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 6 May 2015 14:59:35 +0100 Subject: [PATCH 049/220] Typo --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7e7d404a..d35540d0 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -18,7 +18,7 @@ If you enable metadata, please note that the option has changed somewhat. The op * Session Timeout and Allow Session Interruption can now be set independently. This is really some "housekeeping" as referred to above -- it's a kind of a bug fix, where the bug in question is an inappropriate connection of the setting of two parameters. To explain: (1) By default, when a source such as iTunes starts playing to the Shairport Sync device, any other source attempting to start a play session receives a "busy" signal. If a source disappears without warning, Shairport Sync will wait for 120 seconds before dropping the session and allowing another source to start a play session. (2) The command-line option `-t` or `--timeout` allows you to set the wait time before dropping the session. If you set this parameter to `0`, Shairport Sync will not send a "busy" signal, thus allowing another source to interrupt an existing one. (3) The problem is that if you set the parameter to `0`, a session will never be dropped if the source disappears without warning. The (obvious) fix for this is to separate the setting of the two parameters, and this is now done in the configuration file `/etc/shairport-sync.conf` -- please see the settings `allow_session_interruption` and `session_timeout`. The behaviour of the `-t` and `--timeout` command-line options is unchanged but deprecated. * New Option -- "Ignore Volume Control" ('ignore_volume_control'). If you set this to "yes", the output from Shairport Sync is always set at 100%. This is useful when you want to set the volume locally. Available via the settings file only. - * New Option -- "Set DAC Buffer Desired Length" ('dac_buffer_desired_length'). Default is 6615 frames, or 0.15 seconds. On some slower machines, particularly with metadata processing going on, the DAC buffer can underflow on this setting,. so it might be worth making the buffer larger. A problem on software mixers only is that changes to volume control settings have to propagate through the buffer to be heard, so the larger the buffer, the longer the response time. This is only a problem if you're using a software mixer; hardware mixers are unaffected. Available via the settings file only. + * New Option -- "Set DAC Buffer Desired Length" ('dac_buffer_desired_length'). Default is 6615 frames, or 0.15 seconds. On some slower machines, particularly with metadata processing going on, the DAC buffer can underflow on this setting, so it might be worth making the buffer larger. A problem on software mixers only is that changes to volume control settings have to propagate through the buffer to be heard, so the larger the buffer, the longer the response time. This is only a problem if you're using a software mixer; hardware mixers are unaffected. Available via the settings file only. * Statistics option correctly reports when no frames are received in a sampling interval. Version 2.3 From 511fc7fbce77374fdae7b4cdda60b71a1484d783 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 6 May 2015 17:29:39 +0000 Subject: [PATCH 050/220] Quieten a debug message. --- player.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player.c b/player.c index 8c168128..8c6d7797 100644 --- a/player.c +++ b/player.c @@ -898,7 +898,7 @@ static void *player_thread_func(void *arg) { // only allow stuffing if there is enough time to do it -- check DAC buffer... if (current_delay Date: Wed, 6 May 2015 18:36:17 +0100 Subject: [PATCH 051/220] Update ref to Arch Linux package. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 867be255..64c57765 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Building And Installing --------------------- If you're interested in Shairport Sync for OpenWrt, there's an OpenWrt package at https://github.com/mikebrady/shairport-sync-for-openwrt. OpenWrt doesn't support the IQaudIO Pi-DAC. -A number of Shairport Sync for Arch Linux installation packages exist, for instance [EliaCereda/shairport-sync-PKGBUILD](https://github.com/EliaCereda/shairport-sync-PKGBUILD). An installation package is also available at [Arch Linux User Repository](https://aur.archlinux.org/packages/shairport-sync/). +An Arch Linux installation package is available (thanks!) at [EliaCereda/shairport-sync-PKGBUILD](https://github.com/EliaCereda/shairport-sync-PKGBUILD). Otherwise, follow these instructions. From e7c585b5841c0712b374937fd09cf2f3d5b88325 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 6 May 2015 18:38:31 +0100 Subject: [PATCH 052/220] Rephrase active branch stuff. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 64c57765..75931127 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ To maintain the exact latency required, if an output device is running slow rela There are four default latency settings, chosen automatically. One latency matches the latency used by recent versions of iTunes when playing audio and the other matches the latency used by older version of iTunes, by iOS devices and by iTunes and Quicktime Player when playing video. A third default latency is used when the audio source is `forked-daapd`. The fourth latency is the default if no other latency is chosen. -Shairport Sync is a pretty substantial rewrite of Shairport 1.0 by James Laird and others -- please see https://github.com/abrasive/shairport/blob/master/README.md#contributors-to-version-1x for a list of the contributors to Shairport 1.x and Shairport 0.x. From a "heritage" point of view, Shairport Sync is a fork of Shairport 1.0 and the active branch is Shairport Sync 2.2. +Shairport Sync is a pretty substantial rewrite of Shairport 1.0 by James Laird and others -- please see https://github.com/abrasive/shairport/blob/master/README.md#contributors-to-version-1x for a list of the contributors to Shairport 1.x and Shairport 0.x. From a "heritage" point of view, Shairport Sync is a fork of Shairport 1.0. Shairport Sync works only with Linux and ALSA. The sound card you use must be capable of working with 44,100 samples per second interleaved PCM stereo (you'll get a message in the logfile if there's a problem). From bce91e4013e4eb740a605f23477bdace3482af63 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 7 May 2015 18:24:08 +0100 Subject: [PATCH 053/220] Remove "daemonise" setting. The daemonise option really belongs in on the command line. --- scripts/shairport-sync.conf | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 781e2b90..5ad07eb7 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -5,7 +5,6 @@ general = { // name = "Shairport Sync Player"; // This is the name the service will advertise to iTunes. The default is "Shairport Sync on " - daemonize = "yes"; // default is "no" // mdns_backend = "avahi"; // not used, not tested // output_backend = "alsa"; // setting is not used, alsa is default // port = 5000; @@ -15,8 +14,8 @@ general = // drift = 88; // allow this number of frames of drift before correcting it // resync_threshold = 2205; // a synchronisation error greater than this will cause resynchronisation; 0 disables it // log_verbosity = 0; // "0" means no verbosity, "3" is highest. -// ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. -// dac_buffer_desired_length = 6615; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. +// ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. +// dac_buffer_desired_length = 6615; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. }; latencies = From 65cdc5261eb152be47f22bf65a0ba060dd7aa6f6 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 7 May 2015 18:26:12 +0100 Subject: [PATCH 054/220] Add `-d` option back in. This is the right place for this, in a System V init script. Other startup systems don't use it. --- scripts/shairport-sync | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/shairport-sync b/scripts/shairport-sync index 8288d6e7..d1175504 100755 --- a/scripts/shairport-sync +++ b/scripts/shairport-sync @@ -63,7 +63,7 @@ do_start() # BTW, if you're using software volume control, you may have to use alsamixer or similar to set the output device's volume to its maximum level first # BTW2, you can use alsamixer to find device identifiers (e.g. hw:1) and mixer names (e.g. "Speaker"). No need to change ALSA's defaults. # BTW3, the argument after -a is simply the name the shairport service will be visible as. - start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- || return 2 + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d || return 2 # start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d -a "Topping TP30 or Griffin iMic on Raspberry Pi" -- -d hw:1 -t hardware -c "PCM" || return 2 # start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d -a "'3D Sound' USB Soundcard on Raspberry Pi" -- -d hw:1 -t hardware -c "Speaker" || return 2 # BTW, that "3D Sound" USB soundcard sometimes has the mixer name "Headphone" rather than "Speaker" -- use alsamixer to check. From d2e48f73cabe1ec97052dbc34ae2d7d9d396cb85 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 8 May 2015 09:47:34 +0000 Subject: [PATCH 055/220] Allow -d option as part of the basic set of command line options. Fix a few documentation and help typos. --- shairport.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shairport.c b/shairport.c index 7948eb06..8421f702 100644 --- a/shairport.c +++ b/shairport.c @@ -152,6 +152,7 @@ void usage(char *progname) { printf("\n"); printf("Options:\n"); printf(" -h, --help show this help\n"); + printf(" -d, --daemon daemonise.\n"); printf(" -V, --version show version information\n"); printf(" -k, --kill kill the existing shairport daemon.\n"); printf(" -D, --disconnectFromOutput disconnect immediately from the output device.\n"); @@ -173,7 +174,6 @@ void usage(char *progname) { printf(" \"basic\" (default) inserts or deletes audio frames from packet frames with low processor overhead.\n"); printf(" \"soxr\" uses libsoxr to minimally resample packet frames -- moderate processor overhead.\n"); printf(" \"soxr\" option only available if built with soxr support.\n"); - printf(" -d, --daemon daemonise.\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, e.g. /usr/bin/logger.\n"); @@ -189,7 +189,7 @@ void usage(char *progname) { printf(" --tolerance=TOLERANCE allow a synchronization error of TOLERANCE frames (default 88) before trying to correct it.\n"); printf(" --password=PASSWORD require PASSWORD to connect. Default is not to require a password.\n"); #ifdef CONFIG_METADATA - printf(" --metadata-pipename=FILE get metadata from the source and pipe it to FILE, e.g. --meta-dir=/tmp/shairport-sync-metadata.\n"); + printf(" --metadata-pipename=PIPE send metadata to PIPE, e.g. --metadata-pipename=/tmp/shairport-sync-metadata.\n"); printf(" --get-coverart send cover art through the metadata pipe.\n"); #endif #endif @@ -216,11 +216,11 @@ int parse_options(int argc, char **argv) { { "disconnectFromOutput", 'D', POPT_ARG_NONE, NULL, 0, NULL }, { "reconnectToOutput", 'R', POPT_ARG_NONE, NULL, 0, NULL }, { "kill", 'k', POPT_ARG_NONE, NULL, 0, NULL }, + { "daemon", 'd', POPT_ARG_NONE, &config.daemonise, 0, NULL }, #ifdef SUPPORT_CONFIG_FILES { "configfile", 'c', POPT_ARG_STRING, &config.configfile, 0, NULL }, #endif #ifdef COMMAND_LINE_ARGUMENT_SUPPORT - { "daemon", 'd', POPT_ARG_NONE, &config.daemonise, 0, NULL }, { "statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL}, { "version", 'V', POPT_ARG_NONE, NULL, 0, NULL}, { "port", 'p', POPT_ARG_INT, &config.port, 0, NULL } , From 14f68c18ac8836742b576e0ea6f8ce61a747b317 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 8 May 2015 17:00:53 +0100 Subject: [PATCH 056/220] Typo --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d35540d0..b0a676b8 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -13,7 +13,7 @@ When `make install` is executed, a sample configuration is installed or updated If you enable metadata, please note that the option has changed somewhat. The option `-M` has a new long name equivalent: `--metadata-pipename` and the argument you provide must now be the full name of the metadata pipe, e.g. `-M /tmp/shairport-sync-metadata`. * Enhancements - * Shairport Sync now reads settings from the configuration file `/etc/shairport-sync.conf`. This has settings for most command-line options and it's where any new settings will go. A default configuration file will be installed if one doesn't exist, and a sample file configuration file is always installed or updated. Details of settings are provided in the sample file. Shairport Sync relies on the `libconfig` library to read configuration files. For the present, you can disable the new feature (and save the space taken up by `libconfig`) by using the configure option `--without-config-file-support`. + * Shairport Sync now reads settings from the configuration file `/etc/shairport-sync.conf`. This has settings for most command-line options and it's where any new settings will go. A default configuration file will be installed if one doesn't exist, and a sample file configuration file is always installed or updated. Details of settings are provided in the sample file. Shairport Sync relies on the `libconfig` library to read configuration files. For the present, you can disable the new feature (and save the space taken up by `libconfig`) by using the configure option `--without-configfile-support`. * New command-line option `-c ` or `--configfile=` allows you to specify a configuration file other than `/etc/shairport-sync.conf`. * Session Timeout and Allow Session Interruption can now be set independently. This is really some "housekeeping" as referred to above -- it's a kind of a bug fix, where the bug in question is an inappropriate connection of the setting of two parameters. To explain: (1) By default, when a source such as iTunes starts playing to the Shairport Sync device, any other source attempting to start a play session receives a "busy" signal. If a source disappears without warning, Shairport Sync will wait for 120 seconds before dropping the session and allowing another source to start a play session. (2) The command-line option `-t` or `--timeout` allows you to set the wait time before dropping the session. If you set this parameter to `0`, Shairport Sync will not send a "busy" signal, thus allowing another source to interrupt an existing one. (3) The problem is that if you set the parameter to `0`, a session will never be dropped if the source disappears without warning. The (obvious) fix for this is to separate the setting of the two parameters, and this is now done in the configuration file `/etc/shairport-sync.conf` -- please see the settings `allow_session_interruption` and `session_timeout`. The behaviour of the `-t` and `--timeout` command-line options is unchanged but deprecated. From f3d84858bbb3ec2b565c72356c518f32e64900dc Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 8 May 2015 17:09:19 +0100 Subject: [PATCH 057/220] Update man link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 75931127..33c25862 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ Run `$sudo make install` to install `shairport-sync` along with a default config Man Page -------- -You can view the man page here: http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/2.3/man/shairport-sync.html +You can view the man page here: http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/development/man/shairport-sync.html Configuring Shairport Sync From 6bd14a6a091f925dfd91ca8fd5e7b391c180aae2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 8 May 2015 18:41:45 +0100 Subject: [PATCH 058/220] Add note about switching to the development branch --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 33c25862..937c0328 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,10 @@ Download Shairport Sync: Next, `cd` into the shairport-sync directory and execute the following commands: -`$ autoreconf -i -f` +``` +$ git checkout development #switch to the development branch of shairport-sync +$ autoreconf -i -f +``` Choose the appropriate `--with-*` options: From 744214df54e39507b9b048c281706417f4723f0e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 20 May 2015 19:58:07 +0000 Subject: [PATCH 059/220] Apply command line arguments _after_ sewttings from configuration file --- audio_alsa.c | 58 +++++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 42661a95..90682d12 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -98,6 +98,36 @@ static int init(int argc, char **argv, config_t *cfgp) { int value; int hardware_mixer = 0; + + // get settings from settings file first, allow them to be over-ridden by command line options + + if (cfgp!=NULL) { + /* Get the Output Device Name. */ + if(config_lookup_string(cfgp, "alsa.output_device", &str)) { + alsa_out_dev = (char*)str; + } + + /* Get the Mixer Type setting. */ + if(config_lookup_string(cfgp, "alsa.mixer_type", &str)) { + if (strcasecmp(str,"software")==0) + hardware_mixer=0; + else if (strcasecmp(str,"hardware")==0) + hardware_mixer=1; + else + die("Invalid alsa mixer option choice \"%s\". It should be \"software\" or \"hardware\""); + } + + /* Get the Mixer Device Name. */ + if(config_lookup_string(cfgp, "alsa.mixer_device", &str)) { + alsa_mix_dev = (char*)str; + } + + /* Get the Mixer Control Name. */ + if(config_lookup_string(cfgp, "alsa.mixer_control_name", &str)) { + alsa_mix_ctrl = (char*)str; + } + + } optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour argv--; // so we shift the arguments to satisfy getopt() @@ -131,34 +161,6 @@ static int init(int argc, char **argv, config_t *cfgp) { if (optind < argc) die("Invalid audio argument: %s", argv[optind]); - if (cfgp!=NULL) { - /* Get the Output Device Name. */ - if(config_lookup_string(cfgp, "alsa.output_device", &str)) { - alsa_out_dev = (char*)str; - } - - /* Get the Mixer Type setting. */ - if(config_lookup_string(cfgp, "alsa.mixer_type", &str)) { - if (strcasecmp(str,"software")==0) - hardware_mixer=0; - else if (strcasecmp(str,"hardware")==0) - hardware_mixer=1; - else - die("Invalid alsa mixer option choice \"%s\". It should be \"software\" or \"hardware\""); - } - - /* Get the Mixer Device Name. */ - if(config_lookup_string(cfgp, "alsa.mixer_device", &str)) { - alsa_mix_dev = (char*)str; - } - - /* Get the Mixer Control Name. */ - if(config_lookup_string(cfgp, "alsa.mixer_control_name", &str)) { - alsa_mix_ctrl = (char*)str; - } - - } - if (!hardware_mixer) return 0; From 4cd3f5c399d7b47ea68418634b5e9d4ccc7ac395 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 20 May 2015 19:58:40 +0000 Subject: [PATCH 060/220] experiment with pipe backend. --- audio_pipe.c | 30 ++++++++++++++++++++++++++---- scripts/shairport-sync.conf | 7 +++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/audio_pipe.c b/audio_pipe.c index 367eb470..df54bbd4 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -29,6 +29,7 @@ #include #include #include +#include #include "common.h" #include "audio.h" @@ -37,11 +38,14 @@ static int fd = -1; char *pipename = NULL; static void start(int sample_rate) { - fd = open(pipename, O_WRONLY); + debug(1,"Pipename to start is \"%s\"",pipename); + fd = open(pipename, O_WRONLY | O_NONBLOCK); + /* if (fd < 0) { perror("open"); die("could not open specified pipe for writing"); } + */ } static void play(short buf[], int samples) { @@ -53,10 +57,28 @@ static void stop(void) { } static int init(int argc, char **argv, config_t *cfgp) { - if (argc != 1) - die("bad argument(s) to pipe"); - pipename = strdup(argv[0]); + if (cfgp!=NULL) { + /* Get the Output Pipename. */ + const char *str; + if(config_lookup_string(cfgp, "pipe.name", &str)) { + pipename = (char*)str; + } + } + + + if ((pipename==NULL) && (argc != 1)) + die("bad or missing argument(s) to pipe"); + + if (argc==1) + pipename = strdup(argv[0]); + + // here, create the pipe + if (mkfifo(pipename, 0644) && errno != EEXIST) + die("Could not create metadata FIFO %s", pipename); + + + debug(1,"Pipename is \"%s\"",pipename); // test open pipe so we error on startup if it's going to fail start(44100); diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 5ad07eb7..e4886b5b 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -42,6 +42,7 @@ sessioncontrol = // session_timeout = 120; }; +// These are parameters for the alsa back end, the only back end that currently works with shairport-sync alsa = { // output_device = "default"; @@ -50,3 +51,9 @@ alsa = // mixer_control_name = "PCM"; // the name of the mixer to use -- there is no default. }; +// These are parameters for the pipe back end, an experimental back end that directs output to a pipe. +pipe = +{ +// name = "/dev/null"; +}; + From c887aafdf00c17bec01444f3899bf7b8393cddc7 Mon Sep 17 00:00:00 2001 From: Mke Brady Date: Thu, 21 May 2015 09:09:37 +0100 Subject: [PATCH 061/220] Very broken player mod. --- player.c | 202 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 109 insertions(+), 93 deletions(-) diff --git a/player.c b/player.c index 8c6d7797..72622f79 100644 --- a/player.c +++ b/player.c @@ -849,117 +849,133 @@ static void *player_thread_func(void *arg) { } else { td_in_frames = -((-td*44100)>>32); } + + // This is the timing error for the next audio frame in the DAC, if applicable + int64_t sync_error =0; + + int amount_to_stuff = 0; + + // check sequencing + if (last_seqno_read==-1) + last_seqno_read=inframe->sequence_number; + else { + last_seqno_read = (SUCCESSOR(last_seqno_read) & 0xffff); + if (inframe->sequence_number!=last_seqno_read) { + debug(1,"Player: packets out of sequence: expected: %d, got: %d, sync error: %d frames.",last_seqno_read,inframe->sequence_number,sync_error); + last_seqno_read=inframe->sequence_number; // reset warning... + } + } + if (config.output->delay) { current_delay = config.output->delay(); if (current_delay==-1) { debug(1,"Delay error when checking running latency."); current_delay=0; } - } else { - current_delay = 0; - } - if (current_delaymaximum_buffer_occupancy) - maximum_buffer_occupancy=bo; - - // this is the actual delay, including the latency we actually want, which will fluctuate a good bit about a potentially rising or falling trend. - int64_t delay = td_in_frames+rt-(nt-current_delay); - - // This is the timing error for the next audio frame in the DAC. - int64_t sync_error = delay-config.latency; - - // before we finally commit to this frame, check its sequencing and timing - // check sequencing - if (last_seqno_read==-1) - last_seqno_read=inframe->sequence_number; - else { - last_seqno_read = (SUCCESSOR(last_seqno_read) & 0xffff); - if (inframe->sequence_number!=last_seqno_read) - debug(1,"Player: packets out of sequence: expected: %d, got: %d, sync error: %d frames.",last_seqno_read,inframe->sequence_number,sync_error); - last_seqno_read=inframe->sequence_number; // reset warning... - } - - int amount_to_stuff = 0; - // require a certain error before bothering to fix it... - if (sync_error>config.tolerance) { - amount_to_stuff = -1; - } - if (sync_error<-config.tolerance) { - amount_to_stuff = 1; - } - - // only allow stuffing if there is enough time to do it -- check DAC buffer... - if (current_delay352) - amount_to_stuff=0; - } +// } else { +// current_delay = 0; +// } + if (current_delayplay(inbuf, frame_size); - } else { + uint32_t bo = seq_diff(ab_read,ab_write); + + if (bomaximum_buffer_occupancy) + maximum_buffer_occupancy=bo; + + // this is the actual delay, including the latency we actually want, which will fluctuate a good bit about a potentially rising or falling trend. + int64_t delay = td_in_frames+rt-(nt-current_delay); + + // This is the timing error for the next audio frame in the DAC. + sync_error = delay-config.latency; + + // before we finally commit to this frame, check its sequencing and timing + + // require a certain error before bothering to fix it... + if (sync_error>config.tolerance) { + amount_to_stuff = -1; + } + if (sync_error<-config.tolerance) { + amount_to_stuff = 1; + } + + // only allow stuffing if there is enough time to do it -- check DAC buffer... + if (current_delay352) + amount_to_stuff=0; + } + + if ((amount_to_stuff==0) && (fix_volume==0x10000)) { + // if no stuffing needed and no volume adjustment, then + // don't send to stuff_buffer_* and don't copy to outbuf; just send directly to the output device... + config.output->play(inbuf, frame_size); + } else { #ifdef HAVE_LIBSOXR - switch (config.packet_stuffing) { - case ST_basic: + switch (config.packet_stuffing) { + case ST_basic: // if (amount_to_stuff) debug(1,"Basic stuff..."); - play_samples = stuff_buffer_basic(inbuf, outbuf,amount_to_stuff); - break; - case ST_soxr: + play_samples = stuff_buffer_basic(inbuf, outbuf,amount_to_stuff); + break; + case ST_soxr: // if (amount_to_stuff) debug(1,"Soxr stuff..."); - play_samples = stuff_buffer_soxr(inbuf, outbuf,amount_to_stuff); - break; - } + play_samples = stuff_buffer_soxr(inbuf, outbuf,amount_to_stuff); + break; + } #else // if (amount_to_stuff) debug(1,"Standard stuff..."); - play_samples = stuff_buffer_basic(inbuf, outbuf,amount_to_stuff); + play_samples = stuff_buffer_basic(inbuf, outbuf,amount_to_stuff); #endif - /* - { - int co; - int is_silent=1; - short *p = outbuf; - for (co=0;coplay(outbuf, play_samples); + /* + { + int co; + int is_silent=1; + short *p = outbuf; + for (co=0;cotimestamp!=0) && (!please_stop) && (config.resyncthreshold!=0) && (abs(sync_error)>config.resyncthreshold)) { - sync_error_out_of_bounds++; - // debug(1,"Sync error out of bounds: Error: %lld; previous error: %lld; DAC: %lld; timestamp: %llx, time now %llx",sync_error,previous_sync_error,current_delay,inframe->timestamp,local_time_now); - if (sync_error_out_of_bounds>3) { - debug(1,"Lost sync with source for %d consecutive packets -- flushing and resyncing. Error: %lld.",sync_error_out_of_bounds,sync_error); + if (is_silent) + debug(1,"Silence!"); + } + */ + + config.output->play(outbuf, play_samples); + } + + // check for loss of sync + // timestamp of zero means an inserted silent frame in place of a missing frame + if ((inframe->timestamp!=0) && (!please_stop) && (config.resyncthreshold!=0) && (abs(sync_error)>config.resyncthreshold)) { + sync_error_out_of_bounds++; + // debug(1,"Sync error out of bounds: Error: %lld; previous error: %lld; DAC: %lld; timestamp: %llx, time now %llx",sync_error,previous_sync_error,current_delay,inframe->timestamp,local_time_now); + if (sync_error_out_of_bounds>3) { + debug(1,"Lost sync with source for %d consecutive packets -- flushing and resyncing. Error: %lld.",sync_error_out_of_bounds,sync_error); + sync_error_out_of_bounds = 0; + player_flush(nt); + } + } else { sync_error_out_of_bounds = 0; - player_flush(nt); - } + } } else { - sync_error_out_of_bounds = 0; + // if there is no delay procedure, there can be no synchronising + if (fix_volume==0x10000) + config.output->play(inbuf, frame_size); + else { + play_samples = stuff_buffer_basic(inbuf, outbuf,0); + config.output->play(outbuf, frame_size); + } } // mark the frame as finished From 5f009106936bdf1bc1040b945825af3b472df8d3 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 21 May 2015 16:12:37 +0000 Subject: [PATCH 062/220] Allow the pipe to be stdout, regluate timing better in the player thread. Rough, rough. --- audio_pipe.c | 26 ++++++++++++-------------- player.c | 30 +++++++++++++++++++++++++----- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/audio_pipe.c b/audio_pipe.c index df54bbd4..b218d787 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -39,13 +39,10 @@ char *pipename = NULL; static void start(int sample_rate) { debug(1,"Pipename to start is \"%s\"",pipename); - fd = open(pipename, O_WRONLY | O_NONBLOCK); - /* - if (fd < 0) { - perror("open"); - die("could not open specified pipe for writing"); - } - */ + if (strcasecmp(pipename,"STDOUT")==0) + fd = STDOUT_FILENO; + else + fd = open(pipename, O_WRONLY); } static void play(short buf[], int samples) { @@ -53,6 +50,7 @@ static void play(short buf[], int samples) { } static void stop(void) { + if (fd!=STDOUT_FILENO) close(fd); } @@ -72,10 +70,12 @@ static int init(int argc, char **argv, config_t *cfgp) { if (argc==1) pipename = strdup(argv[0]); - + + // here, create the pipe - if (mkfifo(pipename, 0644) && errno != EEXIST) - die("Could not create metadata FIFO %s", pipename); + if (strcasecmp(pipename,"STDOUT")!=0) + if (mkfifo(pipename, 0644) && errno != EEXIST) + die("Could not create metadata FIFO %s", pipename); debug(1,"Pipename is \"%s\"",pipename); @@ -88,14 +88,12 @@ static int init(int argc, char **argv, config_t *cfgp) { } static void deinit(void) { - if (fd > 0) + if ((fd > 0) && (fd!=STDOUT_FILENO)) close(fd); - if (pipename) - free(pipename); } static void help(void) { - printf(" pipe takes 1 argument: the name of the FIFO to write to.\n"); + printf(" pipe takes 1 argument: the name of the FIFO to write to, which can be \"stdout\".\n"); } audio_output audio_pipe = { diff --git a/player.c b/player.c index 72622f79..6840e8ff 100644 --- a/player.c +++ b/player.c @@ -573,8 +573,31 @@ static abuf_t *buffer_get_frame(void) { } } } - wait = (ab_buffering || (dac_delay>=config.dac_buffer_queue_desired_length) || (!ab_synced)) && (!please_stop); -// wait = (ab_buffering || (seq_diff(ab_read, ab_write) < (config.latency-22000)/(352)) || (!ab_synced)) && (!please_stop); + + // here, we work out whether to wait or not + // if we are getting feedback from the backend, we can use that. + // otherwise we have to release a buffer when the time is right. + + if (config.output->delay) + wait = (ab_buffering || (dac_delay>=config.dac_buffer_queue_desired_length) || (!ab_synced)) && (!please_stop); + else { + int do_wait = 1; + if ((curframe) && (curframe->ready) && (curframe->timestamp)) { + uint32_t reference_timestamp; + uint64_t reference_timestamp_time; + get_reference_timestamp_stuff(&reference_timestamp,&reference_timestamp_time); + if (reference_timestamp) { // if we have a reference time + uint32_t packet_timestamp=curframe->timestamp; + int64_t delta = ((int64_t)packet_timestamp-(int64_t)reference_timestamp); + int64_t time_to_play = reference_timestamp_time+((delta+(int64_t)(config.latency-config.dac_buffer_queue_desired_length))<<32)/44100; // using the latency requested... + if (local_time_now>=time_to_play) { + do_wait = 0; + } + } + } + wait = (ab_buffering || (do_wait!=0) || (!ab_synced)) && (!please_stop); +// wait = (ab_buffering || (seq_diff(ab_read, ab_write) < (config.latency-22000)/(352)) || (!ab_synced)) && (!please_stop); + } if (wait) { uint64_t time_to_wait_for_wakeup_fp = ((uint64_t)1<<32)/44100; // this is time period of one frame time_to_wait_for_wakeup_fp *= 4*352; // four full 352-frame packets @@ -873,9 +896,6 @@ static void *player_thread_func(void *arg) { debug(1,"Delay error when checking running latency."); current_delay=0; } -// } else { -// current_delay = 0; -// } if (current_delay Date: Fri, 22 May 2015 17:08:57 +0000 Subject: [PATCH 063/220] Uses the dac buffer size to offset the timing of the packets by a fixed amount --- player.c | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/player.c b/player.c index 6840e8ff..838b0e23 100644 --- a/player.c +++ b/player.c @@ -511,8 +511,16 @@ static abuf_t *buffer_get_frame(void) { // it might not be the timestamp of our first packet, however, so we might have to do some calculations. int64_t delta = ((int64_t)first_packet_timestamp-(int64_t)reference_timestamp); + + // Now, if the back end can tell us about its latency, we will set the first packet's time to play to be the exact time. + // Otherwise we will set it to the exact time less the config.dac_buffer_queue_desired_length, a proxy for the lead time we want the stream to appear at the output + // We will probably change this in the future to pick up the lead time from the back end itself + + if (config.output->delay) + first_packet_time_to_play = reference_timestamp_time+((delta+(int64_t)config.latency)<<32)/44100; // using the latency requested... + else + first_packet_time_to_play = reference_timestamp_time+((delta+(int64_t)config.latency-(int64_t)config.dac_buffer_queue_desired_length)<<32)/44100; // using the latency requested... - first_packet_time_to_play = reference_timestamp_time+((delta+(int64_t)config.latency)<<32)/44100; // using the latency requested... if (local_time_now>=first_packet_time_to_play) { debug(1,"First packet is late! It should have played before now. Flushing 0.1 seconds"); player_flush(first_packet_timestamp+4410); @@ -589,7 +597,20 @@ static abuf_t *buffer_get_frame(void) { if (reference_timestamp) { // if we have a reference time uint32_t packet_timestamp=curframe->timestamp; int64_t delta = ((int64_t)packet_timestamp-(int64_t)reference_timestamp); - int64_t time_to_play = reference_timestamp_time+((delta+(int64_t)(config.latency-config.dac_buffer_queue_desired_length))<<32)/44100; // using the latency requested... + int64_t offset = (int64_t)config.latency-(int64_t)config.dac_buffer_queue_desired_length; + int64_t net_offset = delta+offset; + int64_t time_to_play = reference_timestamp_time; + int64_t net_offset_fp_sec; + if (net_offset>=0) { + net_offset_fp_sec = (net_offset<<32)/44100; + time_to_play+=net_offset_fp_sec; // using the latency requested... + debug(2,"Net Offset: %lld, adjusted: %lld.",net_offset,net_offset_fp_sec); + } else { + net_offset_fp_sec = ((-net_offset)<<32)/44100; + time_to_play-=net_offset_fp_sec; + debug(2,"Net Offset: %lld, adjusted: -%lld.",net_offset,net_offset_fp_sec); + } + if (local_time_now>=time_to_play) { do_wait = 0; } From 6e7358c76272130e18f818e17c16baccc637a163 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 27 May 2015 16:36:31 +0000 Subject: [PATCH 064/220] Simplify calculation of buffer timing. Rename dac_buffer_queue_desired_length --- RELEASENOTES.md | 2 ++ common.h | 2 +- player.c | 33 +++++++++++++++------------------ scripts/shairport-sync.conf | 8 +++++--- shairport.c | 12 ++++++------ 5 files changed, 29 insertions(+), 28 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b0a676b8..f2dc05b5 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -12,6 +12,8 @@ When `make install` is executed, a sample configuration is installed or updated If you enable metadata, please note that the option has changed somewhat. The option `-M` has a new long name equivalent: `--metadata-pipename` and the argument you provide must now be the full name of the metadata pipe, e.g. `-M /tmp/shairport-sync-metadata`. +Change the name of the setting "dac_buffer_desired_length" to "backend_buffer_desired_length" + * Enhancements * Shairport Sync now reads settings from the configuration file `/etc/shairport-sync.conf`. This has settings for most command-line options and it's where any new settings will go. A default configuration file will be installed if one doesn't exist, and a sample file configuration file is always installed or updated. Details of settings are provided in the sample file. Shairport Sync relies on the `libconfig` library to read configuration files. For the present, you can disable the new feature (and save the space taken up by `libconfig`) by using the configure option `--without-configfile-support`. * New command-line option `-c ` or `--configfile=` allows you to specify a configuration file other than `/etc/shairport-sync.conf`. diff --git a/common.h b/common.h index fdbe32b0..ed4b8cab 100644 --- a/common.h +++ b/common.h @@ -76,7 +76,7 @@ typedef struct { char *logfile; char *errfile; char *configfile; - uint dac_buffer_queue_desired_length; + uint backend_buffer_desired_length; // this will be the desired number of frames in the audio backend buffer -- the DAC buffer for ALSA } shairport_cfg; //true if Shairport Sync is supposed to be sending output to the output device, false otherwise diff --git a/player.c b/player.c index 838b0e23..f352938a 100644 --- a/player.c +++ b/player.c @@ -505,21 +505,23 @@ static abuf_t *buffer_get_frame(void) { // debug(1,"First frame seen with timestamp..."); first_packet_timestamp=curframe->timestamp; // we will keep buffering until we are supposed to start playing this - // here, see if we should start playing. We need to know when to allow the packets to be sent to the player - // we will get a fix every second or so, which will be stored as a pair consisting of - // the time when the packet with a particular timestamp should be played. - // it might not be the timestamp of our first packet, however, so we might have to do some calculations. + // Here, see if we should start playing. We need to know when to allow the packets to be sent to the player. + // We will be getting a timing fix every second or so from the source, which will be stored as a pair consisting of - + // i.e. the local time that corresponds to a specific packet timestamp. + // It might not be the timestamp of our first packet, however, so we might have to do some calculations. + // Also, we have to allow for the desired latency. int64_t delta = ((int64_t)first_packet_timestamp-(int64_t)reference_timestamp); - // Now, if the back end can tell us about its latency, we will set the first packet's time to play to be the exact time. - // Otherwise we will set it to the exact time less the config.dac_buffer_queue_desired_length, a proxy for the lead time we want the stream to appear at the output - // We will probably change this in the future to pick up the lead time from the back end itself + // Now, if the back end can tell us about its latency, we will set the first packet's time to play to be the exact time it's supposed to play plus the desired latency. + // Otherwise we will set it to the exact time plus desired latency less the config.backend_buffer_desired_length, a proxy for the lead time we want the stream to appear at the output. + + // We may change this in the future to pick up the lead time from the back end itself if (config.output->delay) first_packet_time_to_play = reference_timestamp_time+((delta+(int64_t)config.latency)<<32)/44100; // using the latency requested... else - first_packet_time_to_play = reference_timestamp_time+((delta+(int64_t)config.latency-(int64_t)config.dac_buffer_queue_desired_length)<<32)/44100; // using the latency requested... + first_packet_time_to_play = reference_timestamp_time+((delta+(int64_t)config.latency-(int64_t)config.backend_buffer_desired_length)<<32)/44100; // using the latency requested... if (local_time_now>=first_packet_time_to_play) { debug(1,"First packet is late! It should have played before now. Flushing 0.1 seconds"); @@ -531,7 +533,7 @@ static abuf_t *buffer_get_frame(void) { if (first_packet_time_to_play!=0) { uint32_t filler_size = frame_size; - uint32_t max_dac_delay = config.dac_buffer_queue_desired_length; + uint32_t max_dac_delay = config.backend_buffer_desired_length; // if (dac_delay==0) // i.e. if this is the first fill filler_size = 4410; // 0.1 second -- the maximum we'll add to the DAC @@ -582,13 +584,9 @@ static abuf_t *buffer_get_frame(void) { } } - // here, we work out whether to wait or not - // if we are getting feedback from the backend, we can use that. - // otherwise we have to release a buffer when the time is right. + // Here, we work out whether to release a packet or wait + // We release a buffer when the time is right. - if (config.output->delay) - wait = (ab_buffering || (dac_delay>=config.dac_buffer_queue_desired_length) || (!ab_synced)) && (!please_stop); - else { int do_wait = 1; if ((curframe) && (curframe->ready) && (curframe->timestamp)) { uint32_t reference_timestamp; @@ -597,7 +595,7 @@ static abuf_t *buffer_get_frame(void) { if (reference_timestamp) { // if we have a reference time uint32_t packet_timestamp=curframe->timestamp; int64_t delta = ((int64_t)packet_timestamp-(int64_t)reference_timestamp); - int64_t offset = (int64_t)config.latency-(int64_t)config.dac_buffer_queue_desired_length; + int64_t offset = (int64_t)config.latency-(int64_t)config.backend_buffer_desired_length; int64_t net_offset = delta+offset; int64_t time_to_play = reference_timestamp_time; int64_t net_offset_fp_sec; @@ -617,8 +615,7 @@ static abuf_t *buffer_get_frame(void) { } } wait = (ab_buffering || (do_wait!=0) || (!ab_synced)) && (!please_stop); -// wait = (ab_buffering || (seq_diff(ab_read, ab_write) < (config.latency-22000)/(352)) || (!ab_synced)) && (!please_stop); - } + if (wait) { uint64_t time_to_wait_for_wakeup_fp = ((uint64_t)1<<32)/44100; // this is time period of one frame time_to_wait_for_wakeup_fp *= 4*352; // four full 352-frame packets diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index e4886b5b..e5c35a9d 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -14,8 +14,10 @@ general = // drift = 88; // allow this number of frames of drift before correcting it // resync_threshold = 2205; // a synchronisation error greater than this will cause resynchronisation; 0 disables it // log_verbosity = 0; // "0" means no verbosity, "3" is highest. -// ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. -// dac_buffer_desired_length = 6615; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. +// ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. + +// This may well change -- move it to the backend's settings... +// audio_backend_buffer_desired_length = 6615; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. }; latencies = @@ -54,6 +56,6 @@ alsa = // These are parameters for the pipe back end, an experimental back end that directs output to a pipe. pipe = { -// name = "/dev/null"; +// name = "stdout"; }; diff --git a/shairport.c b/shairport.c index 8421f702..472f7d30 100644 --- a/shairport.c +++ b/shairport.c @@ -396,12 +396,12 @@ int parse_options(int argc, char **argv) { die("Invalid ignore_volume_control option choice \"%s\". It should be \"yes\" or \"no\""); } - /* Get the dac buffer size setting. */ - if(config_lookup_int(&config.cfg, "general.dac_buffer_desired_length", &value)) { + /* Get the audio backend's desired buffer size setting. */ + if(config_lookup_int(&config.cfg, "general.audio_backend_buffer_desired_length", &value)) { if ((value<0) || (value>66150)) - die("Invalid DAC buffer length \"%sd\". It should be between 0 and 66150, default is 6615",value); + die("Invalid audio backend buffer desired length \"%sd\". It should be between 0 and 66150, default is 6615",value); else - config.dac_buffer_queue_desired_length=value; + config.backend_buffer_desired_length=value; } /* Get the default latency. */ @@ -506,7 +506,7 @@ int parse_options(int argc, char **argv) { debug(2,"tolerance is %d frames.",config.tolerance); debug(2,"password is \"%s\".",config.password); debug(2,"ignore_volume_contorl is %d.",config.ignore_volume_control); - debug(2,"dac desired buffer length is %d.",config.dac_buffer_queue_desired_length); + debug(2,"dac desired buffer length is %d.",config.backend_buffer_desired_length); #ifdef CONFIG_METADATA debug(2,"metdata enabled is %d.",config.metadata_enabled); debug(2,"metadata pipename is \"%s\".",config.metadata_pipename); @@ -606,7 +606,7 @@ int main(int argc, char **argv) { config.apname = malloc(20 + 100); snprintf(config.apname, 20 + 100, "Shairport Sync on %s", hostname); set_requested_connection_state_to_output(1); // we expect to be able to connect to the output device - config.dac_buffer_queue_desired_length = 6615; // 0.15 seconds. + config.backend_buffer_desired_length = 6615; // 0.15 seconds. // this is a bit weird, but apparently necessary char* basec = strdup(argv[0]); From 181d6d4395b7f5dcc3da3aba64077bf260590482 Mon Sep 17 00:00:00 2001 From: Mke Brady Date: Mon, 1 Jun 2015 21:31:41 +0100 Subject: [PATCH 065/220] Remove one parameter from the init function of audio back ends. --- audio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio.h b/audio.h index 9d9ac05a..8990b4f7 100644 --- a/audio.h +++ b/audio.h @@ -19,7 +19,7 @@ typedef struct { char *name; // start of program - int (*init)(int argc, char **argv, config_t* cfgp); + int (*init)(int argc, char **argv); // at end of program void (*deinit)(void); From 5395fbe00cc2f32461f8af9b0c1af7518a4071d9 Mon Sep 17 00:00:00 2001 From: Mke Brady Date: Mon, 1 Jun 2015 21:32:55 +0100 Subject: [PATCH 066/220] Add an extra parameter -- audio backend latency offset to allow for delays in audio back ends --- common.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common.h b/common.h index ed4b8cab..710eb0bc 100644 --- a/common.h +++ b/common.h @@ -41,7 +41,7 @@ enum stuffing_type { typedef struct { - config_t cfg; + config_t* cfg; char *password; char *apname; #ifdef CONFIG_METADATA @@ -76,7 +76,8 @@ typedef struct { char *logfile; char *errfile; char *configfile; - uint backend_buffer_desired_length; // this will be the desired number of frames in the audio backend buffer -- the DAC buffer for ALSA + uint audio_backend_buffer_desired_length; // this will be the desired number of frames in the audio backend buffer -- the DAC buffer for ALSA + uint audio_backend_latency_offset; // this will be the offset to compensate for any fixed latency there might be in the audio } shairport_cfg; //true if Shairport Sync is supposed to be sending output to the output device, false otherwise @@ -113,6 +114,7 @@ shairport_cfg config; char sender_name[1024]; char sender_ip[1024]; char album_name[1024]; // we might need this for picture diagnostics +config_t config_file_stuff; void command_start(void); void command_stop(void); From 0be43c269000783fc62ace2ef657cbf94bfcfa83 Mon Sep 17 00:00:00 2001 From: Mke Brady Date: Mon, 1 Jun 2015 21:33:56 +0100 Subject: [PATCH 067/220] Move backend settings to backend stanzas --- scripts/shairport-sync.conf | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index e5c35a9d..f8f91775 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -15,9 +15,6 @@ general = // resync_threshold = 2205; // a synchronisation error greater than this will cause resynchronisation; 0 disables it // log_verbosity = 0; // "0" means no verbosity, "3" is highest. // ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. - -// This may well change -- move it to the backend's settings... -// audio_backend_buffer_desired_length = 6615; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. }; latencies = @@ -51,6 +48,8 @@ alsa = // mixer_type = "software"; // "software" or "hardware" // mixer_device = "default"; //actually, the mixer default is whatever the output_device is. Normally you wouldn't have to use this. // mixer_control_name = "PCM"; // the name of the mixer to use -- there is no default. +// audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. +// audio_backend_buffer_desired_length = 6615; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. }; // These are parameters for the pipe back end, an experimental back end that directs output to a pipe. From 8739c9a1509561e3aa29a5a1169e10dcb5c68d47 Mon Sep 17 00:00:00 2001 From: Mke Brady Date: Mon, 1 Jun 2015 21:35:07 +0100 Subject: [PATCH 068/220] Add support for new config option and for modified config data structure. --- shairport.c | 82 ++++++++++++++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/shairport.c b/shairport.c index 472f7d30..cf8ce069 100644 --- a/shairport.c +++ b/shairport.c @@ -311,19 +311,21 @@ int parse_options(int argc, char **argv) { const char *str; int value; - config_init(&config.cfg); + config_init(&config_file_stuff); char *cfp = configuration_file_path; // use the configuration file path given in the -c option, if any if (config.configfile) cfp = config.configfile; /* Read the file. If there is an error, report it and exit. */ - if(config_read_file(&config.cfg,cfp)) { + if(config_read_file(&config_file_stuff,cfp)) { + // make config.cfg point to it + config.cfg = &config_file_stuff; /* Get the Service Name. */ - if(config_lookup_string(&config.cfg, "general.name", &str)) + if(config_lookup_string(config.cfg, "general.name", &str)) config.apname=(char *)str; /* Get the Daemonize setting. */ - if(config_lookup_string(&config.cfg, "general.daemonize", &str)) { + if(config_lookup_string(config.cfg, "general.daemonize", &str)) { if (strcasecmp(str,"no")==0) config.daemonise=0; else if (strcasecmp(str,"yes")==0) @@ -333,15 +335,15 @@ int parse_options(int argc, char **argv) { } /* Get the mdns_backend setting. */ - if(config_lookup_string(&config.cfg, "general.mdns_backend", &str)) + 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)) + 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(config_lookup_int(config.cfg, "general.port", &value)) { if ((value<0) || (value>65535)) die("Invalid port number \"%sd\". It should be between 0 and 65535, default is 5000",value); else @@ -349,10 +351,10 @@ int parse_options(int argc, char **argv) { } /* Get the password setting. */ - if(config_lookup_string(&config.cfg, "general.password", &str)) + if(config_lookup_string(config.cfg, "general.password", &str)) config.password=(char *)str; - if(config_lookup_string(&config.cfg, "general.interpolation", &str)) { + if(config_lookup_string(config.cfg, "general.interpolation", &str)) { if (strcasecmp(str,"basic")==0) config.packet_stuffing=ST_basic; else if (strcasecmp(str,"soxr")==0) @@ -362,7 +364,7 @@ int parse_options(int argc, char **argv) { } /* Get the statistics setting. */ - if(config_lookup_string(&config.cfg, "general.statistics", &str)) { + if(config_lookup_string(config.cfg, "general.statistics", &str)) { if (strcasecmp(str,"no")==0) config.statistics_requested=0; else if (strcasecmp(str,"yes")==0) @@ -372,22 +374,22 @@ int parse_options(int argc, char **argv) { } /* Get the drift tolerance setting. */ - if(config_lookup_int(&config.cfg, "general.drift", &value)) + if(config_lookup_int(config.cfg, "general.drift", &value)) config.tolerance=value; /* Get the resync setting. */ - if(config_lookup_int(&config.cfg, "general.resync_threshold", &value)) + if(config_lookup_int(config.cfg, "general.resync_threshold", &value)) config.resyncthreshold=value; /* Get the verbosity setting. */ - if(config_lookup_int(&config.cfg, "general.log_verbosity", &value)) + if(config_lookup_int(config.cfg, "general.log_verbosity", &value)) 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 ignore_volume_control setting. */ - if(config_lookup_string(&config.cfg, "general.ignore_volume_control", &str)) { + 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) @@ -396,33 +398,25 @@ int parse_options(int argc, char **argv) { die("Invalid ignore_volume_control option choice \"%s\". It should be \"yes\" or \"no\""); } - /* Get the audio backend's desired buffer size setting. */ - if(config_lookup_int(&config.cfg, "general.audio_backend_buffer_desired_length", &value)) { - if ((value<0) || (value>66150)) - die("Invalid audio backend buffer desired length \"%sd\". It should be between 0 and 66150, default is 6615",value); - else - config.backend_buffer_desired_length=value; - } - /* Get the default latency. */ - if(config_lookup_int(&config.cfg, "latencies.default", &value)) + if(config_lookup_int(config.cfg, "latencies.default", &value)) config.latency=value; /* Get the itunes latency. */ - if(config_lookup_int(&config.cfg, "latencies.itunes", &value)) + if(config_lookup_int(config.cfg, "latencies.itunes", &value)) config.iTunesLatency=value; /* Get the AirPlay latency. */ - if(config_lookup_int(&config.cfg, "latencies.airplay", &value)) + if(config_lookup_int(config.cfg, "latencies.airplay", &value)) config.AirPlayLatency=value; /* Get the forkedDaapd latency. */ - if(config_lookup_int(&config.cfg, "latencies.forkedDaapd", &value)) + if(config_lookup_int(config.cfg, "latencies.forkedDaapd", &value)) config.ForkedDaapdLatency=value; #ifdef CONFIG_METADATA /* Get the metadata setting. */ - if(config_lookup_string(&config.cfg, "metadata.enabled", &str)) { + if(config_lookup_string(config.cfg, "metadata.enabled", &str)) { if (strcasecmp(str,"no")==0) config.metadata_enabled=0; else if (strcasecmp(str,"yes")==0) @@ -431,7 +425,7 @@ int parse_options(int argc, char **argv) { die("Invalid metadata enabled option choice \"%s\". It should be \"yes\" or \"no\""); } - if(config_lookup_string(&config.cfg, "metadata.include_cover_art", &str)) { + if(config_lookup_string(config.cfg, "metadata.include_cover_art", &str)) { if (strcasecmp(str,"no")==0) config.get_coverart=0; else if (strcasecmp(str,"yes")==0) @@ -440,20 +434,20 @@ int parse_options(int argc, char **argv) { die("Invalid metadata include_cover_art option choice \"%s\". It should be \"yes\" or \"no\""); } - if(config_lookup_string(&config.cfg, "metadata.pipe_name", &str)) { + if(config_lookup_string(config.cfg, "metadata.pipe_name", &str)) { config.metadata_pipename=(char *)str; } #endif - if(config_lookup_string(&config.cfg, "sessioncontrol.run_this_before_play_begins", &str)) { + if(config_lookup_string(config.cfg, "sessioncontrol.run_this_before_play_begins", &str)) { config.cmd_start=(char *)str; } - if(config_lookup_string(&config.cfg, "sessioncontrol.run_this_after_play_ends", &str)) { + if(config_lookup_string(config.cfg, "sessioncontrol.run_this_after_play_ends", &str)) { config.cmd_stop=(char *)str; } - if(config_lookup_string(&config.cfg, "sessioncontrol.wait_for_completion", &str)) { + if(config_lookup_string(config.cfg, "sessioncontrol.wait_for_completion", &str)) { if (strcasecmp(str,"no")==0) config.cmd_blocking=0; else if (strcasecmp(str,"yes")==0) @@ -462,7 +456,7 @@ int parse_options(int argc, char **argv) { die("Invalid session control wait_for_completion option choice \"%s\". It should be \"yes\" or \"no\""); } - if(config_lookup_string(&config.cfg, "sessioncontrol.allow_session_interruption", &str)) { + if(config_lookup_string(config.cfg, "sessioncontrol.allow_session_interruption", &str)) { config.dont_check_timeout=0; // this is for legacy -- only set by -t 0 if (strcasecmp(str,"no")==0) config.allow_session_interruption=0; @@ -472,14 +466,18 @@ int parse_options(int argc, char **argv) { die("Invalid session control allow_interruption option choice \"%s\". It should be \"yes\" or \"no\""); } - if(config_lookup_int(&config.cfg, "sessioncontrol.session_timeout", &value)) { + if(config_lookup_int(config.cfg, "sessioncontrol.session_timeout", &value)) { config.timeout=value; config.dont_check_timeout=0; // this is for legacy -- only set by -t 0 } } else { - die("Line %d of the configuration file \"%s\":\n%s", - config_error_line(&config.cfg),config_error_file(&config.cfg),config_error_text(&config.cfg)); + if (config_error_type (&config_file_stuff)==CONFIG_ERR_FILE_IO) + debug(1,"Error reading configuration file \"%s\": \"%s\".",config_error_file(&config_file_stuff),config_error_text(&config_file_stuff)); + else { + die("Line %d of the configuration file \"%s\":\n%s", + config_error_line(&config_file_stuff),config_error_file(&config_file_stuff),config_error_text(&config_file_stuff)); + } } #endif @@ -505,8 +503,9 @@ int parse_options(int argc, char **argv) { debug(2,"busy timeout time is %d.",config.timeout); debug(2,"tolerance is %d frames.",config.tolerance); debug(2,"password is \"%s\".",config.password); - debug(2,"ignore_volume_contorl is %d.",config.ignore_volume_control); - debug(2,"dac desired buffer length is %d.",config.backend_buffer_desired_length); + debug(2,"ignore_volume_control is %d.",config.ignore_volume_control); + debug(2,"audio backend desired buffer length is %d.",config.audio_backend_buffer_desired_length); + debug(2,"audio backend latency offset is %d.",config.audio_backend_latency_offset); #ifdef CONFIG_METADATA debug(2,"metdata enabled is %d.",config.metadata_enabled); debug(2,"metadata pipename is \"%s\".",config.metadata_pipename); @@ -578,7 +577,8 @@ const char *pid_file_proc(void) { void exit_function() { #ifdef SUPPORT_CONFIG_FILES - config_destroy(&config.cfg); + if (config.cfg) + config_destroy(config.cfg); #endif } @@ -606,7 +606,7 @@ int main(int argc, char **argv) { config.apname = malloc(20 + 100); snprintf(config.apname, 20 + 100, "Shairport Sync on %s", hostname); set_requested_connection_state_to_output(1); // we expect to be able to connect to the output device - config.backend_buffer_desired_length = 6615; // 0.15 seconds. + config.audio_backend_buffer_desired_length = 6615; // 0.15 seconds. // this is a bit weird, but apparently necessary char* basec = strdup(argv[0]); @@ -763,7 +763,7 @@ int main(int argc, char **argv) { audio_ls_outputs(); die("Invalid audio output specified!"); } - config.output->init(argc-audio_arg, argv+audio_arg, &config.cfg); + config.output->init(argc-audio_arg, argv+audio_arg); daemon_log(LOG_NOTICE, "startup"); From c56a9ee26e08307d96c5cb817b4204b49a8d18de Mon Sep 17 00:00:00 2001 From: Mke Brady Date: Mon, 1 Jun 2015 21:36:18 +0100 Subject: [PATCH 069/220] Add support for settable buffer length and latency offset. --- audio_alsa.c | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 90682d12..257497f4 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -35,7 +35,7 @@ #include "audio.h" static void help(void); -static int init(int argc, char **argv, config_t* cfgp); +static int init(int argc, char **argv); static void deinit(void); static void start(int sample_rate); static void play(short buf[], int samples); @@ -93,22 +93,41 @@ static void help(void) { ); } -static int init(int argc, char **argv, config_t *cfgp) { +static int init(int argc, char **argv) { const char *str; int value; int hardware_mixer = 0; + + config.audio_backend_buffer_desired_length = 6615; // this is the default for ALSA + config.audio_backend_latency_offset = 0; // this is the default for ALSA // get settings from settings file first, allow them to be over-ridden by command line options - if (cfgp!=NULL) { - /* Get the Output Device Name. */ - if(config_lookup_string(cfgp, "alsa.output_device", &str)) { + if (config.cfg!=NULL) { + /* Get the desired buffer size setting. */ + if(config_lookup_int(config.cfg, "alsa.audio_backend_buffer_desired_length", &value)) { + if ((value<0) || (value>66150)) + die("Invalid alsa audio backend buffer desired length \"%sd\". It should be between 0 and 66150, default is 6615",value); + else + config.audio_backend_buffer_desired_length=value; + } + + /* Get the latency offset. */ + if(config_lookup_int(config.cfg, "alsa.audio_backend_latency_offset", &value)) { + if ((value<-22050) || (value>22050)) + die("Invalid alsa audio backend buffer latency offset \"%sd\". It should be between -22050 and +22050, default is 0",value); + else + config.audio_backend_latency_offset=value; + } + + /* Get the Output Device Name. */ + if(config_lookup_string(config.cfg, "alsa.output_device", &str)) { alsa_out_dev = (char*)str; } /* Get the Mixer Type setting. */ - if(config_lookup_string(cfgp, "alsa.mixer_type", &str)) { + if(config_lookup_string(config.cfg, "alsa.mixer_type", &str)) { if (strcasecmp(str,"software")==0) hardware_mixer=0; else if (strcasecmp(str,"hardware")==0) @@ -118,12 +137,12 @@ static int init(int argc, char **argv, config_t *cfgp) { } /* Get the Mixer Device Name. */ - if(config_lookup_string(cfgp, "alsa.mixer_device", &str)) { + if(config_lookup_string(config.cfg, "alsa.mixer_device", &str)) { alsa_mix_dev = (char*)str; } /* Get the Mixer Control Name. */ - if(config_lookup_string(cfgp, "alsa.mixer_control_name", &str)) { + if(config_lookup_string(config.cfg, "alsa.mixer_control_name", &str)) { alsa_mix_ctrl = (char*)str; } From e91cddabed42a24090b7808bd1d2ec2531831c6a Mon Sep 17 00:00:00 2001 From: Mke Brady Date: Mon, 1 Jun 2015 21:37:41 +0100 Subject: [PATCH 070/220] Add support for latency offset and buffer length. Update comments. --- player.c | 58 +++++++++++++++++++++++++++----------------------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/player.c b/player.c index f352938a..9a8d7c73 100644 --- a/player.c +++ b/player.c @@ -485,16 +485,6 @@ static abuf_t *buffer_get_frame(void) { } if (curframe->ready) { -/* - // This is broken -- it causes infinite loops. It's in the wrong place, and anyway isn't used much. - if ((flush_rtp_timestamp) && (flush_rtp_timestamp>=curframe->timestamp)) { - debug(1,"Dropping flushed packet seqno %u, timestamp %u",curframe->sequence_number,curframe->timestamp); - curframe->ready=0; - if (flush_rtp_timestamp==curframe->timestamp) // if we are finished... - flush_rtp_timestamp=0; - ab_read==SUCCESSOR(ab_read); - } else -*/ if (ab_buffering) { // if we are getting packets but not yet forwarding them to the player if (first_packet_timestamp==0) { // if this is the very first packet // debug(1,"First frame seen, time %u, with %d frames...",curframe->timestamp,seq_diff(ab_read, ab_write)); @@ -505,23 +495,25 @@ static abuf_t *buffer_get_frame(void) { // debug(1,"First frame seen with timestamp..."); first_packet_timestamp=curframe->timestamp; // we will keep buffering until we are supposed to start playing this - // Here, see if we should start playing. We need to know when to allow the packets to be sent to the player. - // We will be getting a timing fix every second or so from the source, which will be stored as a pair consisting of - - // i.e. the local time that corresponds to a specific packet timestamp. - // It might not be the timestamp of our first packet, however, so we might have to do some calculations. - // Also, we have to allow for the desired latency. + // Here, calculate when we should start playing. We need to know when to allow the packets to be sent to the player. + // We will send packets of silence from now until that time and then we will send the first packet, + // which will be followed by the subsequent packets. + + // we will get a fix every second or so, which will be stored as a pair consisting of + // the time when the packet with a particular timestamp should be played, neglecting latencies, etc. + + // It probably won't be the timestamp of our first packet, however, so we might have to do some calculations. + + // To calculate when the first packet will be played, we figure out the exact time the packet should + // be played according to its timestamp and the reference time. We then need to add the desired latency, typically 88200 frames. + + // Then we need to offset this by the backend latency offset. For example, if we knew that the audio back end has a latency of 100 ms, we would + // ask for the first packet to be emitted 100 ms earlier than it should, i.e. -4410 frames, so that when it got through the audio back end, + // if would be in sync. To do this, we would give it a latency offset of -100 ms, i.e. -4410 frames. int64_t delta = ((int64_t)first_packet_timestamp-(int64_t)reference_timestamp); - // Now, if the back end can tell us about its latency, we will set the first packet's time to play to be the exact time it's supposed to play plus the desired latency. - // Otherwise we will set it to the exact time plus desired latency less the config.backend_buffer_desired_length, a proxy for the lead time we want the stream to appear at the output. - - // We may change this in the future to pick up the lead time from the back end itself - - if (config.output->delay) - first_packet_time_to_play = reference_timestamp_time+((delta+(int64_t)config.latency)<<32)/44100; // using the latency requested... - else - first_packet_time_to_play = reference_timestamp_time+((delta+(int64_t)config.latency-(int64_t)config.backend_buffer_desired_length)<<32)/44100; // using the latency requested... + first_packet_time_to_play = reference_timestamp_time+((delta+(int64_t)config.latency+(int64_t)config.audio_backend_latency_offset)<<32)/44100; if (local_time_now>=first_packet_time_to_play) { debug(1,"First packet is late! It should have played before now. Flushing 0.1 seconds"); @@ -533,7 +525,7 @@ static abuf_t *buffer_get_frame(void) { if (first_packet_time_to_play!=0) { uint32_t filler_size = frame_size; - uint32_t max_dac_delay = config.backend_buffer_desired_length; + uint32_t max_dac_delay = 4410; // if (dac_delay==0) // i.e. if this is the first fill filler_size = 4410; // 0.1 second -- the maximum we'll add to the DAC @@ -586,27 +578,34 @@ static abuf_t *buffer_get_frame(void) { // Here, we work out whether to release a packet or wait // We release a buffer when the time is right. + + // To work out when the time is right, we need to take account of (1) the actual time the packet should be released, + // (2) the latency requested, (3) the audio backend latency offset and (4) the desired length of the audio backend's buffer + + // The time is right if the current time is later or the same as + // The packet time + (latency + latency offset - backend_buffer_length). + // Note: the last three items are expressed in frames and must be converted to time. int do_wait = 1; - if ((curframe) && (curframe->ready) && (curframe->timestamp)) { + if ((ab_synced) && (curframe) && (curframe->ready) && (curframe->timestamp)) { uint32_t reference_timestamp; uint64_t reference_timestamp_time; get_reference_timestamp_stuff(&reference_timestamp,&reference_timestamp_time); if (reference_timestamp) { // if we have a reference time uint32_t packet_timestamp=curframe->timestamp; int64_t delta = ((int64_t)packet_timestamp-(int64_t)reference_timestamp); - int64_t offset = (int64_t)config.latency-(int64_t)config.backend_buffer_desired_length; + int64_t offset = (int64_t)config.latency+config.audio_backend_latency_offset-(int64_t)config.audio_backend_buffer_desired_length; int64_t net_offset = delta+offset; int64_t time_to_play = reference_timestamp_time; int64_t net_offset_fp_sec; if (net_offset>=0) { net_offset_fp_sec = (net_offset<<32)/44100; time_to_play+=net_offset_fp_sec; // using the latency requested... - debug(2,"Net Offset: %lld, adjusted: %lld.",net_offset,net_offset_fp_sec); + // debug(2,"Net Offset: %lld, adjusted: %lld.",net_offset,net_offset_fp_sec); } else { net_offset_fp_sec = ((-net_offset)<<32)/44100; time_to_play-=net_offset_fp_sec; - debug(2,"Net Offset: %lld, adjusted: -%lld.",net_offset,net_offset_fp_sec); + // debug(2,"Net Offset: %lld, adjusted: -%lld.",net_offset,net_offset_fp_sec); } if (local_time_now>=time_to_play) { @@ -845,7 +844,6 @@ static void *player_thread_func(void *arg) { outbuf = malloc(OUTFRAME_BYTES(frame_size)); silence = malloc(OUTFRAME_BYTES(frame_size)); memset(silence, 0, OUTFRAME_BYTES(frame_size)); - late_packet_message_sent=0; missing_packets=late_packets=too_late_packets=resend_requests=0; flush_rtp_timestamp=0; // it seems this number has a special significance -- it seems to be used as a null operand, so we'll use it like that too From 54250f7552911aa92af1477531dfc8bd53437df6 Mon Sep 17 00:00:00 2001 From: Mke Brady Date: Mon, 1 Jun 2015 21:38:56 +0100 Subject: [PATCH 071/220] Simplify init function, add defaults for latency offset and buffer size. Remove redundant timer stuff from audio_dummy.c. --- audio_ao.c | 6 +++++- audio_dummy.c | 20 +++----------------- audio_pipe.c | 8 +++++--- audio_pulse.c | 6 +++++- audio_sndio.c | 5 ++++- 5 files changed, 22 insertions(+), 23 deletions(-) diff --git a/audio_ao.c b/audio_ao.c index 9b7b5d36..e14b13c5 100644 --- a/audio_ao.c +++ b/audio_ao.c @@ -42,7 +42,7 @@ static void help(void) { ); } -static int init(int argc, char **argv, config_t *cfgp) { +static int init(int argc, char **argv) { ao_initialize(); int driver = ao_default_driver_id(); ao_option *ao_opts = NULL; @@ -50,6 +50,10 @@ static int init(int argc, char **argv, config_t *cfgp) { optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour argv--; // so we shift the arguments to satisfy getopt() argc++; + + config.audio_backend_buffer_desired_length = 44100; // one second. + config.audio_backend_latency_offset = 0; + // some platforms apparently require optreset = 1; - which? int opt; char *mid; diff --git a/audio_dummy.c b/audio_dummy.c index d7531a0b..2fd8ebf3 100644 --- a/audio_dummy.c +++ b/audio_dummy.c @@ -32,7 +32,7 @@ int Fs; long long starttime, samples_played; -static int init(int argc, char **argv, config_t *cfgp) { +static int init(int argc, char **argv) { return 0; } @@ -43,28 +43,14 @@ static void start(int sample_rate) { Fs = sample_rate; starttime = 0; samples_played = 0; - printf("dummy audio output started at Fs=%d Hz\n", sample_rate); + debug(1,"dummy audio output started at Fs=%d Hz\n", sample_rate); } static void play(short buf[], int samples) { - struct timeval tv; - - // this is all a bit expensive but it's long-term stable. - gettimeofday(&tv, NULL); - - long long nowtime = tv.tv_usec + 1e6*tv.tv_sec; - - if (!starttime) - starttime = nowtime; - - samples_played += samples; - - long long finishtime = starttime + samples_played * 1e6 / Fs; - usleep(finishtime - nowtime); } static void stop(void) { - printf("dummy audio stopped\n"); + debug(1,"dummy audio stopped\n"); } static void help(void) { diff --git a/audio_pipe.c b/audio_pipe.c index b218d787..00921e86 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -54,12 +54,14 @@ static void stop(void) { close(fd); } -static int init(int argc, char **argv, config_t *cfgp) { +static int init(int argc, char **argv) { + config.audio_backend_buffer_desired_length = 44100; // one second. + config.audio_backend_latency_offset = 0; - if (cfgp!=NULL) { + if (config.cfg!=NULL) { /* Get the Output Pipename. */ const char *str; - if(config_lookup_string(cfgp, "pipe.name", &str)) { + if(config_lookup_string(config.cfg, "pipe.name", &str)) { pipename = (char*)str; } } diff --git a/audio_pulse.c b/audio_pulse.c index 93b0fb0a..d76c1de5 100644 --- a/audio_pulse.c +++ b/audio_pulse.c @@ -58,8 +58,12 @@ static void help(void) { ); } -static int init(int argc, char **argv, config_t *cfgp) { +static int init(int argc, char **argv) { + pulse_options.apname = config.apname; + + config.audio_backend_buffer_desired_length = 44100; // one second. + config.audio_backend_latency_offset = 0; optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour argv--; // so we shift the arguments to satisfy getopt() diff --git a/audio_sndio.c b/audio_sndio.c index 2ece8439..65a42778 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -23,7 +23,7 @@ static struct sio_hdl *sio; static struct sio_par par; -static int init(int argc, char **argv, config_t *cfgp) { +static int init(int argc, char **argv) { sio = sio_open(SIO_DEVANY, SIO_PLAY, 0); if (!sio) die("sndio: cannot connect to sound server"); @@ -40,6 +40,9 @@ static int init(int argc, char **argv, config_t *cfgp) { die("sndio: failed to set audio parameters"); if (!sio_getpar(sio, &par)) die("sndio: failed to get audio parameters"); + + config.audio_backend_buffer_desired_length = 44100; // one second. + config.audio_backend_latency_offset = 0; return 0; } From 04e27cfe4f37f411612e44a7af939ec5848f35d6 Mon Sep 17 00:00:00 2001 From: Mke Brady Date: Mon, 1 Jun 2015 22:08:31 +0100 Subject: [PATCH 072/220] Reduce the number of times the player thread asks for the delay in the audio back end -- halve it, mostly. --- player.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/player.c b/player.c index 9a8d7c73..a7e01c92 100644 --- a/player.c +++ b/player.c @@ -474,15 +474,7 @@ static abuf_t *buffer_get_frame(void) { flush_limit=0; } - dac_delay = 0; curframe = audio_buffer + BUFIDX(ab_read); - if (config.output->delay) { - dac_delay = config.output->delay(); - if (dac_delay==-1) { - debug(1,"Error getting dac_delay at start of loop."); - dac_delay=0; - } - } if (curframe->ready) { if (ab_buffering) { // if we are getting packets but not yet forwarding them to the player @@ -526,8 +518,7 @@ static abuf_t *buffer_get_frame(void) { uint32_t filler_size = frame_size; uint32_t max_dac_delay = 4410; - // if (dac_delay==0) // i.e. if this is the first fill - filler_size = 4410; // 0.1 second -- the maximum we'll add to the DAC + filler_size = 4410; // 0.1 second -- the maximum we'll add to the DAC if (local_time_now>=first_packet_time_to_play) { // we've gone past the time... @@ -539,6 +530,14 @@ static abuf_t *buffer_get_frame(void) { first_packet_timestamp = 0; first_packet_time_to_play = 0; } else { + if (config.output->delay) { + dac_delay = config.output->delay(); + if (dac_delay==-1) { + debug(1,"Error getting dac_delay in buffer_get_frame."); + dac_delay=0; + } + } else + dac_delay=0; uint64_t gross_frame_gap = ((first_packet_time_to_play-local_time_now)*44100)>>32; int64_t exact_frame_gap = gross_frame_gap-dac_delay; if (exact_frame_gap<=0) { From 87a0475c16a8203c9029a34e60cec00e23a86ab7 Mon Sep 17 00:00:00 2001 From: Mke Brady Date: Mon, 1 Jun 2015 23:16:50 +0100 Subject: [PATCH 073/220] Reformat using clang-format. --- audio.c | 40 +- audio.h | 48 +- audio_alsa.c | 355 +++---- audio_ao.c | 151 ++- audio_dummy.c | 50 +- audio_pipe.c | 83 +- audio_pulse.c | 149 ++- audio_sndio.c | 80 +- common.c | 585 ++++++------ common.h | 105 ++- mdns.c | 101 +- mdns.h | 18 +- mdns_avahi.c | 175 ++-- mdns_dns_sd.c | 105 +-- mdns_external.c | 197 ++-- mdns_tinysvcmdns.c | 194 ++-- player.c | 1099 ++++++++++++---------- player.h | 6 +- rtp.c | 906 +++++++++--------- rtp.h | 7 +- rtsp.c | 2029 ++++++++++++++++++++-------------------- rtsp.h | 2 +- shairport.c | 1150 ++++++++++++----------- tinysvcmdns.c | 2211 ++++++++++++++++++++++---------------------- tinysvcmdns.h | 142 ++- 25 files changed, 4987 insertions(+), 5001 deletions(-) diff --git a/audio.c b/audio.c index 95bedc2c..9887531f 100644 --- a/audio.c +++ b/audio.c @@ -56,36 +56,32 @@ static audio_output *outputs[] = { #ifdef CONFIG_AO &audio_ao, #endif - &audio_dummy, - &audio_pipe, - NULL -}; - + &audio_dummy, &audio_pipe, NULL}; audio_output *audio_get_output(char *name) { - audio_output **out; + audio_output **out; - // default to the first - if (!name) - return outputs[0]; + // default to the first + if (!name) + return outputs[0]; - for (out=outputs; *out; out++) - if (!strcasecmp(name, (*out)->name)) - return *out; + for (out = outputs; *out; out++) + if (!strcasecmp(name, (*out)->name)) + return *out; - return NULL; + return NULL; } void audio_ls_outputs(void) { - audio_output **out; + audio_output **out; - printf("Available audio outputs:\n"); - for (out=outputs; *out; out++) - printf(" %s%s\n", (*out)->name, out==outputs ? " (default)" : ""); + printf("Available audio outputs:\n"); + for (out = outputs; *out; out++) + printf(" %s%s\n", (*out)->name, out == outputs ? " (default)" : ""); - for (out=outputs; *out; out++) { - printf("\n"); - printf("Options for output %s:\n", (*out)->name); - (*out)->help(); - } + for (out = outputs; *out; out++) { + printf("\n"); + printf("Options for output %s:\n", (*out)->name); + (*out)->help(); + } } diff --git a/audio.h b/audio.h index 8990b4f7..4738a071 100644 --- a/audio.h +++ b/audio.h @@ -15,33 +15,33 @@ typedef struct { } audio_parameters; typedef struct { - void (*help)(void); - char *name; + void (*help)(void); + char *name; - // start of program - int (*init)(int argc, char **argv); - // at end of program - void (*deinit)(void); + // start of program + int (*init)(int argc, char **argv); + // at end of program + void (*deinit)(void); - void (*start)(int sample_rate); - - // block of samples - void (*play)(short buf[], int samples); - void (*stop)(void); - - // may be null if not implemented - void (*flush)(void); - - // returns the delay before the next frame to be sent to the device would actually be audible. - // almost certainly wrong if the buffer is empty, so put silent buffers into it to make it busy. - // will change dynamically, so keep watching it. Implemented in ALSA only. - uint32_t (*delay)(); + void (*start)(int sample_rate); - // may be NULL, in which case soft volume is applied - void (*volume)(double vol); - - // may be NULL, in which case soft volume parameters are used - void (*parameters)(audio_parameters* info); + // block of samples + void (*play)(short buf[], int samples); + void (*stop)(void); + + // may be null if not implemented + void (*flush)(void); + + // returns the delay before the next frame to be sent to the device would actually be audible. + // almost certainly wrong if the buffer is empty, so put silent buffers into it to make it busy. + // will change dynamically, so keep watching it. Implemented in ALSA only. + uint32_t (*delay)(); + + // may be NULL, in which case soft volume is applied + void (*volume)(double vol); + + // may be NULL, in which case soft volume parameters are used + void (*parameters)(audio_parameters *info); } audio_output; audio_output *audio_get_output(char *name); diff --git a/audio_alsa.c b/audio_alsa.c index 257497f4..02af8d86 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -43,9 +43,9 @@ static void stop(void); static void flush(void); static uint32_t delay(void); static void volume(double vol); -static void parameters(audio_parameters* info); -static int has_mute=0; -static int has_db_vol=0; +static void parameters(audio_parameters *info); +static int has_mute = 0; +static int has_db_vol = 0; static double set_volume; audio_output audio_alsa = { @@ -58,7 +58,7 @@ audio_output audio_alsa = { .flush = &flush, .delay = &delay, .play = &play, - .volume = NULL, // to be set later on... + .volume = NULL, // to be set later on... .parameters = NULL // to be set later on... }; @@ -81,16 +81,15 @@ static char *alsa_mix_ctrl = "Master"; static int alsa_mix_index = 0; static int play_number; -static int64_t accumulated_delay,accumulated_da_delay; +static int64_t accumulated_delay, accumulated_da_delay; static void help(void) { - printf(" -d output-device set the output device [default*|...]\n" - " -t mixer-type set the mixer type [software*|hardware]\n" - " -m mixer-device set the mixer device ['output-device'*|...]\n" - " -c mixer-control set the mixer control [Master*|...]\n" - " -i mixer-index set the mixer index [0*|...]\n" - " *) default option\n" - ); + printf(" -d output-device set the output device [default*|...]\n" + " -t mixer-type set the mixer type [software*|hardware]\n" + " -m mixer-device set the mixer device ['output-device'*|...]\n" + " -c mixer-control set the mixer control [Master*|...]\n" + " -i mixer-index set the mixer index [0*|...]\n" + " *) default option\n"); } static int init(int argc, char **argv) { @@ -100,52 +99,55 @@ static int init(int argc, char **argv) { int hardware_mixer = 0; config.audio_backend_buffer_desired_length = 6615; // this is the default for ALSA - config.audio_backend_latency_offset = 0; // this is the default for ALSA - + config.audio_backend_latency_offset = 0; // this is the default for ALSA + // get settings from settings file first, allow them to be over-ridden by command line options - - if (config.cfg!=NULL) { - /* Get the desired buffer size setting. */ - if(config_lookup_int(config.cfg, "alsa.audio_backend_buffer_desired_length", &value)) { - if ((value<0) || (value>66150)) - die("Invalid alsa audio backend buffer desired length \"%sd\". It should be between 0 and 66150, default is 6615",value); - else - config.audio_backend_buffer_desired_length=value; - } - - /* Get the latency offset. */ - if(config_lookup_int(config.cfg, "alsa.audio_backend_latency_offset", &value)) { - if ((value<-22050) || (value>22050)) - die("Invalid alsa audio backend buffer latency offset \"%sd\". It should be between -22050 and +22050, default is 0",value); - else - config.audio_backend_latency_offset=value; - } - /* Get the Output Device Name. */ - if(config_lookup_string(config.cfg, "alsa.output_device", &str)) { - alsa_out_dev = (char*)str; - } - - /* Get the Mixer Type setting. */ - if(config_lookup_string(config.cfg, "alsa.mixer_type", &str)) { - if (strcasecmp(str,"software")==0) - hardware_mixer=0; - else if (strcasecmp(str,"hardware")==0) - hardware_mixer=1; - else - die("Invalid alsa mixer option choice \"%s\". It should be \"software\" or \"hardware\""); - } + if (config.cfg != NULL) { + /* Get the desired buffer size setting. */ + if (config_lookup_int(config.cfg, "alsa.audio_backend_buffer_desired_length", &value)) { + if ((value < 0) || (value > 66150)) + die("Invalid alsa audio backend buffer desired length \"%sd\". It should be between 0 and " + "66150, default is 6615", + value); + else + config.audio_backend_buffer_desired_length = value; + } - /* Get the Mixer Device Name. */ - if(config_lookup_string(config.cfg, "alsa.mixer_device", &str)) { - alsa_mix_dev = (char*)str; - } - - /* Get the Mixer Control Name. */ - if(config_lookup_string(config.cfg, "alsa.mixer_control_name", &str)) { - alsa_mix_ctrl = (char*)str; - } + /* Get the latency offset. */ + if (config_lookup_int(config.cfg, "alsa.audio_backend_latency_offset", &value)) { + if ((value < -22050) || (value > 22050)) + die("Invalid alsa audio backend buffer latency offset \"%sd\". It should be between -22050 " + "and +22050, default is 0", + value); + else + config.audio_backend_latency_offset = value; + } + /* Get the Output Device Name. */ + if (config_lookup_string(config.cfg, "alsa.output_device", &str)) { + alsa_out_dev = (char *)str; + } + + /* Get the Mixer Type setting. */ + if (config_lookup_string(config.cfg, "alsa.mixer_type", &str)) { + if (strcasecmp(str, "software") == 0) + hardware_mixer = 0; + else if (strcasecmp(str, "hardware") == 0) + hardware_mixer = 1; + else + die("Invalid alsa mixer option choice \"%s\". It should be \"software\" or \"hardware\""); + } + + /* Get the Mixer Device Name. */ + if (config_lookup_string(config.cfg, "alsa.mixer_device", &str)) { + alsa_mix_dev = (char *)str; + } + + /* Get the Mixer Control Name. */ + if (config_lookup_string(config.cfg, "alsa.mixer_control_name", &str)) { + alsa_mix_ctrl = (char *)str; + } } optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour @@ -154,37 +156,37 @@ static int init(int argc, char **argv) { // some platforms apparently require optreset = 1; - which? int opt; while ((opt = getopt(argc, argv, "d:t:m:c:i:")) > 0) { - switch (opt) { - case 'd': - alsa_out_dev = optarg; - break; - case 't': - if (strcmp(optarg, "hardware") == 0) - hardware_mixer = 1; - break; - case 'm': - alsa_mix_dev = optarg; - break; - case 'c': - alsa_mix_ctrl = optarg; - break; - case 'i': - alsa_mix_index = strtol(optarg, NULL, 10); - break; - default: - help(); - die("Invalid audio option -%c specified", opt); - } + switch (opt) { + case 'd': + alsa_out_dev = optarg; + break; + case 't': + if (strcmp(optarg, "hardware") == 0) + hardware_mixer = 1; + break; + case 'm': + alsa_mix_dev = optarg; + break; + case 'c': + alsa_mix_ctrl = optarg; + break; + case 'i': + alsa_mix_index = strtol(optarg, NULL, 10); + break; + default: + help(); + die("Invalid audio option -%c specified", opt); + } } if (optind < argc) - die("Invalid audio argument: %s", argv[optind]); - + die("Invalid audio argument: %s", argv[optind]); + if (!hardware_mixer) - return 0; + return 0; if (alsa_mix_dev == NULL) - alsa_mix_dev = alsa_out_dev; + alsa_mix_dev = alsa_out_dev; int ret = 0; @@ -193,39 +195,41 @@ static int init(int argc, char **argv) { snd_mixer_selem_id_set_name(alsa_mix_sid, alsa_mix_ctrl); if ((snd_mixer_open(&alsa_mix_handle, 0)) < 0) - die ("Failed to open mixer"); + die("Failed to open mixer"); if ((snd_mixer_attach(alsa_mix_handle, alsa_mix_dev)) < 0) - die ("Failed to attach mixer"); + die("Failed to attach mixer"); if ((snd_mixer_selem_register(alsa_mix_handle, NULL, NULL)) < 0) - die ("Failed to register mixer element"); + die("Failed to register mixer element"); ret = snd_mixer_load(alsa_mix_handle); if (ret < 0) - die ("Failed to load mixer element"); + die("Failed to load mixer element"); alsa_mix_elem = snd_mixer_find_selem(alsa_mix_handle, alsa_mix_sid); if (!alsa_mix_elem) - die ("Failed to find mixer element"); - if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv)<0) - debug(1,"Can't read mixer's [linear] min and max volumes."); + die("Failed to find mixer element"); + if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) < 0) + debug(1, "Can't read mixer's [linear] min and max volumes."); else { - if ((snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem,alsa_mix_minv,&alsa_mix_mindb)==0) && - (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem,alsa_mix_maxv,&alsa_mix_maxdb)==0)) { - has_db_vol=1; + if ((snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv, &alsa_mix_mindb) == 0) && + (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_maxv, &alsa_mix_maxdb) == 0)) { + has_db_vol = 1; audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff audio_alsa.parameters = ¶meters; // likewise the parameters stuff - if (alsa_mix_mindb==-9999999) { + if (alsa_mix_mindb == -9999999) { // trying to say that the lowest vol is mute, maybe? Raspberry Pi does this - if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem,alsa_mix_minv+1,&alsa_mix_mindb)==0) - debug(1,"Can't get dB value corresponding to a \"volume\" of 1."); + if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1, + &alsa_mix_mindb) == 0) + debug(1, "Can't get dB value corresponding to a \"volume\" of 1."); } - debug(1,"Hardware mixer has dB volume from %f to %f.",(1.0*alsa_mix_mindb)/100.0,(1.0*alsa_mix_maxdb)/100.0); + debug(1, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0, + (1.0 * alsa_mix_maxdb) / 100.0); } else { - debug(1,"Hardware mixer does not have dB volume -- not used."); + debug(1, "Hardware mixer does not have dB volume -- not used."); } } if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) { - has_mute=1; - debug(1,"Has mute ability."); + has_mute = 1; + debug(1, "Has mute ability."); } return 0; } @@ -233,20 +237,20 @@ static int init(int argc, char **argv) { static void deinit(void) { stop(); if (alsa_mix_handle) { - snd_mixer_close(alsa_mix_handle); + snd_mixer_close(alsa_mix_handle); } } -int open_alsa_device(void) { +int open_alsa_device(void) { int ret, dir = 0; unsigned int my_sample_rate = desired_sample_rate; - snd_pcm_uframes_t frames = 441*10; - snd_pcm_uframes_t buffer_size = frames*4; + snd_pcm_uframes_t frames = 441 * 10; + snd_pcm_uframes_t buffer_size = frames * 4; ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, 0); if (ret < 0) - return(ret); - // die("Alsa initialization failed: unable to open pcm device: %s.", snd_strerror(ret)); + return (ret); + // die("Alsa initialization failed: unable to open pcm device: %s.", snd_strerror(ret)); snd_pcm_hw_params_alloca(&alsa_params); snd_pcm_hw_params_any(alsa_handle, alsa_params); @@ -260,75 +264,79 @@ int open_alsa_device(void) { if (ret < 0) { die("unable to set hw parameters: %s.", snd_strerror(ret)); } - if (my_sample_rate!=desired_sample_rate) { - die("Can't set the D/A converter to %d -- set to %d instead./n",desired_sample_rate,my_sample_rate); + if (my_sample_rate != desired_sample_rate) { + die("Can't set the D/A converter to %d -- set to %d instead./n", desired_sample_rate, + my_sample_rate); } - return(0); + return (0); } static void start(int sample_rate) { if (sample_rate != 44100) - die("Unexpected sample rate %d -- only 44,100 supported!",sample_rate); + die("Unexpected sample rate %d -- only 44,100 supported!", sample_rate); desired_sample_rate = sample_rate; // must be a variable } static uint32_t delay() { - if (alsa_handle==NULL) { - return 0; - } else { - pthread_mutex_lock(&alsa_mutex); - snd_pcm_sframes_t current_avail,current_delay = 0; - int derr,ignore; - if (snd_pcm_state(alsa_handle)==SND_PCM_STATE_RUNNING) { - derr = snd_pcm_avail_delay(alsa_handle,¤t_avail,¤t_delay); - // current_avail not used - if (derr != 0) { - ignore = snd_pcm_recover(alsa_handle, derr, 0); - debug(1,"Error %d in delay(): %s. Delay reported is %d frames.", derr, snd_strerror(derr),current_delay); - current_delay=-1; - } - } else if (snd_pcm_state(alsa_handle)==SND_PCM_STATE_PREPARED) { - current_delay=0; - } else { - if (snd_pcm_state(alsa_handle)==SND_PCM_STATE_XRUN) - current_delay=0; - else { - current_delay=-1; - debug(1,"Error -- ALSA delay(): bad state: %d.",snd_pcm_state(alsa_handle)); - } - if (derr = snd_pcm_prepare(alsa_handle)) { - ignore = snd_pcm_recover(alsa_handle, derr, 0); - debug(1,"Error preparing after delay error: %s.", snd_strerror(derr)); - current_delay = -1; - } - } - pthread_mutex_unlock(&alsa_mutex); - return current_delay; + if (alsa_handle == NULL) { + return 0; + } else { + pthread_mutex_lock(&alsa_mutex); + snd_pcm_sframes_t current_avail, current_delay = 0; + int derr, ignore; + if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING) { + derr = snd_pcm_avail_delay(alsa_handle, ¤t_avail, ¤t_delay); + // current_avail not used + if (derr != 0) { + ignore = snd_pcm_recover(alsa_handle, derr, 0); + debug(1, "Error %d in delay(): %s. Delay reported is %d frames.", derr, snd_strerror(derr), + current_delay); + current_delay = -1; + } + } else if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) { + current_delay = 0; + } else { + if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) + current_delay = 0; + else { + current_delay = -1; + debug(1, "Error -- ALSA delay(): bad state: %d.", snd_pcm_state(alsa_handle)); + } + if (derr = snd_pcm_prepare(alsa_handle)) { + ignore = snd_pcm_recover(alsa_handle, derr, 0); + debug(1, "Error preparing after delay error: %s.", snd_strerror(derr)); + current_delay = -1; + } + } + pthread_mutex_unlock(&alsa_mutex); + return current_delay; } } static void play(short buf[], int samples) { int ret = 0; - if (alsa_handle==NULL) { - ret = open_alsa_device(); - if ((ret==0) && (audio_alsa.volume)) - volume(set_volume); - } - if (ret==0) { + if (alsa_handle == NULL) { + ret = open_alsa_device(); + if ((ret == 0) && (audio_alsa.volume)) + volume(set_volume); + } + if (ret == 0) { pthread_mutex_lock(&alsa_mutex); snd_pcm_sframes_t current_delay = 0; - int err,ignore; - if ((snd_pcm_state(alsa_handle)==SND_PCM_STATE_PREPARED) || (snd_pcm_state(alsa_handle)==SND_PCM_STATE_RUNNING)) { - err = snd_pcm_writei(alsa_handle, (char*)buf, samples); + int err, ignore; + if ((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) || + (snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING)) { + err = snd_pcm_writei(alsa_handle, (char *)buf, samples); if (err < 0) { ignore = snd_pcm_recover(alsa_handle, err, 0); - debug(1,"Error %d writing %d samples in play() %s.",err,samples, snd_strerror(err)); + debug(1, "Error %d writing %d samples in play() %s.", err, samples, snd_strerror(err)); } } else { - debug(1,"Error -- ALSA device in incorrect state (%d) for play.",snd_pcm_state(alsa_handle)); + debug(1, "Error -- ALSA device in incorrect state (%d) for play.", + snd_pcm_state(alsa_handle)); if (err = snd_pcm_prepare(alsa_handle)) { ignore = snd_pcm_recover(alsa_handle, err, 0); - debug(1,"Error preparing after play error: %s.", snd_strerror(err)); + debug(1, "Error preparing after play error: %s.", snd_strerror(err)); } } pthread_mutex_unlock(&alsa_mutex); @@ -340,20 +348,21 @@ static void flush(void) { if (alsa_handle) { // debug(1,"Dropping frames for flush..."); if (derr = snd_pcm_drop(alsa_handle)) - debug(1,"Error dropping frames: %s.", snd_strerror(derr)); + debug(1, "Error dropping frames: %s.", snd_strerror(derr)); // debug(1,"Dropped frames ok. State is %d.",snd_pcm_state(alsa_handle)); if (derr = snd_pcm_prepare(alsa_handle)) - debug(1,"Error preparing after flush: %s.", snd_strerror(derr)); + debug(1, "Error preparing after flush: %s.", snd_strerror(derr)); // debug(1,"Frames successfully dropped."); /* if (snd_pcm_state(alsa_handle)==SND_PCM_STATE_PREPARED) - debug(1,"Flush returns to SND_PCM_STATE_PREPARED state."); + debug(1,"Flush returns to SND_PCM_STATE_PREPARED state."); if (snd_pcm_state(alsa_handle)==SND_PCM_STATE_RUNNING) debug(1,"Flush returns to SND_PCM_STATE_RUNNING state."); - */ - if (!((snd_pcm_state(alsa_handle)==SND_PCM_STATE_PREPARED) || (snd_pcm_state(alsa_handle)==SND_PCM_STATE_RUNNING))) - debug(1,"Flush returning unexpected state -- %d.",snd_pcm_state(alsa_handle)); - + */ + if (!((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) || + (snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING))) + debug(1, "Flush returning unexpected state -- %d.", snd_pcm_state(alsa_handle)); + // flush also closes the device snd_pcm_close(alsa_handle); alsa_handle = NULL; @@ -361,29 +370,29 @@ static void flush(void) { } static void stop(void) { - if (alsa_handle!=0) - // when we want to stop, we want the alsa device - // to be closed immediately -- we may even be killing the thread, so we don't wish to wait - // so we should flush first - flush(); // flush will also close the device - // close_alsa_device(); + if (alsa_handle != 0) + // when we want to stop, we want the alsa device + // to be closed immediately -- we may even be killing the thread, so we don't wish to wait + // so we should flush first + flush(); // flush will also close the device + // close_alsa_device(); } -static void parameters(audio_parameters* info) { - info->has_true_mute=has_mute; +static void parameters(audio_parameters *info) { + info->has_true_mute = has_mute; info->is_muted = ((has_mute) && (set_volume == -144.0)); - info->minimum_volume_dB=alsa_mix_mindb; - info->maximum_volume_dB=alsa_mix_maxdb; - info->airplay_volume=set_volume; - info->current_volume_dB=vol2attn(set_volume,alsa_mix_maxdb,alsa_mix_mindb); + info->minimum_volume_dB = alsa_mix_mindb; + info->maximum_volume_dB = alsa_mix_maxdb; + info->airplay_volume = set_volume; + info->current_volume_dB = vol2attn(set_volume, alsa_mix_maxdb, alsa_mix_mindb); } static void volume(double vol) { - set_volume=vol; - double vol_setting = vol2attn(vol,alsa_mix_maxdb,alsa_mix_mindb); + set_volume = vol; + double vol_setting = vol2attn(vol, alsa_mix_maxdb, alsa_mix_mindb); // debug(1,"Setting volume db to %f, for volume input of %f.",vol_setting/100,vol); if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol_setting, -1) != 0) - die ("Failed to set playback dB volume"); - if (has_mute) - snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, (vol!=-144.0)); + die("Failed to set playback dB volume"); + if (has_mute) + snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, (vol != -144.0)); } diff --git a/audio_ao.c b/audio_ao.c index e14b13c5..0acc8114 100644 --- a/audio_ao.c +++ b/audio_ao.c @@ -24,7 +24,6 @@ * OTHER DEALINGS IN THE SOFTWARE. */ - #include #include #include @@ -35,102 +34,96 @@ ao_device *dev = NULL; static void help(void) { - printf(" -d driver set the output driver\n" - " -o name=value set an arbitrary ao option\n" - " -i id shorthand for -o id=\n" - " -n name shorthand for -o dev= -o dsp=\n" - ); + printf(" -d driver set the output driver\n" + " -o name=value set an arbitrary ao option\n" + " -i id shorthand for -o id=\n" + " -n name shorthand for -o dev= -o dsp=\n"); } static int init(int argc, char **argv) { - ao_initialize(); - int driver = ao_default_driver_id(); - ao_option *ao_opts = NULL; + ao_initialize(); + int driver = ao_default_driver_id(); + ao_option *ao_opts = NULL; - optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour - argv--; // so we shift the arguments to satisfy getopt() - argc++; - - config.audio_backend_buffer_desired_length = 44100; // one second. - config.audio_backend_latency_offset = 0; + optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour + argv--; // so we shift the arguments to satisfy getopt() + argc++; - // some platforms apparently require optreset = 1; - which? - int opt; - char *mid; - while ((opt = getopt(argc, argv, "d:i:n:o:")) > 0) { - switch (opt) { - case 'd': - driver = ao_driver_id(optarg); - if (driver < 0) - die("could not find ao driver %s", optarg); - break; - case 'i': - ao_append_option(&ao_opts, "id", optarg); - break; - case 'n': - ao_append_option(&ao_opts, "dev", optarg); - // Old libao versions (for example, 0.8.8) only support - // "dsp" instead of "dev". - ao_append_option(&ao_opts, "dsp", optarg); - break; - case 'o': - mid = strchr(optarg, '='); - if (!mid) - die("Expected an = in audio option %s", optarg); - *mid = 0; - ao_append_option(&ao_opts, optarg, mid+1); - break; - default: - help(); - die("Invalid audio option -%c specified", opt); - } + config.audio_backend_buffer_desired_length = 44100; // one second. + config.audio_backend_latency_offset = 0; + + // some platforms apparently require optreset = 1; - which? + int opt; + char *mid; + while ((opt = getopt(argc, argv, "d:i:n:o:")) > 0) { + switch (opt) { + case 'd': + driver = ao_driver_id(optarg); + if (driver < 0) + die("could not find ao driver %s", optarg); + break; + case 'i': + ao_append_option(&ao_opts, "id", optarg); + break; + case 'n': + ao_append_option(&ao_opts, "dev", optarg); + // Old libao versions (for example, 0.8.8) only support + // "dsp" instead of "dev". + ao_append_option(&ao_opts, "dsp", optarg); + break; + case 'o': + mid = strchr(optarg, '='); + if (!mid) + die("Expected an = in audio option %s", optarg); + *mid = 0; + ao_append_option(&ao_opts, optarg, mid + 1); + break; + default: + help(); + die("Invalid audio option -%c specified", opt); } + } - if (optind < argc) - die("Invalid audio argument: %s", argv[optind]); + if (optind < argc) + die("Invalid audio argument: %s", argv[optind]); - ao_sample_format fmt; - memset(&fmt, 0, sizeof(fmt)); + ao_sample_format fmt; + memset(&fmt, 0, sizeof(fmt)); - fmt.bits = 16; - fmt.rate = 44100; - fmt.channels = 2; - fmt.byte_format = AO_FMT_NATIVE; + fmt.bits = 16; + fmt.rate = 44100; + fmt.channels = 2; + fmt.byte_format = AO_FMT_NATIVE; - dev = ao_open_live(driver, &fmt, ao_opts); + dev = ao_open_live(driver, &fmt, ao_opts); - return dev ? 0 : 1; + return dev ? 0 : 1; } static void deinit(void) { - if (dev) - ao_close(dev); - dev = NULL; - ao_shutdown(); + if (dev) + ao_close(dev); + dev = NULL; + ao_shutdown(); } static void start(int sample_rate) { - if (sample_rate != 44100) - die("unexpected sample rate!"); + if (sample_rate != 44100) + die("unexpected sample rate!"); } -static void play(short buf[], int samples) { - ao_play(dev, (char*)buf, samples*4); -} +static void play(short buf[], int samples) { ao_play(dev, (char *)buf, samples * 4); } -static void stop(void) { -} +static void stop(void) {} -audio_output audio_ao = { - .name = "ao", - .help = &help, - .init = &init, - .deinit = &deinit, - .start = &start, - .stop = &stop, - .flush = NULL, - .delay = NULL, - .play = &play, - .volume = NULL, - .parameters = NULL -}; +audio_output audio_ao = {.name = "ao", + .help = &help, + .init = &init, + .deinit = &deinit, + .start = &start, + .stop = &stop, + .flush = NULL, + .delay = NULL, + .play = &play, + .volume = NULL, + .parameters = NULL}; diff --git a/audio_dummy.c b/audio_dummy.c index 2fd8ebf3..57fbec17 100644 --- a/audio_dummy.c +++ b/audio_dummy.c @@ -32,41 +32,31 @@ int Fs; long long starttime, samples_played; -static int init(int argc, char **argv) { - return 0; -} +static int init(int argc, char **argv) { return 0; } -static void deinit(void) { -} +static void deinit(void) {} static void start(int sample_rate) { - Fs = sample_rate; - starttime = 0; - samples_played = 0; - debug(1,"dummy audio output started at Fs=%d Hz\n", sample_rate); + Fs = sample_rate; + starttime = 0; + samples_played = 0; + debug(1, "dummy audio output started at Fs=%d Hz\n", sample_rate); } -static void play(short buf[], int samples) { -} +static void play(short buf[], int samples) {} -static void stop(void) { - debug(1,"dummy audio stopped\n"); -} +static void stop(void) { debug(1, "dummy audio stopped\n"); } -static void help(void) { - printf(" There are no options for dummy audio.\n"); -} +static void help(void) { printf(" There are no options for dummy audio.\n"); } -audio_output audio_dummy = { - .name = "dummy", - .help = &help, - .init = &init, - .deinit = &deinit, - .start = &start, - .stop = &stop, - .flush = NULL, - .delay = NULL, - .play = &play, - .volume = NULL, - .parameters = NULL -}; +audio_output audio_dummy = {.name = "dummy", + .help = &help, + .init = &init, + .deinit = &deinit, + .start = &start, + .stop = &stop, + .flush = NULL, + .delay = NULL, + .play = &play, + .volume = NULL, + .parameters = NULL}; diff --git a/audio_pipe.c b/audio_pipe.c index 00921e86..12b33360 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -38,76 +38,69 @@ static int fd = -1; char *pipename = NULL; static void start(int sample_rate) { - debug(1,"Pipename to start is \"%s\"",pipename); - if (strcasecmp(pipename,"STDOUT")==0) + debug(1, "Pipename to start is \"%s\"", pipename); + if (strcasecmp(pipename, "STDOUT") == 0) fd = STDOUT_FILENO; else fd = open(pipename, O_WRONLY); } -static void play(short buf[], int samples) { - int ignore = write(fd, buf, samples*4); -} +static void play(short buf[], int samples) { int ignore = write(fd, buf, samples * 4); } static void stop(void) { - if (fd!=STDOUT_FILENO) + if (fd != STDOUT_FILENO) close(fd); } static int init(int argc, char **argv) { - config.audio_backend_buffer_desired_length = 44100; // one second. + config.audio_backend_buffer_desired_length = 44100; // one second. config.audio_backend_latency_offset = 0; - if (config.cfg!=NULL) { - /* Get the Output Pipename. */ - const char *str; - if(config_lookup_string(config.cfg, "pipe.name", &str)) { - pipename = (char*)str; - } + if (config.cfg != NULL) { + /* Get the Output Pipename. */ + const char *str; + if (config_lookup_string(config.cfg, "pipe.name", &str)) { + pipename = (char *)str; + } } + if ((pipename == NULL) && (argc != 1)) + die("bad or missing argument(s) to pipe"); - if ((pipename==NULL) && (argc != 1)) - die("bad or missing argument(s) to pipe"); + if (argc == 1) + pipename = strdup(argv[0]); - if (argc==1) - pipename = strdup(argv[0]); - - - // here, create the pipe - if (strcasecmp(pipename,"STDOUT")!=0) - if (mkfifo(pipename, 0644) && errno != EEXIST) - die("Could not create metadata FIFO %s", pipename); + // here, create the pipe + if (strcasecmp(pipename, "STDOUT") != 0) + if (mkfifo(pipename, 0644) && errno != EEXIST) + die("Could not create metadata FIFO %s", pipename); - - debug(1,"Pipename is \"%s\"",pipename); + debug(1, "Pipename is \"%s\"", pipename); - // test open pipe so we error on startup if it's going to fail - start(44100); - stop(); + // test open pipe so we error on startup if it's going to fail + start(44100); + stop(); - return 0; + return 0; } static void deinit(void) { - if ((fd > 0) && (fd!=STDOUT_FILENO)) - close(fd); + if ((fd > 0) && (fd != STDOUT_FILENO)) + close(fd); } static void help(void) { - printf(" pipe takes 1 argument: the name of the FIFO to write to, which can be \"stdout\".\n"); + printf(" pipe takes 1 argument: the name of the FIFO to write to, which can be \"stdout\".\n"); } -audio_output audio_pipe = { - .name = "pipe", - .help = &help, - .init = &init, - .deinit = &deinit, - .start = &start, - .stop = &stop, - .flush = NULL, - .delay = NULL, - .play = &play, - .volume = NULL, - .parameters = NULL -}; +audio_output audio_pipe = {.name = "pipe", + .help = &help, + .init = &init, + .deinit = &deinit, + .start = &start, + .stop = &stop, + .flush = NULL, + .delay = NULL, + .play = &play, + .volume = NULL, + .parameters = NULL}; diff --git a/audio_pulse.c b/audio_pulse.c index d76c1de5..29feed88 100644 --- a/audio_pulse.c +++ b/audio_pulse.c @@ -24,7 +24,6 @@ * OTHER DEALINGS IN THE SOFTWARE. */ - #include #include #include @@ -37,121 +36,105 @@ static pa_simple *pa_dev = NULL; static struct { - char *server; - char *sink; - char *apname; -} pulse_options = { - .server = NULL, - .sink = NULL, - .apname = NULL -}; + char *server; + char *sink; + char *apname; +} pulse_options = {.server = NULL, .sink = NULL, .apname = NULL}; static int pa_error; static void pulse_connect(void); static void help(void) { - printf(" -a server set the server name\n" - " -s sink set the output sink\n" - " -n name set the application name, as seen by PulseAudio\n" - " defaults to the access point name\n" - ); + printf(" -a server set the server name\n" + " -s sink set the output sink\n" + " -n name set the application name, as seen by PulseAudio\n" + " defaults to the access point name\n"); } static int init(int argc, char **argv) { - pulse_options.apname = config.apname; - - config.audio_backend_buffer_desired_length = 44100; // one second. - config.audio_backend_latency_offset = 0; + pulse_options.apname = config.apname; - optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour - argv--; // so we shift the arguments to satisfy getopt() - argc++; + config.audio_backend_buffer_desired_length = 44100; // one second. + config.audio_backend_latency_offset = 0; - // some platforms apparently require optreset = 1; - which? - int opt; - while ((opt = getopt(argc, argv, "a:s:n:")) > 0) { - switch (opt) { - case 'a': - pulse_options.server = optarg; - break; - case 's': - pulse_options.sink = optarg; - break; - case 'n': - pulse_options.apname = optarg; - break; - default: - help(); - die("Invalid audio option -%c specified", opt); - } + optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour + argv--; // so we shift the arguments to satisfy getopt() + argc++; + + // some platforms apparently require optreset = 1; - which? + int opt; + while ((opt = getopt(argc, argv, "a:s:n:")) > 0) { + switch (opt) { + case 'a': + pulse_options.server = optarg; + break; + case 's': + pulse_options.sink = optarg; + break; + case 'n': + pulse_options.apname = optarg; + break; + default: + help(); + die("Invalid audio option -%c specified", opt); } + } - if (optind < argc) - die("Invalid audio argument: %s", argv[optind]); + if (optind < argc) + die("Invalid audio argument: %s", argv[optind]); - pulse_connect(); + pulse_connect(); - return 0; + return 0; } static void pulse_connect(void) { - static const pa_sample_spec ss = { - .format = PA_SAMPLE_S16LE, - .rate = 44100, - .channels = 2 - }; + static const pa_sample_spec ss = {.format = PA_SAMPLE_S16LE, .rate = 44100, .channels = 2}; - pa_dev = pa_simple_new(pulse_options.server, - pulse_options.apname, - PA_STREAM_PLAYBACK, - pulse_options.sink, - "Shairport Stream", - &ss, NULL, NULL, - &pa_error); + pa_dev = pa_simple_new(pulse_options.server, pulse_options.apname, PA_STREAM_PLAYBACK, + pulse_options.sink, "Shairport Stream", &ss, NULL, NULL, &pa_error); - if (!pa_dev) - die("Could not connect to pulseaudio server: %s", pa_strerror(pa_error)); + if (!pa_dev) + die("Could not connect to pulseaudio server: %s", pa_strerror(pa_error)); } static void deinit(void) { - if (pa_dev) - pa_simple_free(pa_dev); - pa_dev = NULL; + if (pa_dev) + pa_simple_free(pa_dev); + pa_dev = NULL; } static void start(int sample_rate) { - if (sample_rate != 44100) - die("unexpected sample rate!"); + if (sample_rate != 44100) + die("unexpected sample rate!"); } static void play(short buf[], int samples) { - if( pa_simple_write(pa_dev, (char *)buf, (size_t)samples * 4, &pa_error) < 0 ) { - fprintf(stderr, __FILE__": pa_simple_write() failed: %s\n", pa_strerror(pa_error)); - if (pa_error == PA_ERR_CONNECTIONTERMINATED) { - fprintf(stderr, __FILE__": reconnecting."); - deinit(); - pulse_connect(); - } + if (pa_simple_write(pa_dev, (char *)buf, (size_t)samples * 4, &pa_error) < 0) { + fprintf(stderr, __FILE__ ": pa_simple_write() failed: %s\n", pa_strerror(pa_error)); + if (pa_error == PA_ERR_CONNECTIONTERMINATED) { + fprintf(stderr, __FILE__ ": reconnecting."); + deinit(); + pulse_connect(); } + } } static void stop(void) { - if (pa_simple_drain(pa_dev, &pa_error) < 0) - fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(pa_error)); + if (pa_simple_drain(pa_dev, &pa_error) < 0) + fprintf(stderr, __FILE__ ": pa_simple_drain() failed: %s\n", pa_strerror(pa_error)); } -audio_output audio_pulse = { - .name = "pulse", - .help = &help, - .init = &init, - .deinit = &deinit, - .start = &start, - .stop = &stop, - .flush = NULL, - .delay = NULL, - .play = &play, - .volume = NULL, - .parameters = NULL -}; +audio_output audio_pulse = {.name = "pulse", + .help = &help, + .init = &init, + .deinit = &deinit, + .start = &start, + .stop = &stop, + .flush = NULL, + .delay = NULL, + .play = &play, + .volume = NULL, + .parameters = NULL}; diff --git a/audio_sndio.c b/audio_sndio.c index 65a42778..e59dbb85 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -24,67 +24,61 @@ static struct sio_hdl *sio; static struct sio_par par; static int init(int argc, char **argv) { - sio = sio_open(SIO_DEVANY, SIO_PLAY, 0); - if (!sio) - die("sndio: cannot connect to sound server"); + sio = sio_open(SIO_DEVANY, SIO_PLAY, 0); + if (!sio) + die("sndio: cannot connect to sound server"); - sio_initpar(&par); + sio_initpar(&par); - par.bits = 16; - par.rate = 44100; - par.pchan = 2; - par.le = SIO_LE_NATIVE; - par.sig = 1; + par.bits = 16; + par.rate = 44100; + par.pchan = 2; + par.le = SIO_LE_NATIVE; + par.sig = 1; - if (!sio_setpar(sio, &par)) - die("sndio: failed to set audio parameters"); - if (!sio_getpar(sio, &par)) - die("sndio: failed to get audio parameters"); - - config.audio_backend_buffer_desired_length = 44100; // one second. + if (!sio_setpar(sio, &par)) + die("sndio: failed to set audio parameters"); + if (!sio_getpar(sio, &par)) + die("sndio: failed to get audio parameters"); + + config.audio_backend_buffer_desired_length = 44100; // one second. config.audio_backend_latency_offset = 0; - return 0; + return 0; } -static void deinit(void) { - sio_close(sio); -} +static void deinit(void) { sio_close(sio); } static void start(int sample_rate) { - if (sample_rate != par.rate) - die("unexpected sample rate!"); - sio_start(sio); + if (sample_rate != par.rate) + die("unexpected sample rate!"); + sio_start(sio); } static void play(short buf[], int samples) { - sio_write(sio, (char *)buf, samples * par.bps * par.pchan); + sio_write(sio, (char *)buf, samples * par.bps * par.pchan); } -static void stop(void) { - sio_stop(sio); -} +static void stop(void) { sio_stop(sio); } static void help(void) { - printf(" There are no options for sndio audio.\n"); - printf(" Use AUDIODEVICE environment variable.\n"); + printf(" There are no options for sndio audio.\n"); + printf(" Use AUDIODEVICE environment variable.\n"); } static void volume(double vol) { - unsigned int v = vol * SIO_MAXVOL; - sio_setvol(sio, v); + unsigned int v = vol * SIO_MAXVOL; + sio_setvol(sio, v); } -audio_output audio_sndio = { - .name = "sndio", - .help = &help, - .init = &init, - .deinit = &deinit, - .start = &start, - .stop = &stop, - .flush = NULL, - .delay = NULL, - .play = &play, - .volume = &volume, - .parameters = NULL -}; +audio_output audio_sndio = {.name = "sndio", + .help = &help, + .init = &init, + .deinit = &deinit, + .start = &start, + .stop = &stop, + .flush = NULL, + .delay = NULL, + .play = &play, + .volume = &volume, + .parameters = NULL}; diff --git a/common.c b/common.c index 1957ffbf..fb56c508 100644 --- a/common.c +++ b/common.c @@ -64,12 +64,10 @@ #endif #endif - - #include "common.h" #include -//true if Shairport Sync is supposed to be sending output to the output device, false otherwise +// true if Shairport Sync is supposed to be sending output to the output device, false otherwise static volatile int requested_connection_state_to_output = 1; @@ -77,99 +75,98 @@ shairport_cfg config; int debuglev = 0; -int get_requested_connection_state_to_output() { - return requested_connection_state_to_output; -} - -void set_requested_connection_state_to_output(int v) { - requested_connection_state_to_output = v; -} +int get_requested_connection_state_to_output() { return requested_connection_state_to_output; } +void set_requested_connection_state_to_output(int v) { requested_connection_state_to_output = v; } void die(char *format, ...) { - char s[1024]; - s[0]=0; - va_list args; - va_start(args, format); - vsprintf(s,format,args); - va_end(args); - daemon_log(LOG_EMERG,"%s", s); - shairport_shutdown(); - exit(1); + char s[1024]; + s[0] = 0; + va_list args; + va_start(args, format); + vsprintf(s, format, args); + va_end(args); + daemon_log(LOG_EMERG, "%s", s); + shairport_shutdown(); + exit(1); } void warn(char *format, ...) { - char s[1024]; - s[0]=0; - va_list args; - va_start(args, format); - vsprintf(s,format,args); - va_end(args); - daemon_log(LOG_WARNING,"%s", s); + char s[1024]; + s[0] = 0; + va_list args; + va_start(args, format); + vsprintf(s, format, args); + va_end(args); + daemon_log(LOG_WARNING, "%s", s); } void debug(int level, char *format, ...) { - if (level > debuglev) - return; - char s[1024]; - s[0]=0; - va_list args; - va_start(args, format); - vsprintf(s,format,args); - va_end(args); - daemon_log(LOG_DEBUG,"%s", s); + if (level > debuglev) + return; + char s[1024]; + s[0] = 0; + va_list args; + va_start(args, format); + vsprintf(s, format, args); + va_end(args); + daemon_log(LOG_DEBUG, "%s", s); } void inform(char *format, ...) { - char s[1024]; - s[0]=0; - va_list args; - va_start(args, format); - vsprintf(s,format,args); - va_end(args); - daemon_log(LOG_INFO,"%s", s); + char s[1024]; + s[0] = 0; + va_list args; + va_start(args, format); + vsprintf(s, format, args); + va_end(args); + daemon_log(LOG_INFO, "%s", s); } #ifdef HAVE_LIBPOLARSSL char *base64_enc(uint8_t *input, int length) { char *buf = NULL; size_t dlen = 0; - int rc = base64_encode(NULL,&dlen,input,length); - if (rc && (rc!=POLARSSL_ERR_BASE64_BUFFER_TOO_SMALL)) - debug(1,"Error %d getting length of base64 encode.",rc); + int rc = base64_encode(NULL, &dlen, input, length); + if (rc && (rc != POLARSSL_ERR_BASE64_BUFFER_TOO_SMALL)) + debug(1, "Error %d getting length of base64 encode.", rc); else { buf = (char *)malloc(dlen); - rc = base64_encode((unsigned char *)buf,&dlen,input,length); - if (rc!=0) - debug(1,"Error %d encoding base64.",rc); + rc = base64_encode((unsigned char *)buf, &dlen, input, length); + if (rc != 0) + debug(1, "Error %d encoding base64.", rc); } return buf; } uint8_t *base64_dec(char *input, int *outlen) { - // slight problem here is that Apple cut the padding off their challenges. We must restore it before passing it in to the decoder, it seems + // slight problem here is that Apple cut the padding off their challenges. We must restore it + // before passing it in to the decoder, it seems uint8_t *buf = NULL; size_t dlen = 0; - int inbufsize = ((strlen(input)+3)/4)*4; // this is the size of the input buffer we will send to the decoder, but we need space for 3 extra "="s and a NULL - char *inbuf = malloc(inbufsize+4); - if (inbuf==0) - debug(1,"Can't malloc memory for inbuf in base64_decode."); + int inbufsize = ((strlen(input) + 3) / 4) * 4; // this is the size of the input buffer we will + // send to the decoder, but we need space for 3 + // extra "="s and a NULL + char *inbuf = malloc(inbufsize + 4); + if (inbuf == 0) + debug(1, "Can't malloc memory for inbuf in base64_decode."); else { - strcpy(inbuf,input); - strcat(inbuf,"==="); - // debug(1,"base64_dec called with string \"%s\", length %d, filled string: \"%s\", length %d.",input,strlen(input),inbuf,inbufsize); - int rc = base64_decode(buf,&dlen,(unsigned char *)inbuf,inbufsize); - if (rc && (rc!=POLARSSL_ERR_BASE64_BUFFER_TOO_SMALL)) - debug(1,"Error %d getting decode length, result is %d.",rc,dlen); + strcpy(inbuf, input); + strcat(inbuf, "==="); + // debug(1,"base64_dec called with string \"%s\", length %d, filled string: \"%s\", length + // %d.",input,strlen(input),inbuf,inbufsize); + int rc = base64_decode(buf, &dlen, (unsigned char *)inbuf, inbufsize); + if (rc && (rc != POLARSSL_ERR_BASE64_BUFFER_TOO_SMALL)) + debug(1, "Error %d getting decode length, result is %d.", rc, dlen); else { // debug(1,"Decode size is %d.",dlen); buf = malloc(dlen); - if (buf==0) - debug(1,"Can't allocate memory in base64_dec."); + if (buf == 0) + debug(1, "Can't allocate memory in base64_dec."); else { - rc = base64_decode(buf,&dlen,(unsigned char *)inbuf,inbufsize); - if (rc!=0) - debug(1,"Error %d in base64_dec.",rc); + rc = base64_decode(buf, &dlen, (unsigned char *)inbuf, inbufsize); + if (rc != 0) + debug(1, "Error %d in base64_dec.", rc); } } free(inbuf); @@ -181,221 +178,226 @@ uint8_t *base64_dec(char *input, int *outlen) { #ifdef HAVE_LIBSSL char *base64_enc(uint8_t *input, int length) { - BIO *bmem, *b64; - BUF_MEM *bptr; - b64 = BIO_new(BIO_f_base64()); - bmem = BIO_new(BIO_s_mem()); - b64 = BIO_push(b64, bmem); - BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); - BIO_write(b64, input, length); - BIO_flush(b64); - BIO_get_mem_ptr(b64, &bptr); + BIO *bmem, *b64; + BUF_MEM *bptr; + b64 = BIO_new(BIO_f_base64()); + bmem = BIO_new(BIO_s_mem()); + b64 = BIO_push(b64, bmem); + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); + BIO_write(b64, input, length); + BIO_flush(b64); + BIO_get_mem_ptr(b64, &bptr); - char *buf = (char *)malloc(bptr->length); - if (bptr->length) { - memcpy(buf, bptr->data, bptr->length-1); - buf[bptr->length-1] = 0; - } + char *buf = (char *)malloc(bptr->length); + if (bptr->length) { + memcpy(buf, bptr->data, bptr->length - 1); + buf[bptr->length - 1] = 0; + } - BIO_free_all(bmem); + BIO_free_all(bmem); - return buf; + return buf; } uint8_t *base64_dec(char *input, int *outlen) { - BIO *bmem, *b64; - int inlen = strlen(input); + BIO *bmem, *b64; + int inlen = strlen(input); - b64 = BIO_new(BIO_f_base64()); - BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); - bmem = BIO_new(BIO_s_mem()); - b64 = BIO_push(b64, bmem); + b64 = BIO_new(BIO_f_base64()); + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); + bmem = BIO_new(BIO_s_mem()); + b64 = BIO_push(b64, bmem); - // Apple cut the padding off their challenges; restore it - BIO_write(bmem, input, inlen); - while (inlen++ & 3) - BIO_write(bmem, "=", 1); - BIO_flush(bmem); + // Apple cut the padding off their challenges; restore it + BIO_write(bmem, input, inlen); + while (inlen++ & 3) + BIO_write(bmem, "=", 1); + BIO_flush(bmem); - int bufsize = strlen(input)*3/4 + 1; - uint8_t *buf = malloc(bufsize); - int nread; + int bufsize = strlen(input) * 3 / 4 + 1; + uint8_t *buf = malloc(bufsize); + int nread; - nread = BIO_read(b64, buf, bufsize); + nread = BIO_read(b64, buf, bufsize); - BIO_free_all(bmem); + BIO_free_all(bmem); - *outlen = nread; - return buf; + *outlen = nread; + return buf; } #endif static char super_secret_key[] = -"-----BEGIN RSA PRIVATE KEY-----\n" -"MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt\n" -"wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U\n" -"wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf\n" -"/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/\n" -"UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW\n" -"BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa\n" -"LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5\n" -"NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm\n" -"lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz\n" -"aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu\n" -"a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM\n" -"oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z\n" -"oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+\n" -"k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL\n" -"AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA\n" -"cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf\n" -"54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov\n" -"17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc\n" -"1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI\n" -"LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ\n" -"2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\n" -"-----END RSA PRIVATE KEY-----\0"; + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt\n" + "wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U\n" + "wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf\n" + "/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/\n" + "UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW\n" + "BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa\n" + "LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5\n" + "NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm\n" + "lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz\n" + "aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu\n" + "a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM\n" + "oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z\n" + "oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+\n" + "k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL\n" + "AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA\n" + "cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf\n" + "54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov\n" + "17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc\n" + "1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI\n" + "LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ\n" + "2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\n" + "-----END RSA PRIVATE KEY-----\0"; #ifdef HAVE_LIBSSL uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { -static RSA *rsa = NULL; + static RSA *rsa = NULL; - if (!rsa) { - BIO *bmem = BIO_new_mem_buf(super_secret_key, -1); - rsa = PEM_read_bio_RSAPrivateKey(bmem, NULL, NULL, NULL); - BIO_free(bmem); - } + if (!rsa) { + BIO *bmem = BIO_new_mem_buf(super_secret_key, -1); + rsa = PEM_read_bio_RSAPrivateKey(bmem, NULL, NULL, NULL); + BIO_free(bmem); + } - uint8_t *out = malloc(RSA_size(rsa)); - switch (mode) { - case RSA_MODE_AUTH: - *outlen = RSA_private_encrypt(inlen, input, out, rsa, - RSA_PKCS1_PADDING); - break; - case RSA_MODE_KEY: - *outlen = RSA_private_decrypt(inlen, input, out, rsa, - RSA_PKCS1_OAEP_PADDING); - break; - default: - die("bad rsa mode"); - } - return out; + uint8_t *out = malloc(RSA_size(rsa)); + switch (mode) { + case RSA_MODE_AUTH: + *outlen = RSA_private_encrypt(inlen, input, out, rsa, RSA_PKCS1_PADDING); + break; + case RSA_MODE_KEY: + *outlen = RSA_private_decrypt(inlen, input, out, rsa, RSA_PKCS1_OAEP_PADDING); + break; + default: + die("bad rsa mode"); + } + return out; } #endif #ifdef HAVE_LIBPOLARSSL uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { - rsa_context trsa; - const char *pers = "rsa_encrypt"; - int rc; - - entropy_context entropy; - ctr_drbg_context ctr_drbg; - entropy_init( &entropy ); - if( ( rc = ctr_drbg_init( &ctr_drbg, entropy_func, &entropy,(const unsigned char *) pers,strlen( pers ) ) ) != 0 ) - debug(1, "ctr_drbg_init returned %d\n", rc ); + rsa_context trsa; + const char *pers = "rsa_encrypt"; + int rc; - rsa_init(&trsa,RSA_PKCS_V21,POLARSSL_MD_SHA1); // padding and hash id get overwritten - // BTW, this seems to reset a lot of parameters in the rsa_context - rc = x509parse_key(&trsa,(unsigned char *)super_secret_key,strlen(super_secret_key),NULL,0); - if (rc!=0) - debug(1,"Error %d reading the private key."); - - uint8_t *out = NULL; + entropy_context entropy; + ctr_drbg_context ctr_drbg; + entropy_init(&entropy); + if ((rc = ctr_drbg_init(&ctr_drbg, entropy_func, &entropy, (const unsigned char *)pers, + strlen(pers))) != 0) + debug(1, "ctr_drbg_init returned %d\n", rc); - switch (mode) { - case RSA_MODE_AUTH: - trsa.padding = RSA_PKCS_V15; - trsa.hash_id = POLARSSL_MD_NONE; - debug(2,"rsa_apply encrypt"); - out = malloc(trsa.len); - rc = rsa_pkcs1_encrypt( &trsa, ctr_drbg_random, &ctr_drbg, RSA_PRIVATE, inlen, input, out ); - if (rc!=0) - debug(1,"rsa_pkcs1_encrypt error %d.",rc); - *outlen=trsa.len; - break; - case RSA_MODE_KEY: - debug(2,"rsa_apply decrypt"); - trsa.padding=RSA_PKCS_V21; - trsa.hash_id=POLARSSL_MD_SHA1; - out = malloc(trsa.len); + rsa_init(&trsa, RSA_PKCS_V21, POLARSSL_MD_SHA1); // padding and hash id get overwritten + // BTW, this seems to reset a lot of parameters in the rsa_context + rc = x509parse_key(&trsa, (unsigned char *)super_secret_key, strlen(super_secret_key), NULL, 0); + if (rc != 0) + debug(1, "Error %d reading the private key."); + + uint8_t *out = NULL; + + switch (mode) { + case RSA_MODE_AUTH: + trsa.padding = RSA_PKCS_V15; + trsa.hash_id = POLARSSL_MD_NONE; + debug(2, "rsa_apply encrypt"); + out = malloc(trsa.len); + rc = rsa_pkcs1_encrypt(&trsa, ctr_drbg_random, &ctr_drbg, RSA_PRIVATE, inlen, input, out); + if (rc != 0) + debug(1, "rsa_pkcs1_encrypt error %d.", rc); + *outlen = trsa.len; + break; + case RSA_MODE_KEY: + debug(2, "rsa_apply decrypt"); + trsa.padding = RSA_PKCS_V21; + trsa.hash_id = POLARSSL_MD_SHA1; + out = malloc(trsa.len); #if POLARSSL_VERSION_NUMBER >= 0x01020900 - rc = rsa_pkcs1_decrypt(&trsa, ctr_drbg_random, &ctr_drbg, RSA_PRIVATE, (size_t *)outlen, input, out, trsa.len); + rc = rsa_pkcs1_decrypt(&trsa, ctr_drbg_random, &ctr_drbg, RSA_PRIVATE, (size_t *)outlen, input, + out, trsa.len); #else - rc = rsa_pkcs1_decrypt(&trsa, RSA_PRIVATE, outlen, input, out, trsa.len); + rc = rsa_pkcs1_decrypt(&trsa, RSA_PRIVATE, outlen, input, out, trsa.len); #endif - if (rc!=0) - debug(1,"decrypt error %d.",rc); - break; - default: - die("bad rsa mode"); - } - rsa_free(&trsa); - debug(2,"rsa_apply exit"); - return out; + if (rc != 0) + debug(1, "decrypt error %d.", rc); + break; + default: + die("bad rsa mode"); + } + rsa_free(&trsa); + debug(2, "rsa_apply exit"); + return out; } #endif void command_start(void) { - if (config.cmd_start) { - /*Spawn a child to run the program.*/ - pid_t pid=fork(); - if (pid==0) { /* child process */ - int argC; - char **argV; - // debug(1,"on-start command found."); - if (poptParseArgvString(config.cmd_start,&argC,(const char ***)&argV)!=0) // note that argV should be free()'d after use, but we expect this fork to exit eventually. - debug(1,"Can't decipher on-start command arguments"); - else { - // debug(1,"Executing on-start command %s with %d arguments.",argV[0],argC); - execv(argV[0],argV); - warn("Execution of on-start command failed to start"); - debug(1,"Error executing on-start command %s",config.cmd_start); - exit(127); /* only if execv fails */ - } - } else { - if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for process to finish */ - pid_t rc = waitpid(pid,0,0); /* wait for child to exit */ - if (rc!=pid) { - warn("Execution of on-start command returned an error."); - debug(1,"on-start command %s finished with error %d",config.cmd_start,errno); - } - } - // debug(1,"Continue after on-start command"); - } - } + if (config.cmd_start) { + /*Spawn a child to run the program.*/ + pid_t pid = fork(); + if (pid == 0) { /* child process */ + int argC; + char **argV; + // debug(1,"on-start command found."); + if (poptParseArgvString(config.cmd_start, &argC, (const char ***)&argV) != + 0) // note that argV should be free()'d after use, but we expect this fork to exit + // eventually. + debug(1, "Can't decipher on-start command arguments"); + else { + // debug(1,"Executing on-start command %s with %d arguments.",argV[0],argC); + execv(argV[0], argV); + warn("Execution of on-start command failed to start"); + debug(1, "Error executing on-start command %s", config.cmd_start); + exit(127); /* only if execv fails */ + } + } else { + if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for + process to finish */ + pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */ + if (rc != pid) { + warn("Execution of on-start command returned an error."); + debug(1, "on-start command %s finished with error %d", config.cmd_start, errno); + } + } + // debug(1,"Continue after on-start command"); + } + } } void command_stop(void) { - if (config.cmd_stop) { - /*Spawn a child to run the program.*/ - pid_t pid=fork(); - if (pid==0) { /* child process */ - int argC; - char **argV; - // debug(1,"on-stop command found."); - if (poptParseArgvString(config.cmd_stop,&argC,(const char ***)&argV)!=0) // note that argV should be free()'d after use, but we expect this fork to exit eventually. - debug(1,"Can't decipher on-stop command arguments"); + if (config.cmd_stop) { + /*Spawn a child to run the program.*/ + pid_t pid = fork(); + if (pid == 0) { /* child process */ + int argC; + char **argV; + // debug(1,"on-stop command found."); + if (poptParseArgvString(config.cmd_stop, &argC, (const char ***)&argV) != + 0) // note that argV should be free()'d after use, but we expect this fork to exit + // eventually. + debug(1, "Can't decipher on-stop command arguments"); else { - // debug(1,"Executing on-stop command %s",config.cmd_stop); - execv(argV[0],argV); - warn("Execution of on-stop command failed to start"); - debug(1,"Error executing on-stop command %s",config.cmd_stop); - exit(127); /* only if execv fails */ - } - } else { - if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for process to finish */ - pid_t rc = waitpid(pid,0,0); /* wait for child to exit */ - if (rc!=pid) { - warn("Execution of on-stop command returned an error."); - debug(1,"Stop command %s finished with error %d",config.cmd_stop,errno); - } - } - // debug(1,"Continue after on-stop command"); - } - } + // debug(1,"Executing on-stop command %s",config.cmd_stop); + execv(argV[0], argV); + warn("Execution of on-stop command failed to start"); + debug(1, "Error executing on-stop command %s", config.cmd_stop); + exit(127); /* only if execv fails */ + } + } else { + if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for + process to finish */ + pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */ + if (rc != pid) { + warn("Execution of on-stop command returned an error."); + debug(1, "Stop command %s finished with error %d", config.cmd_stop, errno); + } + } + // debug(1,"Continue after on-stop command"); + } + } } - // this is for reading an unsigned 32 bit number, such as an RTP timestamp uint32_t uatoi(const char *nptr) { @@ -404,19 +406,25 @@ uint32_t uatoi(const char *nptr) { return r; } -// Given a volume (0 to -30) and high and low attenuations available in the mixer in dB, return an attenuation depending on the volume and the function's transfer function +// Given a volume (0 to -30) and high and low attenuations available in the mixer in dB, return an +// attenuation depending on the volume and the function's transfer function // See http://tangentsoft.net/audio/atten.html for data on good attenuators. -// We want a smooth attenuation function, like, for example, the ALPS RK27 Potentiometer transfer functions referred to at the link above. +// We want a smooth attenuation function, like, for example, the ALPS RK27 Potentiometer transfer +// functions referred to at the link above. // Note that the max_db and min_db are given as dB*100 -double vol2attn(double vol, long max_db, long min_db) { +double vol2attn(double vol, long max_db, long min_db) { -// We use a little coordinate geometry to build a transfer function from the volume passed in to the device's dynamic range. +// We use a little coordinate geometry to build a transfer function from the volume passed in to the +// device's dynamic range. // (See the diagram in the documents folder.) -// The x axis is the "volume in" which will be from -30 to 0. The y axis will be the "volume out" which will be from the bottom of the range to the top. -// We build the transfer function from one or more lines. We characterise each line with two numbers: -// the first is where on x the line starts when y=0 (x can be from 0 to -30); the second is where on y the line stops when when x is -30. +// The x axis is the "volume in" which will be from -30 to 0. The y axis will be the "volume out" +// which will be from the bottom of the range to the top. +// We build the transfer function from one or more lines. We characterise each line with two +// numbers: +// the first is where on x the line starts when y=0 (x can be from 0 to -30); the second is where on +// y the line stops when when x is -30. // thus, if the line was characterised as {0,-30}, it would be an identity transfer. // Assuming, for example, a dynamic range of lv=-60 to hv=0 // Typically we'll use three lines -- a three order transfer function @@ -427,27 +435,33 @@ double vol2attn(double vol, long max_db, long min_db) { #define order 3 double vol_setting = max_db; - - if ((vol<=0.0) && (vol>=-30.0)) { - long range_db = max_db-min_db; // this will be a positive nunmber + + if ((vol <= 0.0) && (vol >= -30.0)) { + long range_db = max_db - min_db; // this will be a positive nunmber // debug(1,"Volume min %ddB, max %ddB, range %ddB.",min_db,max_db,range_db); - //double first_slope = -3000.0; // this is the slope of the attenuation at the high end -- 30dB for the full rotation. - double first_slope = -range_db/2; // this is the slope of the attenuation at the high end -- 30dB for the full rotation. - if (-range_db>first_slope) + // double first_slope = -3000.0; // this is the slope of the attenuation at the high end -- 30dB + // for the full rotation. + double first_slope = + -range_db / + 2; // this is the slope of the attenuation at the high end -- 30dB for the full rotation. + if (-range_db > first_slope) first_slope = range_db; - double lines[order][2] = {{0,first_slope},{-5,first_slope-(range_db+first_slope)/2},{-17,-range_db}}; + double lines[order][2] = { + {0, first_slope}, {-5, first_slope - (range_db + first_slope) / 2}, {-17, -range_db}}; int i; - for (i=0;i #if TARGET_OS_MAC == 1 - /* OSX */ - #define COMPILE_FOR_OSX 1 +/* OSX */ +#define COMPILE_FOR_OSX 1 #endif #endif #if defined(__linux__) - /* Linux. --------------------------------------------------- */ - #define COMPILE_FOR_LINUX 1 +/* Linux. --------------------------------------------------- */ +#define COMPILE_FOR_LINUX 1 #endif // struct sockaddr_in6 is bigger than struct sockaddr. derp #ifdef AF_INET6 - #define SOCKADDR struct sockaddr_storage - #define SAFAMILY ss_family +#define SOCKADDR struct sockaddr_storage +#define SAFAMILY ss_family #else - #define SOCKADDR struct sockaddr - #define SAFAMILY sa_family +#define SOCKADDR struct sockaddr +#define SAFAMILY sa_family #endif - enum stuffing_type { - ST_basic = 0, + ST_basic = 0, ST_soxr, } type; - - typedef struct { - config_t* cfg; - char *password; - char *apname; + config_t *cfg; + char *password; + char *apname; #ifdef CONFIG_METADATA - int metadata_enabled; - char *metadata_pipename; - int get_coverart; + int metadata_enabled; + char *metadata_pipename; + int get_coverart; #endif - uint8_t hw_addr[6]; - int port; - int ignore_volume_control; - int resyncthreshold; // if it get's out of whack my more than this, resync. Zero means never resync. - int allow_session_interruption; - int timeout; // while in play mode, exit if no packets of audio come in for more than this number of seconds . Zero means never exit. - int dont_check_timeout; // this is used to maintain backward compatability with the old -t option behaviour; only set by -t 0, cleared by everything else - char *output_name; - audio_output *output; - char *mdns_name; - mdns_backend *mdns; - int buffer_start_fill; - uint32_t latency; - uint32_t userSuppliedLatency; // overrides all other latencies -- use with caution - uint32_t iTunesLatency; // supplied with --iTunesLatency option - uint32_t AirPlayLatency; //supplied with --AirPlayLatency option - uint32_t ForkedDaapdLatency; //supplied with --ForkedDaapdLatency option - int daemonise; - int statistics_requested; - char *cmd_start, *cmd_stop; - int cmd_blocking; - int tolerance; // allow this much drift before attempting to correct it - enum stuffing_type packet_stuffing; - char *pidfile; - char *logfile; - char *errfile; - char *configfile; - uint audio_backend_buffer_desired_length; // this will be the desired number of frames in the audio backend buffer -- the DAC buffer for ALSA - uint audio_backend_latency_offset; // this will be the offset to compensate for any fixed latency there might be in the audio + uint8_t hw_addr[6]; + int port; + int ignore_volume_control; + int resyncthreshold; // if it get's out of whack my more than this, resync. Zero means never + // resync. + int allow_session_interruption; + int timeout; // while in play mode, exit if no packets of audio come in for more than this number + // of seconds . Zero means never exit. + int dont_check_timeout; // this is used to maintain backward compatability with the old -t option + // behaviour; only set by -t 0, cleared by everything else + char *output_name; + audio_output *output; + char *mdns_name; + mdns_backend *mdns; + int buffer_start_fill; + uint32_t latency; + uint32_t userSuppliedLatency; // overrides all other latencies -- use with caution + uint32_t iTunesLatency; // supplied with --iTunesLatency option + uint32_t AirPlayLatency; // supplied with --AirPlayLatency option + uint32_t ForkedDaapdLatency; // supplied with --ForkedDaapdLatency option + int daemonise; + int statistics_requested; + char *cmd_start, *cmd_stop; + int cmd_blocking; + int tolerance; // allow this much drift before attempting to correct it + enum stuffing_type packet_stuffing; + char *pidfile; + char *logfile; + char *errfile; + char *configfile; + uint audio_backend_buffer_desired_length; // this will be the desired number of frames in the + // audio backend buffer -- the DAC buffer for ALSA + uint audio_backend_latency_offset; // this will be the offset to compensate for any fixed latency + // there might be in the audio } shairport_cfg; -//true if Shairport Sync is supposed to be sending output to the output device, false otherwise +// true if Shairport Sync is supposed to be sending output to the output device, false otherwise int get_requested_connection_state_to_output(); @@ -96,10 +98,11 @@ uint8_t *base64_dec(char *input, int *outlen); char *base64_enc(uint8_t *input, int length); #define RSA_MODE_AUTH (0) -#define RSA_MODE_KEY (1) +#define RSA_MODE_KEY (1) uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode); -// given a volume (0 to -30) and high and low attenuations in dB*100 (e.g. 0 to -6000 for 0 to -60 dB), return an attenuation depending on the transfer function +// given a volume (0 to -30) and high and low attenuations in dB*100 (e.g. 0 to -6000 for 0 to -60 +// dB), return an attenuation depending on the transfer function double vol2attn(double vol, long max_db, long min_db); // return a monolithic (always increasing) time in nanoseconds diff --git a/mdns.c b/mdns.c index 53b5df76..f4491fd1 100644 --- a/mdns.c +++ b/mdns.c @@ -24,7 +24,6 @@ * OTHER DEALINGS IN THE SOFTWARE. */ - #include #include #include @@ -47,78 +46,66 @@ extern mdns_backend mdns_tinysvcmdns; static mdns_backend *mdns_backends[] = { #ifdef CONFIG_AVAHI - &mdns_avahi, - &mdns_external_avahi, + &mdns_avahi, &mdns_external_avahi, #endif #ifdef CONFIG_HAVE_DNS_SD_H - &mdns_dns_sd, - &mdns_external_dns_sd, + &mdns_dns_sd, &mdns_external_dns_sd, #endif #ifdef CONFIG_TINYSVCMDNS &mdns_tinysvcmdns, #endif - NULL -}; + NULL}; void mdns_register(void) { - char *mdns_apname = alloca(strlen(config.apname) + 14); - char *p = mdns_apname; - int i; - for (i=0; i<6; i++) { - sprintf(p, "%02X", config.hw_addr[i]); - p += 2; - } - *p++ = '@'; - strcpy(p, config.apname); + char *mdns_apname = alloca(strlen(config.apname) + 14); + char *p = mdns_apname; + int i; + for (i = 0; i < 6; i++) { + sprintf(p, "%02X", config.hw_addr[i]); + p += 2; + } + *p++ = '@'; + strcpy(p, config.apname); - mdns_backend **b = NULL; - - if (config.mdns_name != NULL) - { - for (b = mdns_backends; *b; b++) - { - if (strcmp((*b)->name, config.mdns_name) != 0) // Not the one we are looking for - continue; - int error = (*b)->mdns_register(mdns_apname, config.port); - if (error >= 0) - { - config.mdns = *b; - } - break; - } + mdns_backend **b = NULL; - if (*b == NULL) - warn("%s mDNS backend not found"); - } - else - { - for (b = mdns_backends; *b; b++) - { - int error = (*b)->mdns_register(mdns_apname, config.port); - if (error >= 0) - { - config.mdns = *b; - break; - } - } + if (config.mdns_name != NULL) { + for (b = mdns_backends; *b; b++) { + if (strcmp((*b)->name, config.mdns_name) != 0) // Not the one we are looking for + continue; + int error = (*b)->mdns_register(mdns_apname, config.port); + if (error >= 0) { + config.mdns = *b; + } + break; } - if (config.mdns == NULL) - die("Could not establish mDNS advertisement!"); + if (*b == NULL) + warn("%s mDNS backend not found"); + } else { + for (b = mdns_backends; *b; b++) { + int error = (*b)->mdns_register(mdns_apname, config.port); + if (error >= 0) { + config.mdns = *b; + break; + } + } + } + + if (config.mdns == NULL) + die("Could not establish mDNS advertisement!"); } void mdns_unregister(void) { - if (config.mdns) { - config.mdns->mdns_unregister(); - } + if (config.mdns) { + config.mdns->mdns_unregister(); + } } void mdns_ls_backends(void) { - mdns_backend **b = NULL; - printf("Available mDNS backends: \n"); - for (b = mdns_backends; *b; b++) - { - printf(" %s\n", (*b)->name); - } + mdns_backend **b = NULL; + printf("Available mDNS backends: \n"); + for (b = mdns_backends; *b; b++) { + printf(" %s\n", (*b)->name); + } } - diff --git a/mdns.h b/mdns.h index 247b9a68..a15b77fa 100644 --- a/mdns.h +++ b/mdns.h @@ -10,9 +10,9 @@ void mdns_register(void); void mdns_ls_backends(void); typedef struct { - char *name; - int (*mdns_register)(char *apname, int port); - void (*mdns_unregister)(void); + char *name; + int (*mdns_register)(char *apname, int port); + void (*mdns_unregister)(void); } mdns_backend; // text and progress only -- picture feed really buggy from iTunes @@ -21,13 +21,13 @@ typedef struct { #define METADATA_EXPRESSION config.get_coverart ? "md=0,1,2" : "md=0,2" -#define MDNS_RECORD_WITH_METADATA "tp=UDP", "sm=false", "ek=1", "et=0,1", "cn=0,1", "ch=2", METADATA_EXPRESSION , \ - "ss=16", "sr=44100", "vn=3", "txtvers=1", \ - config.password ? "pw=true" : "pw=false" +#define MDNS_RECORD_WITH_METADATA \ + "tp=UDP", "sm=false", "ek=1", "et=0,1", "cn=0,1", "ch=2", METADATA_EXPRESSION, "ss=16", \ + "sr=44100", "vn=3", "txtvers=1", config.password ? "pw=true" : "pw=false" #endif -#define MDNS_RECORD_WITHOUT_METADATA "tp=UDP", "sm=false", "ek=1", "et=0,1", "cn=0,1", "ch=2", \ - "ss=16", "sr=44100", "vn=3", "txtvers=1", \ - config.password ? "pw=true" : "pw=false" +#define MDNS_RECORD_WITHOUT_METADATA \ + "tp=UDP", "sm=false", "ek=1", "et=0,1", "cn=0,1", "ch=2", "ss=16", "sr=44100", "vn=3", \ + "txtvers=1", config.password ? "pw=true" : "pw=false" #endif // _MDNS_H diff --git a/mdns_avahi.c b/mdns_avahi.c index 35e397d3..31800b8b 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -42,131 +42,104 @@ static AvahiThreadedPoll *tpoll = NULL; static char *name = NULL; static int port = 0; -static void egroup_callback(AvahiEntryGroup *g, - AvahiEntryGroupState state, +static void egroup_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) { - if (state==AVAHI_ENTRY_GROUP_COLLISION) - die("service name already exists on network!"); - if (state==AVAHI_ENTRY_GROUP_FAILURE) - die("avahi entry group failure!"); + if (state == AVAHI_ENTRY_GROUP_COLLISION) + die("service name already exists on network!"); + if (state == AVAHI_ENTRY_GROUP_FAILURE) + die("avahi entry group failure!"); } static void register_service(AvahiClient *c) { - debug(1, "avahi: register_service."); - if (!group) - group = avahi_entry_group_new(c, egroup_callback, NULL); - if (!group) - die("avahi_entry_group_new failed"); + debug(1, "avahi: register_service."); + if (!group) + group = avahi_entry_group_new(c, egroup_callback, NULL); + if (!group) + die("avahi_entry_group_new failed"); - if (!avahi_entry_group_is_empty(group)) - return; + if (!avahi_entry_group_is_empty(group)) + return; - int ret; + int ret; #ifdef CONFIG_METADATA - if (config.metadata_enabled) { - debug(1,"Avahi with metadata"); - ret = avahi_entry_group_add_service(group, - AVAHI_IF_UNSPEC, - AVAHI_PROTO_UNSPEC, - 0, - name, - "_raop._tcp", - NULL, - NULL, - port, - MDNS_RECORD_WITH_METADATA, - NULL); - } - else { + if (config.metadata_enabled) { + debug(1, "Avahi with metadata"); + ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, name, + "_raop._tcp", NULL, NULL, port, MDNS_RECORD_WITH_METADATA, + NULL); + } else { #endif - debug(1,"Avahi without metadata"); - ret = avahi_entry_group_add_service(group, - AVAHI_IF_UNSPEC, - AVAHI_PROTO_UNSPEC, - 0, - name, - "_raop._tcp", - NULL, - NULL, - port, - MDNS_RECORD_WITHOUT_METADATA, - NULL); -#ifdef CONFIG_METADATA - } + debug(1, "Avahi without metadata"); + ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, name, + "_raop._tcp", NULL, NULL, port, + MDNS_RECORD_WITHOUT_METADATA, NULL); +#ifdef CONFIG_METADATA + } #endif - - if (ret < 0) - die("avahi_entry_group_add_service failed"); - ret = avahi_entry_group_commit(group); - if (ret < 0) - die("avahi_entry_group_commit failed"); + if (ret < 0) + die("avahi_entry_group_add_service failed"); + + ret = avahi_entry_group_commit(group); + if (ret < 0) + die("avahi_entry_group_commit failed"); } -static void client_callback(AvahiClient *c, - AvahiClientState state, - AVAHI_GCC_UNUSED void * userdata) { - switch (state) { - case AVAHI_CLIENT_S_REGISTERING: - if (group) - avahi_entry_group_reset(group); - break; +static void client_callback(AvahiClient *c, AvahiClientState state, + AVAHI_GCC_UNUSED void *userdata) { + switch (state) { + case AVAHI_CLIENT_S_REGISTERING: + if (group) + avahi_entry_group_reset(group); + break; - case AVAHI_CLIENT_S_RUNNING: - register_service(c); - break; + case AVAHI_CLIENT_S_RUNNING: + register_service(c); + break; - case AVAHI_CLIENT_FAILURE: - case AVAHI_CLIENT_S_COLLISION: - die("avahi client failure"); + case AVAHI_CLIENT_FAILURE: + case AVAHI_CLIENT_S_COLLISION: + die("avahi client failure"); - case AVAHI_CLIENT_CONNECTING: - break; - } + case AVAHI_CLIENT_CONNECTING: + break; + } } static int avahi_register(char *srvname, int srvport) { - debug(1, "avahi: avahi_register."); - name = strdup(srvname); - port = srvport; + debug(1, "avahi: avahi_register."); + name = strdup(srvname); + port = srvport; - int err; - if (!(tpoll = avahi_threaded_poll_new())) { - warn("couldn't create avahi threaded tpoll!"); - return -1; - } - if (!(client = avahi_client_new(avahi_threaded_poll_get(tpoll), - 0, - client_callback, - NULL, - &err))) { - warn("couldn't create avahi client: %s!", avahi_strerror(err)); - return -1; - } + int err; + if (!(tpoll = avahi_threaded_poll_new())) { + warn("couldn't create avahi threaded tpoll!"); + return -1; + } + if (!(client = + avahi_client_new(avahi_threaded_poll_get(tpoll), 0, client_callback, NULL, &err))) { + warn("couldn't create avahi client: %s!", avahi_strerror(err)); + return -1; + } - if (avahi_threaded_poll_start(tpoll) < 0) { - warn("couldn't start avahi tpoll thread"); - return -1; - } + if (avahi_threaded_poll_start(tpoll) < 0) { + warn("couldn't start avahi tpoll thread"); + return -1; + } - return 0; + return 0; } static void avahi_unregister(void) { - debug(1, "avahi: avahi_unregister."); - if (tpoll) - avahi_threaded_poll_stop(tpoll); - tpoll = NULL; + debug(1, "avahi: avahi_unregister."); + if (tpoll) + avahi_threaded_poll_stop(tpoll); + tpoll = NULL; - if (name) - free(name); - name = NULL; + if (name) + free(name); + name = NULL; } -mdns_backend mdns_avahi = -{ - .name = "avahi", - .mdns_register = avahi_register, - .mdns_unregister = avahi_unregister -}; - +mdns_backend mdns_avahi = { + .name = "avahi", .mdns_register = avahi_register, .mdns_unregister = avahi_unregister}; diff --git a/mdns_dns_sd.c b/mdns_dns_sd.c index 4f586b93..922c2fb5 100644 --- a/mdns_dns_sd.c +++ b/mdns_dns_sd.c @@ -32,79 +32,62 @@ static DNSServiceRef service; static int mdns_dns_sd_register(char *apname, int port) { - const char *recordwithoutmetadata[] = { MDNS_RECORD_WITHOUT_METADATA, NULL }; + const char *recordwithoutmetadata[] = {MDNS_RECORD_WITHOUT_METADATA, NULL}; #ifdef CONFIG_METADATA - const char *recordwithmetadata[] = { MDNS_RECORD_WITH_METADATA, NULL }; -#endif - char **record; -#ifdef CONFIG_METADATA - if (config.meta_dir) - record = recordwithmetadata; - else + const char *recordwithmetadata[] = {MDNS_RECORD_WITH_METADATA, NULL}; #endif - record = recordwithoutmetadata; - - uint16_t length = 0; - const char **field; + char **record; +#ifdef CONFIG_METADATA + if (config.meta_dir) + record = recordwithmetadata; + else +#endif + record = recordwithoutmetadata; - // Concatenate string contained i record into buf. + uint16_t length = 0; + const char **field; - for (field = record; *field; field ++) - { - length += strlen(*field) + 1; // One byte for length each time - } + // Concatenate string contained i record into buf. - char *buf = malloc(length * sizeof(char)); - if (buf == NULL) - { - warn("dns_sd: buffer record allocation failed"); - return -1; - } + for (field = record; *field; field++) { + length += strlen(*field) + 1; // One byte for length each time + } - char *p = buf; + char *buf = malloc(length * sizeof(char)); + if (buf == NULL) { + warn("dns_sd: buffer record allocation failed"); + return -1; + } - for (field = record; *field; field ++) - { - char * newp = stpcpy(p + 1, *field); - *p = newp - p - 1; - p = newp; - } + char *p = buf; - DNSServiceErrorType error; - error = DNSServiceRegister(&service, - 0, - kDNSServiceInterfaceIndexAny, - apname, - "_raop._tcp", - "", - NULL, - htons((uint16_t)port), - length, - buf, - NULL, - NULL); + for (field = record; *field; field++) { + char *newp = stpcpy(p + 1, *field); + *p = newp - p - 1; + p = newp; + } - free(buf); + DNSServiceErrorType error; + error = DNSServiceRegister(&service, 0, kDNSServiceInterfaceIndexAny, apname, "_raop._tcp", "", + NULL, htons((uint16_t)port), length, buf, NULL, NULL); - if (error == kDNSServiceErr_NoError) - return 0; - else - { - warn("dns-sd: DNSServiceRegister error %d", error); - return -1; - } + free(buf); + + if (error == kDNSServiceErr_NoError) + return 0; + else { + warn("dns-sd: DNSServiceRegister error %d", error); + return -1; + } } static void mdns_dns_sd_unregister(void) { - if (service) - { - DNSServiceRefDeallocate(service); - service = NULL; - } + if (service) { + DNSServiceRefDeallocate(service); + service = NULL; + } } -mdns_backend mdns_dns_sd = { - .name = "dns-sd", - .mdns_register = mdns_dns_sd_register, - .mdns_unregister = mdns_dns_sd_unregister -}; +mdns_backend mdns_dns_sd = {.name = "dns-sd", + .mdns_register = mdns_dns_sd_register, + .mdns_unregister = mdns_dns_sd_unregister}; diff --git a/mdns_external.c b/mdns_external.c index 48284e55..f9ba20c6 100644 --- a/mdns_external.c +++ b/mdns_external.c @@ -41,145 +41,128 @@ int mdns_pid = 0; * Check errno for error details. */ static int fork_execvp(const char *file, char *const argv[]) { - int execpipe[2]; - int pid = 0; - if (pipe(execpipe) < 0) { - return -1; - } + int execpipe[2]; + int pid = 0; + if (pipe(execpipe) < 0) { + return -1; + } - if (fcntl(execpipe[1], F_SETFD, fcntl(execpipe[1], F_GETFD) | FD_CLOEXEC) < 0) { - close(execpipe[0]); - close(execpipe[1]); - return -1; - } + if (fcntl(execpipe[1], F_SETFD, fcntl(execpipe[1], F_GETFD) | FD_CLOEXEC) < 0) { + close(execpipe[0]); + close(execpipe[1]); + return -1; + } - pid = fork(); - if (pid < 0) { - close(execpipe[0]); - close(execpipe[1]); - return -1; - } - else if(pid == 0) { // Child - close(execpipe[0]); // Close the read end - execvp(file, argv); + pid = fork(); + if (pid < 0) { + close(execpipe[0]); + close(execpipe[1]); + return -1; + } else if (pid == 0) { // Child + close(execpipe[0]); // Close the read end + execvp(file, argv); - // If we reach this point then execve has failed. - // Write erno's value into the pipe and exit. - int ignore = write(execpipe[1], &errno, sizeof(errno)); + // If we reach this point then execve has failed. + // Write erno's value into the pipe and exit. + int ignore = write(execpipe[1], &errno, sizeof(errno)); - _exit(-1); - return 0; // Just to make the compiler happy. - } - else { // Parent - close(execpipe[1]); // Close the write end + _exit(-1); + return 0; // Just to make the compiler happy. + } else { // Parent + close(execpipe[1]); // Close the write end - int childErrno; - // Block until child closes the pipe or sends errno. - if(read(execpipe[0], &childErrno, sizeof(childErrno)) == sizeof(childErrno)) { // We received errno - errno = childErrno; - return -1; - } - else { // Child closed the pipe. execvp was successful. - return pid; - } + int childErrno; + // Block until child closes the pipe or sends errno. + if (read(execpipe[0], &childErrno, sizeof(childErrno)) == + sizeof(childErrno)) { // We received errno + errno = childErrno; + return -1; + } else { // Child closed the pipe. execvp was successful. + return pid; } + } } static int mdns_external_avahi_register(char *apname, int port) { - char mdns_port[6]; - sprintf(mdns_port, "%d", config.port); + char mdns_port[6]; + sprintf(mdns_port, "%d", config.port); - char *argvwithoutmetadata[] = { - NULL, apname, "_raop._tcp", mdns_port, MDNS_RECORD_WITHOUT_METADATA, NULL - }; + char *argvwithoutmetadata[] = {NULL, apname, "_raop._tcp", mdns_port, + MDNS_RECORD_WITHOUT_METADATA, NULL}; #ifdef CONFIG_METADATA - char *argvwithmetadata[] = { - NULL, apname, "_raop._tcp", mdns_port, MDNS_RECORD_WITH_METADATA, NULL - }; + char *argvwithmetadata[] = {NULL, apname, "_raop._tcp", mdns_port, MDNS_RECORD_WITH_METADATA, + NULL}; #endif - char **argv; + char **argv; #ifdef CONFIG_METADATA - if (config.metadata_enabled) - argv=argvwithmetadata; - else + if (config.metadata_enabled) + argv = argvwithmetadata; + else #endif - argv=argvwithoutmetadata; - - argv[0] = "avahi-publish-service"; - int pid = fork_execvp(argv[0], argv); - if (pid >= 0) - { - mdns_pid = pid; - return 0; - } - else - warn("Calling %s failed !", argv[0]); + argv = argvwithoutmetadata; - argv[0] = "mDNSPublish"; - pid = fork_execvp(argv[0], argv); - if (pid >= 0) - { - mdns_pid = pid; - return 0; - } - else - warn("Calling %s failed !", argv[0]); - - // If we reach here, both execvp calls failed. - return -1; + argv[0] = "avahi-publish-service"; + int pid = fork_execvp(argv[0], argv); + if (pid >= 0) { + mdns_pid = pid; + return 0; + } else + warn("Calling %s failed !", argv[0]); + + argv[0] = "mDNSPublish"; + pid = fork_execvp(argv[0], argv); + if (pid >= 0) { + mdns_pid = pid; + return 0; + } else + warn("Calling %s failed !", argv[0]); + + // If we reach here, both execvp calls failed. + return -1; } static int mdns_external_dns_sd_register(char *apname, int port) { - char mdns_port[6]; - sprintf(mdns_port, "%d", config.port); + char mdns_port[6]; + sprintf(mdns_port, "%d", config.port); - char *argvwithoutmetadata[] = { - NULL, apname, "_raop._tcp", mdns_port, MDNS_RECORD_WITHOUT_METADATA, NULL - }; + char *argvwithoutmetadata[] = {NULL, apname, "_raop._tcp", mdns_port, + MDNS_RECORD_WITHOUT_METADATA, NULL}; #ifdef CONFIG_METADATA - char *argvwithmetadata[] = { - NULL, apname, "_raop._tcp", mdns_port, MDNS_RECORD_WITH_METADATA, NULL - }; + char *argvwithmetadata[] = {NULL, apname, "_raop._tcp", mdns_port, MDNS_RECORD_WITH_METADATA, + NULL}; #endif - char **argv; + char **argv; #ifdef CONFIG_METADATA - if (config.metadata_enabled) - argv=argvwithmetadata; - else + if (config.metadata_enabled) + argv = argvwithmetadata; + else #endif - argv=argvwithoutmetadata; + argv = argvwithoutmetadata; - int pid = fork_execvp(argv[0], argv); - if (pid >= 0) - { - mdns_pid = pid; - return 0; - } - else - warn("Calling %s failed !", argv[0]); + int pid = fork_execvp(argv[0], argv); + if (pid >= 0) { + mdns_pid = pid; + return 0; + } else + warn("Calling %s failed !", argv[0]); - return -1; + return -1; } static void kill_mdns_child(void) { - if (mdns_pid) - kill(mdns_pid, SIGTERM); - mdns_pid = 0; + if (mdns_pid) + kill(mdns_pid, SIGTERM); + mdns_pid = 0; } -mdns_backend mdns_external_avahi = { - .name = "external-avahi", - .mdns_register = mdns_external_avahi_register, - .mdns_unregister = kill_mdns_child -}; - -mdns_backend mdns_external_dns_sd = { - .name = "external-dns-sd", - .mdns_register = mdns_external_dns_sd_register, - .mdns_unregister = kill_mdns_child -}; +mdns_backend mdns_external_avahi = {.name = "external-avahi", + .mdns_register = mdns_external_avahi_register, + .mdns_unregister = kill_mdns_child}; +mdns_backend mdns_external_dns_sd = {.name = "external-dns-sd", + .mdns_register = mdns_external_dns_sd_register, + .mdns_unregister = kill_mdns_child}; diff --git a/mdns_tinysvcmdns.c b/mdns_tinysvcmdns.c index 8b79d49c..a3c475a2 100644 --- a/mdns_tinysvcmdns.c +++ b/mdns_tinysvcmdns.c @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include #include #include @@ -39,128 +39,108 @@ static struct mdnsd *svr = NULL; static int mdns_tinysvcmdns_register(char *apname, int port) { - struct ifaddrs *ifalist; - struct ifaddrs *ifa; + struct ifaddrs *ifalist; + struct ifaddrs *ifa; - svr = mdnsd_start(); - if (svr == NULL) { - warn("tinysvcmdns: mdnsd_start() failed"); - return -1; + svr = mdnsd_start(); + if (svr == NULL) { + warn("tinysvcmdns: mdnsd_start() failed"); + return -1; + } + + // Thanks to Paul Lietar for this + // room for name + .local + NULL + char hostname[100 + 6]; + gethostname(hostname, 99); + // according to POSIX, this may be truncated without a final NULL ! + hostname[99] = 0; + + // will not work if the hostname doesn't end in .local + char *hostend = hostname + strlen(hostname); + if ((strlen(hostname) < strlen(".local")) || (strcmp(hostend - 6, ".local") != 0)) { + strcat(hostname, ".local"); + } + + if (getifaddrs(&ifalist) < 0) { + warn("tinysvcmdns: getifaddrs() failed"); + return -1; + } + + ifa = ifalist; + + // Look for an ipv4/ipv6 non-loopback interface to use as the main one. + for (ifa = ifalist; ifa != NULL; ifa = ifa->ifa_next) { + if (!(ifa->ifa_flags & IFF_LOOPBACK) && ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET) { + uint32_t main_ip = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr; + + mdnsd_set_hostname(svr, hostname, main_ip); // TTL should be 120 seconds + break; + } else if (!(ifa->ifa_flags & IFF_LOOPBACK) && ifa->ifa_addr && + ifa->ifa_addr->sa_family == AF_INET6) { + struct in6_addr *addr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; + + mdnsd_set_hostname_v6(svr, hostname, addr); // TTL should be 120 seconds + break; } + } - // Thanks to Paul Lietar for this - // room for name + .local + NULL - char hostname[100 + 6]; - gethostname(hostname, 99); - // according to POSIX, this may be truncated without a final NULL ! - hostname[99] = 0; + if (ifa == NULL) { + warn("tinysvcmdns: no non-loopback ipv4 or ipv6 interface found"); + return -1; + } - // will not work if the hostname doesn't end in .local - char *hostend = hostname + strlen(hostname); - if ((strlen(hostname) < strlen(".local")) || (strcmp(hostend - 6, ".local")!=0)) { - strcat(hostname, ".local"); - } - - if (getifaddrs(&ifalist) < 0) - { - warn("tinysvcmdns: getifaddrs() failed"); - return -1; + // Skip the first one, it was already added by set_hostname + for (ifa = ifa->ifa_next; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_flags & IFF_LOOPBACK) // Skip loop-back interfaces + continue; + + switch (ifa->ifa_addr->sa_family) { + case AF_INET: { // ipv4 + uint32_t ip = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr; + struct rr_entry *a_e = rr_create_a(create_nlabel(hostname), ip); // TTL should be 120 seconds + mdnsd_add_rr(svr, a_e); + } break; + case AF_INET6: { // ipv6 + struct in6_addr *addr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; + struct rr_entry *aaaa_e = + rr_create_aaaa(create_nlabel(hostname), addr); // TTL should be 120 seconds + mdnsd_add_rr(svr, aaaa_e); + } break; } + } - ifa = ifalist; + freeifaddrs(ifa); - // Look for an ipv4/ipv6 non-loopback interface to use as the main one. - for (ifa = ifalist; ifa != NULL; ifa = ifa->ifa_next) - { - if (!(ifa->ifa_flags & IFF_LOOPBACK) && ifa->ifa_addr && - ifa->ifa_addr->sa_family == AF_INET) - { - uint32_t main_ip = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr; - - mdnsd_set_hostname(svr, hostname, main_ip); // TTL should be 120 seconds - break; - } - else if (!(ifa->ifa_flags & IFF_LOOPBACK) && ifa->ifa_addr && - ifa->ifa_addr->sa_family == AF_INET6) - { - struct in6_addr *addr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; - - mdnsd_set_hostname_v6(svr, hostname, addr); // TTL should be 120 seconds - break; - } - } - - if (ifa == NULL) - { - warn("tinysvcmdns: no non-loopback ipv4 or ipv6 interface found"); - return -1; - } - - - // Skip the first one, it was already added by set_hostname - for (ifa = ifa->ifa_next; ifa != NULL; ifa = ifa->ifa_next) - { - if (ifa->ifa_flags & IFF_LOOPBACK) // Skip loop-back interfaces - continue; - - switch (ifa->ifa_addr->sa_family) - { - case AF_INET: { // ipv4 - uint32_t ip = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr; - struct rr_entry *a_e = rr_create_a(create_nlabel(hostname), ip); // TTL should be 120 seconds - mdnsd_add_rr(svr, a_e); - } - break; - case AF_INET6: { // ipv6 - struct in6_addr *addr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; - struct rr_entry *aaaa_e = rr_create_aaaa(create_nlabel(hostname), addr); // TTL should be 120 seconds - mdnsd_add_rr(svr, aaaa_e); - } - break; - } - } - - freeifaddrs(ifa); - - char *txtwithoutmetadata[] = { MDNS_RECORD_WITHOUT_METADATA, NULL }; + char *txtwithoutmetadata[] = {MDNS_RECORD_WITHOUT_METADATA, NULL}; #ifdef CONFIG_METADATA - char *txtwithmetadata[] = { MDNS_RECORD_WITH_METADATA, NULL }; -#endif - char **txt; + char *txtwithmetadata[] = {MDNS_RECORD_WITH_METADATA, NULL}; +#endif + char **txt; -#ifdef CONFIG_METADATA - if (config.meta_dir) - txt = txtwithmetadata; - else +#ifdef CONFIG_METADATA + if (config.meta_dir) + txt = txtwithmetadata; + else #endif - txt = txtwithoutmetadata; + txt = txtwithoutmetadata; - - - struct mdns_service *svc = mdnsd_register_svc(svr, - apname, - "_raop._tcp.local", - port, - NULL, - (const char **)txt); // TTL should be 75 minutes, i.e. 4500 seconds + struct mdns_service *svc = + mdnsd_register_svc(svr, apname, "_raop._tcp.local", port, NULL, + (const char **)txt); // TTL should be 75 minutes, i.e. 4500 seconds - mdns_service_destroy(svc); + mdns_service_destroy(svc); - return 0; + return 0; } static void mdns_tinysvcmdns_unregister(void) { - if (svr) - { - mdnsd_stop(svr); - svr = NULL; - } + if (svr) { + mdnsd_stop(svr); + svr = NULL; + } } -mdns_backend mdns_tinysvcmdns = { - .name = "tinysvcmdns", - .mdns_register = mdns_tinysvcmdns_register, - .mdns_unregister = mdns_tinysvcmdns_unregister -}; - +mdns_backend mdns_tinysvcmdns = {.name = "tinysvcmdns", + .mdns_register = mdns_tinysvcmdns_register, + .mdns_unregister = mdns_tinysvcmdns_unregister}; diff --git a/player.c b/player.c index a7e01c92..6d28d785 100644 --- a/player.c +++ b/player.c @@ -72,18 +72,19 @@ static AES_KEY aes; #endif static int sampling_rate, frame_size; -#define FRAME_BYTES(frame_size) (4*frame_size) +#define FRAME_BYTES(frame_size) (4 * frame_size) // maximal resampling shift - conservative -#define OUTFRAME_BYTES(frame_size) (4*(frame_size+3)) +#define OUTFRAME_BYTES(frame_size) (4 * (frame_size + 3)) -#ifdef HAVE_LIBPOLARSSL +#ifdef HAVE_LIBPOLARSSL static aes_context dctx; #endif static pthread_t player_thread; static int please_stop; -static int connection_state_to_output; // if true, then play incoming stuff; if false drop everything +static int + connection_state_to_output; // if true, then play incoming stuff; if false drop everything static alac_file *decoder_info; @@ -99,13 +100,13 @@ static pthread_mutex_t vol_mutex = PTHREAD_MUTEX_INITIALIZER; // default buffer size // needs to be a power of 2 because of the way BUFIDX(seqno) works -#define BUFFER_FRAMES 512 -#define MAX_PACKET 2048 +#define BUFFER_FRAMES 512 +#define MAX_PACKET 2048 // DAC buffer occupancy stuff #define DAC_BUFFER_QUEUE_MINIMUM_LENGTH 5000 -typedef struct audio_buffer_entry { // decoded audio packets +typedef struct audio_buffer_entry { // decoded audio packets int ready; uint32_t timestamp; seq_t sequence_number; @@ -133,11 +134,11 @@ static int64_t first_packet_time_to_play; // nanoseconds static audio_parameters audio_information; // stats -static uint64_t missing_packets,late_packets,too_late_packets,resend_requests; +static uint64_t missing_packets, late_packets, too_late_packets, resend_requests; static void ab_resync(void) { int i; - for (i=0; i 0; + int32_t d = ORDINATE(b) - ORDINATE(a); + return d > 0; } static inline seq_t seq_sum(seq_t a, seq_t b) { uint32_t p = a & 0xffff; uint32_t q = b & 0x0ffff; - uint32_t r = (a+b) & 0xffff; + uint32_t r = (a + b) & 0xffff; return r; } - // now for 32-bit wrapping in timestamps // this returns true if the second arg is strictly after the first // on the assumption that the gap between them is never greater than (2^31)-1 // Represent a and b in 64 bits static inline int seq32_order(uint32_t a, uint32_t b) { - if (a==b) - return 0; - int64_t A = a & 0xffffffff; - int64_t B = b & 0xffffffff; - int64_t C = B-A; - // if bit 31 is set, it means either b is before (i.e. less than) a or - // b is (2^31)-1 ahead of a. - - // If we assume the gap between b and a should never reach 2 billion, then - // bit 31 == 0 means b is strictly after a + if (a == b) + return 0; + int64_t A = a & 0xffffffff; + int64_t B = b & 0xffffffff; + int64_t C = B - A; + // if bit 31 is set, it means either b is before (i.e. less than) a or + // b is (2^31)-1 ahead of a. + + // If we assume the gap between b and a should never reach 2 billion, then + // bit 31 == 0 means b is strictly after a return (C & 0x80000000) == 0; } - - static void alac_decode(short *dest, uint8_t *buf, int len) { unsigned char packet[MAX_PACKET]; unsigned char packetp[MAX_PACKET]; - assert(len<=MAX_PACKET); + assert(len <= MAX_PACKET); unsigned char iv[16]; int aeslen = len & ~0xf; memcpy(iv, aesiv, sizeof(iv)); - + #ifdef HAVE_LIBPOLARSSL - aes_crypt_cbc(&dctx,AES_DECRYPT,aeslen,iv,buf, packet); + aes_crypt_cbc(&dctx, AES_DECRYPT, aeslen, iv, buf, packet); #endif #ifdef HAVE_LIBSSL AES_cbc_encrypt(buf, packet, aeslen, &aes, iv, AES_DECRYPT); #endif - memcpy(packet+aeslen, buf+aeslen, len-aeslen); + memcpy(packet + aeslen, buf + aeslen, len - aeslen); int outsize; @@ -264,112 +262,115 @@ static int init_decoder(int32_t fmtp[12]) { decoder_info = alac; alac->setinfo_max_samples_per_frame = frame_size; - alac->setinfo_7a = fmtp[2]; + alac->setinfo_7a = fmtp[2]; alac->setinfo_sample_size = sample_size; alac->setinfo_rice_historymult = fmtp[4]; alac->setinfo_rice_initialhistory = fmtp[5]; alac->setinfo_rice_kmodifier = fmtp[6]; - alac->setinfo_7f = fmtp[7]; - alac->setinfo_80 = fmtp[8]; - alac->setinfo_82 = fmtp[9]; - alac->setinfo_86 = fmtp[10]; + alac->setinfo_7f = fmtp[7]; + alac->setinfo_80 = fmtp[8]; + alac->setinfo_82 = fmtp[9]; + alac->setinfo_86 = fmtp[10]; alac->setinfo_8a_rate = fmtp[11]; alac_allocate_buffers(alac); return 0; } -static void free_decoder(void) { - alac_free(decoder_info); -} +static void free_decoder(void) { alac_free(decoder_info); } static void init_buffer(void) { int i; - for (i=0; i(BUFFER_FRAMES*7)/8) - // debug(1,"An interval of %u frames has opened, with ab_read: %u, ab_write: %u and seqno: %u.",seq_diff(ab_read,seqno),ab_read,ab_write,seqno); - int32_t gap = seq_diff(ab_write,PREDECESSOR(seqno))+1; - if (gap<=0) - debug(1,"Unexpected gap size: %d.",gap); - int i; - for (i=0;iready = 0; // to be sure, to be sure - abuf->timestamp = 0; - abuf->sequence_number = 0; - } - // debug(1,"N %d s %u.",seq_diff(ab_write,PREDECESSOR(seqno))+1,ab_write); - abuf = audio_buffer + BUFIDX(seqno); - rtp_request_resend(ab_write,gap); - resend_requests++; - ab_write = SUCCESSOR(seqno); - } else if (seq_order(ab_read, seqno)) { // late but not yet played - late_packets++; - abuf = audio_buffer + BUFIDX(seqno); - } else { // too late. - too_late_packets++; - /* - if (!late_packet_message_sent) { - debug(1, "too-late packet received: %u; ab_read: %u; ab_write: %u.", seqno, ab_read, ab_write); - late_packet_message_sent=1; - } - */ - } - // pthread_mutex_unlock(&ab_mutex); + abuf_t *abuf = 0; - if (abuf) { - alac_decode(abuf->data, data, len); - abuf->ready = 1; - abuf->timestamp = timestamp; - abuf->sequence_number = seqno; - } - - // pthread_mutex_lock(&ab_mutex); - + if (!ab_synced) { + debug(2, "syncing to seqno %u.", seqno); + ab_write = seqno; + ab_read = seqno; + ab_synced = 1; + } + if (ab_write == seqno) { // expected packet + abuf = audio_buffer + BUFIDX(seqno); + ab_write = SUCCESSOR(seqno); + } else if (seq_order(ab_write, seqno)) { // newer than expected + // if (ORDINATE(seqno)>(BUFFER_FRAMES*7)/8) + // debug(1,"An interval of %u frames has opened, with ab_read: %u, ab_write: %u and seqno: + // %u.",seq_diff(ab_read,seqno),ab_read,ab_write,seqno); + int32_t gap = seq_diff(ab_write, PREDECESSOR(seqno)) + 1; + if (gap <= 0) + debug(1, "Unexpected gap size: %d.", gap); + int i; + for (i = 0; i < gap; i++) { + abuf = audio_buffer + BUFIDX(seq_sum(ab_write, i)); + abuf->ready = 0; // to be sure, to be sure + abuf->timestamp = 0; + abuf->sequence_number = 0; + } + // debug(1,"N %d s %u.",seq_diff(ab_write,PREDECESSOR(seqno))+1,ab_write); + abuf = audio_buffer + BUFIDX(seqno); + rtp_request_resend(ab_write, gap); + resend_requests++; + ab_write = SUCCESSOR(seqno); + } else if (seq_order(ab_read, seqno)) { // late but not yet played + late_packets++; + abuf = audio_buffer + BUFIDX(seqno); + } else { // too late. + too_late_packets++; + /* + if (!late_packet_message_sent) { + debug(1, "too-late packet received: %u; ab_read: %u; ab_write: %u.", seqno, ab_read, + ab_write); + late_packet_message_sent=1; + } + */ + } + // pthread_mutex_unlock(&ab_mutex); + + if (abuf) { + alac_decode(abuf->data, data, len); + abuf->ready = 1; + abuf->timestamp = timestamp; + abuf->sequence_number = seqno; + } + + // pthread_mutex_lock(&ab_mutex); } int rc = pthread_cond_signal(&flowcontrol); if (rc) - debug(1,"Error signalling flowcontrol."); - } + debug(1, "Error signalling flowcontrol."); + } pthread_mutex_unlock(&ab_mutex); } - static inline short lcg_rand(void) { static unsigned long lcg_prev = 12345; lcg_prev = lcg_prev * 69069 + 3; @@ -387,7 +388,7 @@ static inline short dithered_vol(short sample) { out += rand_a; out -= rand_b; } - return out>>16; + return out >> 16; } // get the next frame, when available. return 0 if underrun/stream reset. @@ -398,132 +399,161 @@ static abuf_t *buffer_get_frame(void) { abuf_t *abuf = 0; int i; abuf_t *curframe; - + pthread_mutex_lock(&ab_mutex); int wait; int32_t dac_delay = 0; do { // get the time - local_time_now = get_absolute_time_in_fp(); + local_time_now = get_absolute_time_in_fp(); - // if config.timeout (default 120) seconds have elapsed since the last audio packet was received, then we should stop. - // config.timeout of zero means don't check..., but iTunes may be confused by a long gap followed by a resumption... - - if ((time_of_last_audio_packet!=0) && (shutdown_requested==0) && (config.dont_check_timeout==0)) { - uint64_t ct = config.timeout; // go from int to 64-bit int - if ((local_time_now>time_of_last_audio_packet) && (local_time_now-time_of_last_audio_packet>=ct<<32)) { - debug(1,"As Yeats almost said, \"Too long a silence / can make a stone of the heart\""); + // if config.timeout (default 120) seconds have elapsed since the last audio packet was + // received, then we should stop. + // config.timeout of zero means don't check..., but iTunes may be confused by a long gap + // followed by a resumption... + + if ((time_of_last_audio_packet != 0) && (shutdown_requested == 0) && + (config.dont_check_timeout == 0)) { + uint64_t ct = config.timeout; // go from int to 64-bit int + if ((local_time_now > time_of_last_audio_packet) && + (local_time_now - time_of_last_audio_packet >= ct << 32)) { + debug(1, "As Yeats almost said, \"Too long a silence / can make a stone of the heart\""); rtsp_request_shutdown_stream(); - shutdown_requested=1; + shutdown_requested = 1; } } int rco = get_requested_connection_state_to_output(); - + if (connection_state_to_output != rco) { - connection_state_to_output=rco; + connection_state_to_output = rco; // change happening - if (connection_state_to_output==0) { //going off - pthread_mutex_lock(&flush_mutex); - flush_requested=1; - pthread_mutex_unlock(&flush_mutex); + if (connection_state_to_output == 0) { // going off + pthread_mutex_lock(&flush_mutex); + flush_requested = 1; + pthread_mutex_unlock(&flush_mutex); } } - + pthread_mutex_lock(&flush_mutex); - if (flush_requested==1) { + if (flush_requested == 1) { if (config.output->flush) config.output->flush(); ab_resync(); first_packet_timestamp = 0; first_packet_time_to_play = 0; - flush_requested=0; + flush_requested = 0; } pthread_mutex_unlock(&flush_mutex); uint32_t flush_limit = 0; if (ab_synced) { - do { - curframe = audio_buffer + BUFIDX(ab_read); - if (curframe->ready) { - - if (curframe->sequence_number!=ab_read) { - // some kind of sync problem has occurred. - if (BUFIDX(curframe->sequence_number)==BUFIDX(ab_read)) { - // it looks like some kind of aliasing has happened - if (seq_order(ab_read,curframe->sequence_number)) { - ab_read=curframe->sequence_number; - debug(1,"Aliasing of buffer index -- reset."); - } - } else { - debug(1,"Inconsistent sequence numbers detected"); - } - } - - if ((flush_rtp_timestamp!=0) && ((curframe->timestamp==flush_rtp_timestamp) || seq32_order(curframe->timestamp,flush_rtp_timestamp))) { - debug(1,"Dropping flushed packet seqno %u, timestamp %u",curframe->sequence_number,curframe->timestamp); - curframe->ready=0; - flush_limit++; - ab_read=SUCCESSOR(ab_read); - } - if ((flush_rtp_timestamp!=0) && (!seq32_order(curframe->timestamp,flush_rtp_timestamp))) // if we have gone past the flush boundary time - flush_rtp_timestamp=0; - } - } while ((flush_rtp_timestamp!=0) && (flush_limit<=8820) && (curframe->ready==0)); - - if (flush_limit==8820) { - debug(1,"Flush hit the 8820 frame limit!"); - flush_limit=0; - } - + do { + curframe = audio_buffer + BUFIDX(ab_read); + if (curframe->ready) { + + if (curframe->sequence_number != ab_read) { + // some kind of sync problem has occurred. + if (BUFIDX(curframe->sequence_number) == BUFIDX(ab_read)) { + // it looks like some kind of aliasing has happened + if (seq_order(ab_read, curframe->sequence_number)) { + ab_read = curframe->sequence_number; + debug(1, "Aliasing of buffer index -- reset."); + } + } else { + debug(1, "Inconsistent sequence numbers detected"); + } + } + + if ((flush_rtp_timestamp != 0) && + ((curframe->timestamp == flush_rtp_timestamp) || + seq32_order(curframe->timestamp, flush_rtp_timestamp))) { + debug(1, "Dropping flushed packet seqno %u, timestamp %u", curframe->sequence_number, + curframe->timestamp); + curframe->ready = 0; + flush_limit++; + ab_read = SUCCESSOR(ab_read); + } + if ((flush_rtp_timestamp != 0) && + (!seq32_order(curframe->timestamp, + flush_rtp_timestamp))) // if we have gone past the flush boundary time + flush_rtp_timestamp = 0; + } + } while ((flush_rtp_timestamp != 0) && (flush_limit <= 8820) && (curframe->ready == 0)); + + if (flush_limit == 8820) { + debug(1, "Flush hit the 8820 frame limit!"); + flush_limit = 0; + } + curframe = audio_buffer + BUFIDX(ab_read); if (curframe->ready) { - if (ab_buffering) { // if we are getting packets but not yet forwarding them to the player - if (first_packet_timestamp==0) { // if this is the very first packet - // debug(1,"First frame seen, time %u, with %d frames...",curframe->timestamp,seq_diff(ab_read, ab_write)); - uint32_t reference_timestamp; + if (ab_buffering) { // if we are getting packets but not yet forwarding them to the player + if (first_packet_timestamp == 0) { // if this is the very first packet + // debug(1,"First frame seen, time %u, with %d + // frames...",curframe->timestamp,seq_diff(ab_read, ab_write)); + uint32_t reference_timestamp; uint64_t reference_timestamp_time; - get_reference_timestamp_stuff(&reference_timestamp,&reference_timestamp_time); + get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time); if (reference_timestamp) { // if we have a reference time // debug(1,"First frame seen with timestamp..."); - first_packet_timestamp=curframe->timestamp; // we will keep buffering until we are supposed to start playing this - - // Here, calculate when we should start playing. We need to know when to allow the packets to be sent to the player. - // We will send packets of silence from now until that time and then we will send the first packet, - // which will be followed by the subsequent packets. - - // we will get a fix every second or so, which will be stored as a pair consisting of - // the time when the packet with a particular timestamp should be played, neglecting latencies, etc. - - // It probably won't be the timestamp of our first packet, however, so we might have to do some calculations. - - // To calculate when the first packet will be played, we figure out the exact time the packet should - // be played according to its timestamp and the reference time. We then need to add the desired latency, typically 88200 frames. - - // Then we need to offset this by the backend latency offset. For example, if we knew that the audio back end has a latency of 100 ms, we would - // ask for the first packet to be emitted 100 ms earlier than it should, i.e. -4410 frames, so that when it got through the audio back end, - // if would be in sync. To do this, we would give it a latency offset of -100 ms, i.e. -4410 frames. - - int64_t delta = ((int64_t)first_packet_timestamp-(int64_t)reference_timestamp); - - first_packet_time_to_play = reference_timestamp_time+((delta+(int64_t)config.latency+(int64_t)config.audio_backend_latency_offset)<<32)/44100; + first_packet_timestamp = curframe->timestamp; // we will keep buffering until we are + // supposed to start playing this - if (local_time_now>=first_packet_time_to_play) { - debug(1,"First packet is late! It should have played before now. Flushing 0.1 seconds"); - player_flush(first_packet_timestamp+4410); + // Here, calculate when we should start playing. We need to know when to allow the + // packets to be sent to the player. + // We will send packets of silence from now until that time and then we will send the + // first packet, + // which will be followed by the subsequent packets. + + // we will get a fix every second or so, which will be stored as a pair consisting of + // the time when the packet with a particular timestamp should be played, neglecting + // latencies, etc. + + // It probably won't be the timestamp of our first packet, however, so we might have + // to do some calculations. + + // To calculate when the first packet will be played, we figure out the exact time the + // packet should + // be played according to its timestamp and the reference time. We then need to add + // the desired latency, typically 88200 frames. + + // Then we need to offset this by the backend latency offset. For example, if we knew + // that the audio back end has a latency of 100 ms, we would + // ask for the first packet to be emitted 100 ms earlier than it should, i.e. -4410 + // frames, so that when it got through the audio back end, + // if would be in sync. To do this, we would give it a latency offset of -100 ms, i.e. + // -4410 frames. + + int64_t delta = ((int64_t)first_packet_timestamp - (int64_t)reference_timestamp); + + first_packet_time_to_play = + reference_timestamp_time + + ((delta + (int64_t)config.latency + (int64_t)config.audio_backend_latency_offset) + << 32) / + 44100; + + if (local_time_now >= first_packet_time_to_play) { + debug( + 1, + "First packet is late! It should have played before now. Flushing 0.1 seconds"); + player_flush(first_packet_timestamp + 4410); } } - } + } - if (first_packet_time_to_play!=0) { + if (first_packet_time_to_play != 0) { uint32_t filler_size = frame_size; uint32_t max_dac_delay = 4410; filler_size = 4410; // 0.1 second -- the maximum we'll add to the DAC - if (local_time_now>=first_packet_time_to_play) { + if (local_time_now >= first_packet_time_to_play) { // we've gone past the time... - // debug(1,"Run past the exact start time by %llu frames, with time now of %llx, fpttp of %llx and dac_delay of %d and %d packets; flush.",(((tn-first_packet_time_to_play)*44100)>>32)+dac_delay,tn,first_packet_time_to_play,dac_delay,seq_diff(ab_read, ab_write)); - + // debug(1,"Run past the exact start time by %llu frames, with time now of %llx, fpttp + // of %llx and dac_delay of %d and %d packets; + // flush.",(((tn-first_packet_time_to_play)*44100)>>32)+dac_delay,tn,first_packet_time_to_play,dac_delay,seq_diff(ab_read, + // ab_write)); + if (config.output->flush) config.output->flush(); ab_resync(); @@ -532,40 +562,49 @@ static abuf_t *buffer_get_frame(void) { } else { if (config.output->delay) { dac_delay = config.output->delay(); - if (dac_delay==-1) { - debug(1,"Error getting dac_delay in buffer_get_frame."); - dac_delay=0; + if (dac_delay == -1) { + debug(1, "Error getting dac_delay in buffer_get_frame."); + dac_delay = 0; } } else - dac_delay=0; - uint64_t gross_frame_gap = ((first_packet_time_to_play-local_time_now)*44100)>>32; - int64_t exact_frame_gap = gross_frame_gap-dac_delay; - if (exact_frame_gap<=0) { + dac_delay = 0; + uint64_t gross_frame_gap = + ((first_packet_time_to_play - local_time_now) * 44100) >> 32; + int64_t exact_frame_gap = gross_frame_gap - dac_delay; + if (exact_frame_gap <= 0) { // we've gone past the time... - // debug(1,"Run a bit past the exact start time by %lld frames, with time now of %llx, fpttp of %llx and dac_delay of %d and %d packets; flush.",-exact_frame_gap,tn,first_packet_time_to_play,dac_delay,seq_diff(ab_read, ab_write)); + // debug(1,"Run a bit past the exact start time by %lld frames, with time now of + // %llx, fpttp of %llx and dac_delay of %d and %d packets; + // flush.",-exact_frame_gap,tn,first_packet_time_to_play,dac_delay,seq_diff(ab_read, + // ab_write)); if (config.output->flush) config.output->flush(); ab_resync(); first_packet_timestamp = 0; first_packet_time_to_play = 0; } else { - uint32_t fs=filler_size; - if (fs>(max_dac_delay-dac_delay)) - fs=max_dac_delay-dac_delay; - if ((exact_frame_gap<=fs) || (exact_frame_gap<=frame_size*2)) { - fs=exact_frame_gap; - // debug(1,"Exact frame gap is %llu; play %d frames of silence. Dac_delay is %d, with %d packets, ab_read is %04x, ab_write is %04x.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read, ab_write),ab_read,ab_write); + uint32_t fs = filler_size; + if (fs > (max_dac_delay - dac_delay)) + fs = max_dac_delay - dac_delay; + if ((exact_frame_gap <= fs) || (exact_frame_gap <= frame_size * 2)) { + fs = exact_frame_gap; + // debug(1,"Exact frame gap is %llu; play %d frames of silence. Dac_delay is %d, + // with %d packets, ab_read is %04x, ab_write is + // %04x.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read, + // ab_write),ab_read,ab_write); ab_buffering = 0; } signed short *silence; silence = malloc(FRAME_BYTES(fs)); memset(silence, 0, FRAME_BYTES(fs)); - // debug(1,"Exact frame gap is %llu; play %d frames of silence. Dac_delay is %d, with %d packets.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read, ab_write)); + // debug(1,"Exact frame gap is %llu; play %d frames of silence. Dac_delay is %d, + // with %d packets.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read, ab_write)); config.output->play(silence, fs); free(silence); #ifdef CONFIG_METADATA - if (ab_buffering==0) { - send_ssnc_metadata('prsm',NULL,0,0); // "resume", but don't wait if the queue is locked + if (ab_buffering == 0) { + send_ssnc_metadata('prsm', NULL, 0, + 0); // "resume", but don't wait if the queue is locked } #endif } @@ -574,74 +613,79 @@ static abuf_t *buffer_get_frame(void) { } } } - + // Here, we work out whether to release a packet or wait // We release a buffer when the time is right. - - // To work out when the time is right, we need to take account of (1) the actual time the packet should be released, - // (2) the latency requested, (3) the audio backend latency offset and (4) the desired length of the audio backend's buffer - + + // To work out when the time is right, we need to take account of (1) the actual time the packet + // should be released, + // (2) the latency requested, (3) the audio backend latency offset and (4) the desired length of + // the audio backend's buffer + // The time is right if the current time is later or the same as // The packet time + (latency + latency offset - backend_buffer_length). // Note: the last three items are expressed in frames and must be converted to time. - int do_wait = 1; - if ((ab_synced) && (curframe) && (curframe->ready) && (curframe->timestamp)) { - uint32_t reference_timestamp; - uint64_t reference_timestamp_time; - get_reference_timestamp_stuff(&reference_timestamp,&reference_timestamp_time); - if (reference_timestamp) { // if we have a reference time - uint32_t packet_timestamp=curframe->timestamp; - int64_t delta = ((int64_t)packet_timestamp-(int64_t)reference_timestamp); - int64_t offset = (int64_t)config.latency+config.audio_backend_latency_offset-(int64_t)config.audio_backend_buffer_desired_length; - int64_t net_offset = delta+offset; - int64_t time_to_play = reference_timestamp_time; - int64_t net_offset_fp_sec; - if (net_offset>=0) { - net_offset_fp_sec = (net_offset<<32)/44100; - time_to_play+=net_offset_fp_sec; // using the latency requested... - // debug(2,"Net Offset: %lld, adjusted: %lld.",net_offset,net_offset_fp_sec); - } else { - net_offset_fp_sec = ((-net_offset)<<32)/44100; - time_to_play-=net_offset_fp_sec; - // debug(2,"Net Offset: %lld, adjusted: -%lld.",net_offset,net_offset_fp_sec); - } - - if (local_time_now>=time_to_play) { - do_wait = 0; - } - } - } - wait = (ab_buffering || (do_wait!=0) || (!ab_synced)) && (!please_stop); + int do_wait = 1; + if ((ab_synced) && (curframe) && (curframe->ready) && (curframe->timestamp)) { + uint32_t reference_timestamp; + uint64_t reference_timestamp_time; + get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time); + if (reference_timestamp) { // if we have a reference time + uint32_t packet_timestamp = curframe->timestamp; + int64_t delta = ((int64_t)packet_timestamp - (int64_t)reference_timestamp); + int64_t offset = (int64_t)config.latency + config.audio_backend_latency_offset - + (int64_t)config.audio_backend_buffer_desired_length; + int64_t net_offset = delta + offset; + int64_t time_to_play = reference_timestamp_time; + int64_t net_offset_fp_sec; + if (net_offset >= 0) { + net_offset_fp_sec = (net_offset << 32) / 44100; + time_to_play += net_offset_fp_sec; // using the latency requested... + // debug(2,"Net Offset: %lld, adjusted: %lld.",net_offset,net_offset_fp_sec); + } else { + net_offset_fp_sec = ((-net_offset) << 32) / 44100; + time_to_play -= net_offset_fp_sec; + // debug(2,"Net Offset: %lld, adjusted: -%lld.",net_offset,net_offset_fp_sec); + } + + if (local_time_now >= time_to_play) { + do_wait = 0; + } + } + } + wait = (ab_buffering || (do_wait != 0) || (!ab_synced)) && (!please_stop); if (wait) { - uint64_t time_to_wait_for_wakeup_fp = ((uint64_t)1<<32)/44100; // this is time period of one frame - time_to_wait_for_wakeup_fp *= 4*352; // four full 352-frame packets - time_to_wait_for_wakeup_fp /= 3; //four thirds of a packet time - + uint64_t time_to_wait_for_wakeup_fp = + ((uint64_t)1 << 32) / 44100; // this is time period of one frame + time_to_wait_for_wakeup_fp *= 4 * 352; // four full 352-frame packets + time_to_wait_for_wakeup_fp /= 3; // four thirds of a packet time + #ifdef COMPILE_FOR_LINUX - uint64_t time_of_wakeup_fp = local_time_now+time_to_wait_for_wakeup_fp; - uint64_t sec = time_of_wakeup_fp>>32; - uint64_t nsec = ((time_of_wakeup_fp&0xffffffff)*1000000000)>>32; - + uint64_t time_of_wakeup_fp = local_time_now + time_to_wait_for_wakeup_fp; + uint64_t sec = time_of_wakeup_fp >> 32; + uint64_t nsec = ((time_of_wakeup_fp & 0xffffffff) * 1000000000) >> 32; + struct timespec time_of_wakeup; time_of_wakeup.tv_sec = sec; time_of_wakeup.tv_nsec = nsec; - - pthread_cond_timedwait(&flowcontrol,&ab_mutex,&time_of_wakeup); - // int rc = pthread_cond_timedwait(&flowcontrol,&ab_mutex,&time_of_wakeup); - // if (rc!=0) - // debug(1,"pthread_cond_timedwait returned error code %d.",rc); + + pthread_cond_timedwait(&flowcontrol, &ab_mutex, &time_of_wakeup); +// int rc = pthread_cond_timedwait(&flowcontrol,&ab_mutex,&time_of_wakeup); +// if (rc!=0) +// debug(1,"pthread_cond_timedwait returned error code %d.",rc); #endif #ifdef COMPILE_FOR_OSX - uint64_t sec = time_to_wait_for_wakeup_fp>>32;; - uint64_t nsec = ((time_to_wait_for_wakeup_fp&0xffffffff)*1000000000)>>32; + uint64_t sec = time_to_wait_for_wakeup_fp >> 32; + ; + uint64_t nsec = ((time_to_wait_for_wakeup_fp & 0xffffffff) * 1000000000) >> 32; struct timespec time_to_wait; time_to_wait.tv_sec = sec; time_to_wait.tv_nsec = nsec; - pthread_cond_timedwait_relative_np(&flowcontrol,&ab_mutex,&time_to_wait); + pthread_cond_timedwait_relative_np(&flowcontrol, &ab_mutex, &time_to_wait); #endif - } + } } while (wait); if (please_stop) { @@ -653,11 +697,10 @@ static abuf_t *buffer_get_frame(void) { // check if t+8, t+16, t+32, t+64, t+128, ... (buffer_start_fill / 2) // packets have arrived... last-chance resend - - + if (!ab_buffering) { - for (i = 8; i < (seq_diff(ab_read,ab_write) / 2); i = (i * 2)) { - seq_t next = seq_sum(ab_read,i); + for (i = 8; i < (seq_diff(ab_read, ab_write) / 2); i = (i * 2)) { + seq_t next = seq_sum(ab_read, i); abuf = audio_buffer + BUFIDX(next); if (!abuf->ready) { rtp_request_resend(next, 1); @@ -666,15 +709,15 @@ static abuf_t *buffer_get_frame(void) { } } } - + if (!curframe->ready) { // debug(1, " %d. Supplying a silent frame.", read); missing_packets++; memset(curframe->data, 0, FRAME_BYTES(frame_size)); - curframe->timestamp=0; + curframe->timestamp = 0; } curframe->ready = 0; - ab_read=SUCCESSOR(ab_read); + ab_read = SUCCESSOR(ab_read); pthread_mutex_unlock(&ab_mutex); return curframe; } @@ -682,65 +725,66 @@ static abuf_t *buffer_get_frame(void) { static inline short shortmean(short a, short b) { long al = (long)a; long bl = (long)b; - long longmean = (al+bl)/2; + long longmean = (al + bl) / 2; short r = (short)longmean; - if (r!=longmean) - debug(1,"Error calculating average of two shorts"); + if (r != longmean) + debug(1, "Error calculating average of two shorts"); return r; } // stuff: 1 means add 1; 0 means do nothing; -1 means remove 1 static int stuff_buffer_basic(short *inptr, short *outptr, int stuff) { - if ((stuff>1) || (stuff<-1)) { - debug(1,"Stuff argument to stuff_buffer must be from -1 to +1."); - return frame_size; - } - int i; - int stuffsamp = frame_size; - if (stuff) -// stuffsamp = rand() % (frame_size - 1); - stuffsamp = (rand() % (frame_size-2))+1; // ensure there's always a sample before and after the item + if ((stuff > 1) || (stuff < -1)) { + debug(1, "Stuff argument to stuff_buffer must be from -1 to +1."); + return frame_size; + } + int i; + int stuffsamp = frame_size; + if (stuff) + // stuffsamp = rand() % (frame_size - 1); + stuffsamp = + (rand() % (frame_size - 2)) + 1; // ensure there's always a sample before and after the item - pthread_mutex_lock(&vol_mutex); - for (i=0; i> 1); - //*outptr++ = dithered_vol(((long)inptr[-1] + (long)inptr[1]) >> 1); - *outptr++ = dithered_vol(shortmean(inptr[-2],inptr[0])); - *outptr++ = dithered_vol(shortmean(inptr[-1],inptr[1])); - } else if (stuff==-1) { - debug(3, "---------"); - inptr++; - inptr++; - } - for (i=stuffsamp; i> 1); + //*outptr++ = dithered_vol(((long)inptr[-1] + (long)inptr[1]) >> 1); + *outptr++ = dithered_vol(shortmean(inptr[-2], inptr[0])); + *outptr++ = dithered_vol(shortmean(inptr[-1], inptr[1])); + } else if (stuff == -1) { + debug(3, "---------"); + inptr++; + inptr++; } - pthread_mutex_unlock(&vol_mutex); + for (i = stuffsamp; i < frame_size + stuff; i++) { + *outptr++ = dithered_vol(*inptr++); + *outptr++ = dithered_vol(*inptr++); + } + } + pthread_mutex_unlock(&vol_mutex); - return frame_size + stuff; + return frame_size + stuff; } #ifdef HAVE_LIBSOXR // stuff: 1 means add 1; 0 means do nothing; -1 means remove 1 static int stuff_buffer_soxr(short *inptr, short *outptr, int stuff) { - if ((stuff>1) || (stuff<-1)) { - debug(1,"Stuff argument to sox_stuff_buffer must be from -1 to +1."); + if ((stuff > 1) || (stuff < -1)) { + debug(1, "Stuff argument to sox_stuff_buffer must be from -1 to +1."); return frame_size; } int i; - short *ip,*op; - ip=inptr; - op=outptr; - + short *ip, *op; + ip = inptr; + op = outptr; + if (stuff) { // debug(1,"Stuff %d.",stuff); soxr_io_spec_t io_spec; @@ -753,10 +797,10 @@ static int stuff_buffer_soxr(short *inptr, short *outptr, int stuff) { size_t odone; soxr_error_t error = soxr_oneshot(frame_size, frame_size + stuff, 2, /* Rates and # of chans. */ - inptr, frame_size, NULL, /* Input. */ - outptr, frame_size + stuff, &odone, /* Output. */ - &io_spec, /* Input, output and transfer spec. */ - NULL, NULL); /* Default configuration.*/ + inptr, frame_size, NULL, /* Input. */ + outptr, frame_size + stuff, &odone, /* Output. */ + &io_spec, /* Input, output and transfer spec. */ + NULL, NULL); /* Default configuration.*/ if (error) die("soxr error: %s\n", "error: %s\n", soxr_strerror(error)); @@ -765,26 +809,26 @@ static int stuff_buffer_soxr(short *inptr, short *outptr, int stuff) { die("odone = %d!\n", odone); const int gpm = 5; - + // keep the first (dpm) samples, to mitigate the Gibbs phenomenon - for (i=0;itimestamp==0) { + if (inframe->timestamp == 0) { // debug(1,"Player has a supplied silent frame."); - last_seqno_read = (SUCCESSOR(last_seqno_read)&0xffff); //manage the packet out of sequence minder + last_seqno_read = + (SUCCESSOR(last_seqno_read) & 0xffff); // manage the packet out of sequence minder config.output->play(inbuf, frame_size); } else { - // We have a frame of data. We need to see if we want to add or remove a frame from it to keep in sync. + // We have a frame of data. We need to see if we want to add or remove a frame from it to + // keep in sync. // So we calculate the timing error for the first frame in the DAC. // If it's ahead of time, we add one audio frame to this frame to delay a subsequent frame - // If it's late, we remove an audio frame from this frame to bring a subsequent frame forward in time - - at_least_one_frame_seen = 1; - - uint32_t reference_timestamp; - uint64_t reference_timestamp_time; - get_reference_timestamp_stuff(&reference_timestamp,&reference_timestamp_time); + // If it's late, we remove an audio frame from this frame to bring a subsequent frame + // forward in time - int64_t rt,nt; + at_least_one_frame_seen = 1; + + uint32_t reference_timestamp; + uint64_t reference_timestamp_time; + get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time); + + int64_t rt, nt; rt = reference_timestamp; nt = inframe->timestamp; - + uint64_t local_time_now = get_absolute_time_in_fp(); - //struct timespec tn; - //clock_gettime(CLOCK_MONOTONIC,&tn); - //uint64_t local_time_now=((uint64_t)tn.tv_sec<<32)+((uint64_t)tn.tv_nsec<<32)/1000000000; + // struct timespec tn; + // clock_gettime(CLOCK_MONOTONIC,&tn); + // uint64_t + // local_time_now=((uint64_t)tn.tv_sec<<32)+((uint64_t)tn.tv_nsec<<32)/1000000000; int64_t td_in_frames; - int64_t td = local_time_now-reference_timestamp_time; - // debug(1,"td is %lld.",td); - if (td>=0) { - td_in_frames = (td*44100)>>32; + int64_t td = local_time_now - reference_timestamp_time; + // debug(1,"td is %lld.",td); + if (td >= 0) { + td_in_frames = (td * 44100) >> 32; } else { - td_in_frames = -((-td*44100)>>32); + td_in_frames = -((-td * 44100) >> 32); } - + // This is the timing error for the next audio frame in the DAC, if applicable - int64_t sync_error =0; - + int64_t sync_error = 0; + int amount_to_stuff = 0; - + // check sequencing - if (last_seqno_read==-1) - last_seqno_read=inframe->sequence_number; + if (last_seqno_read == -1) + last_seqno_read = inframe->sequence_number; else { - last_seqno_read = (SUCCESSOR(last_seqno_read) & 0xffff); - if (inframe->sequence_number!=last_seqno_read) { - debug(1,"Player: packets out of sequence: expected: %d, got: %d, sync error: %d frames.",last_seqno_read,inframe->sequence_number,sync_error); - last_seqno_read=inframe->sequence_number; // reset warning... + last_seqno_read = (SUCCESSOR(last_seqno_read) & 0xffff); + if (inframe->sequence_number != last_seqno_read) { + debug( + 1, + "Player: packets out of sequence: expected: %d, got: %d, sync error: %d frames.", + last_seqno_read, inframe->sequence_number, sync_error); + last_seqno_read = inframe->sequence_number; // reset warning... } } - if (config.output->delay) { current_delay = config.output->delay(); - if (current_delay==-1) { - debug(1,"Delay error when checking running latency."); - current_delay=0; + if (current_delay == -1) { + debug(1, "Delay error when checking running latency."); + current_delay = 0; } - if (current_delaymaximum_buffer_occupancy) - maximum_buffer_occupancy=bo; + uint32_t bo = seq_diff(ab_read, ab_write); + + if (bo < minimum_buffer_occupancy) + minimum_buffer_occupancy = bo; + + if (bo > maximum_buffer_occupancy) + maximum_buffer_occupancy = bo; + + // this is the actual delay, including the latency we actually want, which will + // fluctuate a good bit about a potentially rising or falling trend. + int64_t delay = td_in_frames + rt - (nt - current_delay); - // this is the actual delay, including the latency we actually want, which will fluctuate a good bit about a potentially rising or falling trend. - int64_t delay = td_in_frames+rt-(nt-current_delay); - // This is the timing error for the next audio frame in the DAC. - sync_error = delay-config.latency; - + sync_error = delay - config.latency; + // before we finally commit to this frame, check its sequencing and timing - + // require a certain error before bothering to fix it... - if (sync_error>config.tolerance) { + if (sync_error > config.tolerance) { amount_to_stuff = -1; } - if (sync_error<-config.tolerance) { + if (sync_error < -config.tolerance) { amount_to_stuff = 1; } - + // only allow stuffing if there is enough time to do it -- check DAC buffer... - if (current_delay352) - amount_to_stuff=0; + uint32_t x = random() % 1000; + if (x > 352) + amount_to_stuff = 0; } - - if ((amount_to_stuff==0) && (fix_volume==0x10000)) { + + if ((amount_to_stuff == 0) && (fix_volume == 0x10000)) { // if no stuffing needed and no volume adjustment, then - // don't send to stuff_buffer_* and don't copy to outbuf; just send directly to the output device... + // don't send to stuff_buffer_* and don't copy to outbuf; just send directly to the + // output device... config.output->play(inbuf, frame_size); } else { #ifdef HAVE_LIBSOXR switch (config.packet_stuffing) { - case ST_basic: -// if (amount_to_stuff) debug(1,"Basic stuff..."); - play_samples = stuff_buffer_basic(inbuf, outbuf,amount_to_stuff); - break; - case ST_soxr: -// if (amount_to_stuff) debug(1,"Soxr stuff..."); - play_samples = stuff_buffer_soxr(inbuf, outbuf,amount_to_stuff); - break; - } + case ST_basic: + // if (amount_to_stuff) debug(1,"Basic stuff..."); + play_samples = stuff_buffer_basic(inbuf, outbuf, amount_to_stuff); + break; + case ST_soxr: + // if (amount_to_stuff) debug(1,"Soxr stuff..."); + play_samples = stuff_buffer_soxr(inbuf, outbuf, amount_to_stuff); + break; + } #else -// if (amount_to_stuff) debug(1,"Standard stuff..."); - play_samples = stuff_buffer_basic(inbuf, outbuf,amount_to_stuff); + // if (amount_to_stuff) debug(1,"Standard stuff..."); + play_samples = stuff_buffer_basic(inbuf, outbuf, amount_to_stuff); #endif - /* - { - int co; - int is_silent=1; - short *p = outbuf; - for (co=0;coplay(outbuf, play_samples); + config.output->play(outbuf, play_samples); } - + // check for loss of sync // timestamp of zero means an inserted silent frame in place of a missing frame - if ((inframe->timestamp!=0) && (!please_stop) && (config.resyncthreshold!=0) && (abs(sync_error)>config.resyncthreshold)) { + if ((inframe->timestamp != 0) && (!please_stop) && (config.resyncthreshold != 0) && + (abs(sync_error) > config.resyncthreshold)) { sync_error_out_of_bounds++; - // debug(1,"Sync error out of bounds: Error: %lld; previous error: %lld; DAC: %lld; timestamp: %llx, time now %llx",sync_error,previous_sync_error,current_delay,inframe->timestamp,local_time_now); - if (sync_error_out_of_bounds>3) { - debug(1,"Lost sync with source for %d consecutive packets -- flushing and resyncing. Error: %lld.",sync_error_out_of_bounds,sync_error); + // debug(1,"Sync error out of bounds: Error: %lld; previous error: %lld; DAC: %lld; + // timestamp: %llx, time now + // %llx",sync_error,previous_sync_error,current_delay,inframe->timestamp,local_time_now); + if (sync_error_out_of_bounds > 3) { + debug(1, "Lost sync with source for %d consecutive packets -- flushing and " + "resyncing. Error: %lld.", + sync_error_out_of_bounds, sync_error); sync_error_out_of_bounds = 0; player_flush(nt); - } + } } else { sync_error_out_of_bounds = 0; } } else { // if there is no delay procedure, there can be no synchronising - if (fix_volume==0x10000) + if (fix_volume == 0x10000) config.output->play(inbuf, frame_size); else { - play_samples = stuff_buffer_basic(inbuf, outbuf,0); + play_samples = stuff_buffer_basic(inbuf, outbuf, 0); config.output->play(outbuf, frame_size); } } - // mark the frame as finished - inframe->timestamp=0; + // mark the frame as finished + inframe->timestamp = 0; inframe->sequence_number = 0; - + // debug(1,"Sync error %lld frames. Amount to stuff %d." ,sync_error,amount_to_stuff); - - // new stats calculation. We want a running average of sync error, drift, adjustment, number of additions+subtractions - - if (number_of_statistics==trend_interval) { + + // new stats calculation. We want a running average of sync error, drift, adjustment, + // number of additions+subtractions + + if (number_of_statistics == trend_interval) { // here we remove the oldest statistical data and take it from the summaries as well - tsum_of_sync_errors-=statistics[oldest_statistic].sync_error; + tsum_of_sync_errors -= statistics[oldest_statistic].sync_error; tsum_of_drifts -= statistics[oldest_statistic].drift; - if (statistics[oldest_statistic].correction>0) - tsum_of_insertions_and_deletions-=statistics[oldest_statistic].correction; + if (statistics[oldest_statistic].correction > 0) + tsum_of_insertions_and_deletions -= statistics[oldest_statistic].correction; else - tsum_of_insertions_and_deletions+=statistics[oldest_statistic].correction; - tsum_of_corrections-=statistics[oldest_statistic].correction; - oldest_statistic=(oldest_statistic+1)%trend_interval; - number_of_statistics--; + tsum_of_insertions_and_deletions += statistics[oldest_statistic].correction; + tsum_of_corrections -= statistics[oldest_statistic].correction; + oldest_statistic = (oldest_statistic + 1) % trend_interval; + number_of_statistics--; } - + statistics[newest_statistic].sync_error = sync_error; statistics[newest_statistic].correction = amount_to_stuff; - if (number_of_statistics==0) + if (number_of_statistics == 0) statistics[newest_statistic].drift = 0; else - statistics[newest_statistic].drift = sync_error-previous_sync_error-previous_correction; + statistics[newest_statistic].drift = + sync_error - previous_sync_error - previous_correction; previous_sync_error = sync_error; previous_correction = amount_to_stuff; tsum_of_sync_errors += sync_error; tsum_of_drifts += statistics[newest_statistic].drift; - if (amount_to_stuff>0) - tsum_of_insertions_and_deletions+=amount_to_stuff; + if (amount_to_stuff > 0) + tsum_of_insertions_and_deletions += amount_to_stuff; else - tsum_of_insertions_and_deletions-=amount_to_stuff; - tsum_of_corrections+=amount_to_stuff; - - newest_statistic=(newest_statistic+1)%trend_interval; + tsum_of_insertions_and_deletions -= amount_to_stuff; + tsum_of_corrections += amount_to_stuff; + + newest_statistic = (newest_statistic + 1) % trend_interval; number_of_statistics++; - } - if (play_number%print_interval==0) { - // we can now calculate running averages for sync error (frames), corrections (ppm), insertions plus deletions (ppm), drift (ppm) - double moving_average_sync_error = (1.0*tsum_of_sync_errors)/number_of_statistics; - double moving_average_correction = (1.0*tsum_of_corrections)/number_of_statistics; - double moving_average_insertions_plus_deletions = (1.0*tsum_of_insertions_and_deletions)/number_of_statistics; - double moving_average_drift = (1.0*tsum_of_drifts)/number_of_statistics; + if (play_number % print_interval == 0) { + // we can now calculate running averages for sync error (frames), corrections (ppm), + // insertions plus deletions (ppm), drift (ppm) + double moving_average_sync_error = (1.0 * tsum_of_sync_errors) / number_of_statistics; + double moving_average_correction = (1.0 * tsum_of_corrections) / number_of_statistics; + double moving_average_insertions_plus_deletions = + (1.0 * tsum_of_insertions_and_deletions) / number_of_statistics; + double moving_average_drift = (1.0 * tsum_of_drifts) / number_of_statistics; // if ((play_number/print_interval)%20==0) if (config.statistics_requested) if (at_least_one_frame_seen) - inform("Sync error: %.1f (frames); net correction: %.1f (ppm); corrections: %.1f (ppm); missing packets %llu; late packets %llu; too late packets %llu; resend requests %llu; min DAC queue size %lli, min and max buffer occupancy %u and %u.", moving_average_sync_error, moving_average_correction*1000000/352, moving_average_insertions_plus_deletions*1000000/352,missing_packets,late_packets,too_late_packets,resend_requests,minimum_dac_queue_size,minimum_buffer_occupancy,maximum_buffer_occupancy); + inform("Sync error: %.1f (frames); net correction: %.1f (ppm); corrections: %.1f " + "(ppm); missing packets %llu; late packets %llu; too late packets %llu; " + "resend requests %llu; min DAC queue size %lli, min and max buffer occupancy " + "%u and %u.", + moving_average_sync_error, moving_average_correction * 1000000 / 352, + moving_average_insertions_plus_deletions * 1000000 / 352, missing_packets, + late_packets, too_late_packets, resend_requests, minimum_dac_queue_size, + minimum_buffer_occupancy, maximum_buffer_occupancy); else inform("No frames received in the last sampling interval."); - minimum_dac_queue_size=1000000; // hack reset - maximum_buffer_occupancy = 0; // can't be less than this + minimum_dac_queue_size = 1000000; // hack reset + maximum_buffer_occupancy = 0; // can't be less than this minimum_buffer_occupancy = BUFFER_FRAMES; // can't be more than this at_least_one_frame_seen = 0; } @@ -1082,29 +1153,33 @@ static void *player_thread_func(void *arg) { return 0; } - // takes the volume as specified by the airplay protocol void player_volume(double f) { -// The volume ranges -144.0 (mute) or -30 -- 0. See http://git.zx2c4.com/Airtunes2/about/#setting-volume -// By examination, the -30 -- 0 range is linear on the slider; i.e. the slider is calibrated in 30 equal increments -// So, we will pass this on without any weighting if we have a hardware mixer, as we expect the mixer to be calibrated in dB. + // The volume ranges -144.0 (mute) or -30 -- 0. See + // http://git.zx2c4.com/Airtunes2/about/#setting-volume + // By examination, the -30 -- 0 range is linear on the slider; i.e. the slider is calibrated in 30 + // equal increments + // So, we will pass this on without any weighting if we have a hardware mixer, as we expect the + // mixer to be calibrated in dB. -// Here, we ask for an attenuation we will apply in software. The dB range of a value from 1 to 65536 is about 48.1 dB (log10 of 65536 is 4.8164). -// Thus, we ask our vol2attn function for an appropriate dB between -48.1 and 0 dB and translate it back to a number. - - double scaled_volume = vol2attn(f,0,-4810); - double linear_volume = pow(10,scaled_volume/1000); - - if(f == -144.0) + // Here, we ask for an attenuation we will apply in software. The dB range of a value from 1 to + // 65536 is about 48.1 dB (log10 of 65536 is 4.8164). + // Thus, we ask our vol2attn function for an appropriate dB between -48.1 and 0 dB and translate + // it back to a number. + + double scaled_volume = vol2attn(f, 0, -4810); + double linear_volume = pow(10, scaled_volume / 1000); + + if (f == -144.0) linear_volume = 0.0; - + if (config.output->volume) { - config.output->volume(f); // volume will be sent as metadata by the config.output device - linear_volume=1.0; // no attenuation needed -- this value is used as a flag to avoid calculations + config.output->volume(f); // volume will be sent as metadata by the config.output device + linear_volume = + 1.0; // no attenuation needed -- this value is used as a flag to avoid calculations } - - + if (config.output->parameters) config.output->parameters(&audio_information); else { @@ -1115,42 +1190,44 @@ void player_volume(double f) { audio_information.has_true_mute = 0; audio_information.is_muted = 0; } - audio_information.valid=1; + audio_information.valid = 1; pthread_mutex_lock(&vol_mutex); - software_mixer_volume = linear_volume; - fix_volume = 65536.0 * software_mixer_volume; + software_mixer_volume = linear_volume; + fix_volume = 65536.0 * software_mixer_volume; pthread_mutex_unlock(&vol_mutex); #ifdef CONFIG_METADATA char *dv = malloc(64); // will be freed in the metadata thread if (dv) { - memset(dv,0,64); - snprintf(dv,63,"%.2f,%.2f,%.2f,%.2f",audio_information.airplay_volume,audio_information.current_volume_dB/100.0,audio_information.minimum_volume_dB/100.0,audio_information.maximum_volume_dB/100.0); - send_ssnc_metadata('pvol',dv,strlen(dv),1); + memset(dv, 0, 64); + snprintf(dv, 63, "%.2f,%.2f,%.2f,%.2f", audio_information.airplay_volume, + audio_information.current_volume_dB / 100.0, + audio_information.minimum_volume_dB / 100.0, + audio_information.maximum_volume_dB / 100.0); + send_ssnc_metadata('pvol', dv, strlen(dv), 1); } #endif - } void player_flush(uint32_t timestamp) { - // debug(1,"Flush requested up to %u. It seems as if 0 is special.",timestamp); + // debug(1,"Flush requested up to %u. It seems as if 0 is special.",timestamp); pthread_mutex_lock(&flush_mutex); - flush_requested=1; - //if (timestamp!=0) - flush_rtp_timestamp=timestamp; // flush all packets up to (and including?) this + flush_requested = 1; + // if (timestamp!=0) + flush_rtp_timestamp = timestamp; // flush all packets up to (and including?) this pthread_mutex_unlock(&flush_mutex); #ifdef CONFIG_METADATA - send_ssnc_metadata('pfls',NULL,0,1); + send_ssnc_metadata('pfls', NULL, 0, 1); #endif } int player_play(stream_cfg *stream) { packet_count = 0; if (config.buffer_start_fill > BUFFER_FRAMES) - die("specified buffer starting fill %d > buffer size %d", - config.buffer_start_fill, BUFFER_FRAMES); + die("specified buffer starting fill %d > buffer size %d", config.buffer_start_fill, + BUFFER_FRAMES); #ifdef HAVE_LIBPOLARSSL - memset(&dctx,0,sizeof(aes_context)); + memset(&dctx, 0, sizeof(aes_context)); aes_setkey_dec(&dctx, stream->aeskey, 128); #endif @@ -1165,21 +1242,21 @@ int player_play(stream_cfg *stream) { please_stop = 0; command_start(); #ifdef CONFIG_METADATA - send_ssnc_metadata('pbeg',NULL,0,1); + send_ssnc_metadata('pbeg', NULL, 0, 1); #endif - - // set the flowcontrol condition variable to wait on a monotonic clock + +// set the flowcontrol condition variable to wait on a monotonic clock #ifdef COMPILE_FOR_LINUX pthread_condattr_t attr; pthread_condattr_init(&attr); - pthread_condattr_setclock( &attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it. - int rc = pthread_cond_init(&flowcontrol,&attr); + pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it. + int rc = pthread_cond_init(&flowcontrol, &attr); #endif #ifdef COMPILE_FOR_OSX - int rc = pthread_cond_init(&flowcontrol,NULL); + int rc = pthread_cond_init(&flowcontrol, NULL); #endif if (rc) - debug(1,"Error initialising condition variable."); + debug(1, "Error initialising condition variable."); config.output->start(sampling_rate); pthread_create(&player_thread, NULL, player_thread_func, NULL); @@ -1191,7 +1268,7 @@ void player_stop(void) { pthread_cond_signal(&flowcontrol); // tell it to give up pthread_join(player_thread, NULL); #ifdef CONFIG_METADATA - send_ssnc_metadata('pend',NULL,0,1); + send_ssnc_metadata('pend', NULL, 0, 1); #endif config.output->stop(); command_stop(); @@ -1199,5 +1276,5 @@ void player_stop(void) { free_decoder(); int rc = pthread_cond_destroy(&flowcontrol); if (rc) - debug(1,"Error destroying condition variable."); + debug(1, "Error destroying condition variable."); } diff --git a/player.h b/player.h index 0a9f82a5..9f4b93d6 100644 --- a/player.h +++ b/player.h @@ -4,8 +4,8 @@ #include "audio.h" typedef struct { - uint8_t aesiv[16], aeskey[16]; - int32_t fmtp[12]; + uint8_t aesiv[16], aeskey[16]; + int32_t fmtp[12]; } stream_cfg; typedef uint16_t seq_t; @@ -19,6 +19,6 @@ void player_stop(void); void player_volume(double f); void player_flush(uint32_t timestamp); -void player_put_packet(seq_t seqno,uint32_t timestamp, uint8_t *data, int len); +void player_put_packet(seq_t seqno, uint32_t timestamp, uint8_t *data, int len); #endif //_PLAYER_H diff --git a/rtp.c b/rtp.c index dea71c3c..5590e649 100644 --- a/rtp.c +++ b/rtp.c @@ -41,10 +41,9 @@ #include "player.h" #include "rtp.h" - typedef struct { - uint32_t seconds; - uint32_t fraction; + uint32_t seconds; + uint32_t fraction; } ntp_timestamp; typedef struct time_ping_record { @@ -57,14 +56,14 @@ static int running = 0; static int please_shutdown; static char client_ip_string[INET6_ADDRSTRLEN]; // the ip string pointing to the client -static short client_ip_family; // AF_INET / AF_INET6 -static uint32_t client_active_remote; // used when you want to control the client... +static short client_ip_family; // AF_INET / AF_INET6 +static uint32_t client_active_remote; // used when you want to control the client... static SOCKADDR rtp_client_control_socket; // a socket pointing to the control port of the client -static SOCKADDR rtp_client_timing_socket; // a socket pointing to the timing port of the client -static int audio_socket; // our local [server] audio socket -static int control_socket; // our local [server] control socket -static int timing_socket; // local timing socket +static SOCKADDR rtp_client_timing_socket; // a socket pointing to the timing port of the client +static int audio_socket; // our local [server] audio socket +static int control_socket; // our local [server] control socket +static int timing_socket; // local timing socket static pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread; static uint32_t reference_timestamp; @@ -73,168 +72,175 @@ static uint64_t reference_timestamp_time; // debug variables static int request_sent; -#define time_ping_history 8 -#define time_ping_fudge_factor 100000 +#define time_ping_history 8 +#define time_ping_fudge_factor 100000 static uint8_t time_ping_count; struct time_ping_record time_pings[time_ping_history]; -//static struct timespec dtt; // dangerous -- this assumes that there will never be two timing request in flight at the same time -static uint64_t departure_time; // dangerous -- this assumes that there will never be two timing request in flight at the same time +// static struct timespec dtt; // dangerous -- this assumes that there will never be two timing +// request in flight at the same time +static uint64_t departure_time; // dangerous -- this assumes that there will never be two timing + // request in flight at the same time static pthread_mutex_t reference_time_mutex = PTHREAD_MUTEX_INITIALIZER; uint64_t static local_to_remote_time_difference; // used to switch between local and remote clocks static void *rtp_audio_receiver(void *arg) { - // we inherit the signal mask (SIGUSR1) - - int32_t last_seqno = -1; - uint8_t packet[2048], *pktp; + // we inherit the signal mask (SIGUSR1) - ssize_t nread; - while (1) { - if (please_shutdown) - break; - nread = recv(audio_socket, packet, sizeof(packet), 0); - if (nread < 0) - break; + int32_t last_seqno = -1; + uint8_t packet[2048], *pktp; - ssize_t plen = nread; - uint8_t type = packet[1] & ~0x80; - if (type == 0x60 || type == 0x56) { // audio data / resend - pktp = packet; - if (type==0x56) { - pktp += 4; - plen -= 4; - } - seq_t seqno = ntohs(*(unsigned short *)(pktp+2)); - // increment last_seqno and see if it's the same as the incoming seqno + ssize_t nread; + while (1) { + if (please_shutdown) + break; + nread = recv(audio_socket, packet, sizeof(packet), 0); + if (nread < 0) + break; - if (last_seqno==-1) - last_seqno=seqno; - else { - last_seqno = (last_seqno+1)&0xffff; - if (seqno!=last_seqno) - debug(2,"RTP: Packets out of sequence: expected: %d, got %d.",last_seqno,seqno); - last_seqno=seqno; // reset warning... - } - uint32_t timestamp = ntohl(*(unsigned long *)(pktp+4)); - - //if (packet[1]&0x10) - // debug(1,"Audio packet Extension bit set."); + ssize_t plen = nread; + uint8_t type = packet[1] & ~0x80; + if (type == 0x60 || type == 0x56) { // audio data / resend + pktp = packet; + if (type == 0x56) { + pktp += 4; + plen -= 4; + } + seq_t seqno = ntohs(*(unsigned short *)(pktp + 2)); + // increment last_seqno and see if it's the same as the incoming seqno - pktp += 12; - plen -= 12; + if (last_seqno == -1) + last_seqno = seqno; + else { + last_seqno = (last_seqno + 1) & 0xffff; + if (seqno != last_seqno) + debug(2, "RTP: Packets out of sequence: expected: %d, got %d.", last_seqno, seqno); + last_seqno = seqno; // reset warning... + } + uint32_t timestamp = ntohl(*(unsigned long *)(pktp + 4)); - // check if packet contains enough content to be reasonable - if (plen >= 16) { - player_put_packet(seqno,timestamp, pktp, plen); - continue; - } - if (type == 0x56 && seqno == 0) { - debug(2, "resend-related request packet received, ignoring."); - continue; - } - debug(1, "Audio receiver -- Unknown RTP packet of type 0x%02X length %d seqno %d", type, nread, seqno); - } - warn("Audio receiver -- Unknown RTP packet of type 0x%02X length %d.", type, nread); + // if (packet[1]&0x10) + // debug(1,"Audio packet Extension bit set."); + + pktp += 12; + plen -= 12; + + // check if packet contains enough content to be reasonable + if (plen >= 16) { + player_put_packet(seqno, timestamp, pktp, plen); + continue; + } + if (type == 0x56 && seqno == 0) { + debug(2, "resend-related request packet received, ignoring."); + continue; + } + debug(1, "Audio receiver -- Unknown RTP packet of type 0x%02X length %d seqno %d", type, + nread, seqno); } + warn("Audio receiver -- Unknown RTP packet of type 0x%02X length %d.", type, nread); + } - debug(1, "Audio receiver -- Server RTP thread interrupted. terminating."); - close(audio_socket); + debug(1, "Audio receiver -- Server RTP thread interrupted. terminating."); + close(audio_socket); - return NULL; + return NULL; } static void *rtp_control_receiver(void *arg) { - // we inherit the signal mask (SIGUSR1) - reference_timestamp=0; // nothing valid received yet - uint8_t packet[2048]; - struct timespec tn; - uint64_t remote_time_of_sync,local_time_now, remote_time_now; - uint32_t sync_rtp_timestamp,rtp_timestamp_less_latency; - ssize_t nread; - while (1) { - if (please_shutdown) - break; - nread = recv(control_socket, packet, sizeof(packet), 0); - local_time_now=get_absolute_time_in_fp(); -// clock_gettime(CLOCK_MONOTONIC,&tn); -// local_time_now=((uint64_t)tn.tv_sec<<32)+((uint64_t)tn.tv_nsec<<32)/1000000000; - - if (nread < 0) - break; + // we inherit the signal mask (SIGUSR1) + reference_timestamp = 0; // nothing valid received yet + uint8_t packet[2048]; + struct timespec tn; + uint64_t remote_time_of_sync, local_time_now, remote_time_now; + uint32_t sync_rtp_timestamp, rtp_timestamp_less_latency; + ssize_t nread; + while (1) { + if (please_shutdown) + break; + nread = recv(control_socket, packet, sizeof(packet), 0); + local_time_now = get_absolute_time_in_fp(); + // clock_gettime(CLOCK_MONOTONIC,&tn); + // local_time_now=((uint64_t)tn.tv_sec<<32)+((uint64_t)tn.tv_nsec<<32)/1000000000; - ssize_t plen = nread; - if (packet[1] == 0xd4) { // sync data - /* - char obf[4096]; - char *obfp = obf; - int obfc; - for (obfc=0;obfc>32); - //debug(1,"Sync Time is %lld us late (local times).",((local_time_now-reference_timestamp_time)*1000000)>>32); - } else { - debug(1,"Sync packet received before we got a timing packet back."); - } - } else - debug(1,"Control Port -- Unknown RTP packet of type 0x%02X length %d.", packet[1], nread); - } + if (nread < 0) + break; - debug(1, "Control RTP thread interrupted. terminating."); - close(control_socket); + ssize_t plen = nread; + if (packet[1] == 0xd4) { // sync data + /* + char obf[4096]; + char *obfp = obf; + int obfc; + for (obfc=0;obfc>32); + // debug(1,"Sync Time is %lld us late (local + // times).",((local_time_now-reference_timestamp_time)*1000000)>>32); + } else { + debug(1, "Sync packet received before we got a timing packet back."); + } + } else + debug(1, "Control Port -- Unknown RTP packet of type 0x%02X length %d.", packet[1], nread); + } + + debug(1, "Control RTP thread interrupted. terminating."); + close(control_socket); + + return NULL; } static void *rtp_timing_sender(void *arg) { - struct timing_request { + struct timing_request { char leader; char type; uint16_t seqno; uint32_t filler; - uint64_t origin,receive,transmit; + uint64_t origin, receive, transmit; }; - - uint64_t request_number=0; - - struct timing_request req; // *not* a standard RTCP NACK - + + uint64_t request_number = 0; + + struct timing_request req; // *not* a standard RTCP NACK + req.leader = 0x80; - req.type = 0xd2; // Timing request + req.type = 0xd2; // Timing request req.filler = 0; - req.seqno=htons(7); - + req.seqno = htons(7); + time_ping_count = 0; // we inherit the signal mask (SIGUSR1) @@ -245,392 +251,404 @@ static void *rtp_timing_sender(void *arg) { if (!running) die("rtp_timing_sender called without active stream!"); - //debug(1, "Requesting ntp timestamp exchange."); + // debug(1, "Requesting ntp timestamp exchange."); req.filler = 0; - req.origin = req.receive = req.transmit=0; + req.origin = req.receive = req.transmit = 0; -// clock_gettime(CLOCK_MONOTONIC,&dtt); - departure_time=get_absolute_time_in_fp(); - socklen_t msgsize = sizeof(struct sockaddr_in); + // clock_gettime(CLOCK_MONOTONIC,&dtt); + departure_time = get_absolute_time_in_fp(); + socklen_t msgsize = sizeof(struct sockaddr_in); #ifdef AF_INET6 - if (rtp_client_timing_socket.SAFAMILY==AF_INET6) { - msgsize = sizeof(struct sockaddr_in6); - } + if (rtp_client_timing_socket.SAFAMILY == AF_INET6) { + msgsize = sizeof(struct sockaddr_in6); + } #endif - if (sendto(timing_socket, &req, sizeof(req), 0, (struct sockaddr*)&rtp_client_timing_socket, msgsize)==-1) { - perror("Error sendto-ing to timing socket"); - } + if (sendto(timing_socket, &req, sizeof(req), 0, (struct sockaddr *)&rtp_client_timing_socket, + msgsize) == -1) { + perror("Error sendto-ing to timing socket"); + } request_number++; - if (request_number<=4) + if (request_number <= 4) usleep(500000); else sleep(3); } - debug(1, "rtp_timing_sender thread interrupted. terminating."); + debug(1, "rtp_timing_sender thread interrupted. terminating."); return NULL; } static void *rtp_timing_receiver(void *arg) { - // we inherit the signal mask (SIGUSR1) - uint8_t packet[2048], *pktp; - ssize_t nread; - pthread_t timer_requester; - pthread_create(&timer_requester, NULL, &rtp_timing_sender, NULL); -// struct timespec att; - uint64_t distant_receive_time,distant_transmit_time,arrival_time,return_time,transit_time,processing_time; - local_to_remote_time_jitters = 0; - local_to_remote_time_jitters_count = 0; - uint64_t l2rtd=0; - while (1) { - if (please_shutdown) - break; - nread = recv(timing_socket, packet, sizeof(packet), 0); - arrival_time=get_absolute_time_in_fp(); -// clock_gettime(CLOCK_MONOTONIC,&att); - - if (nread < 0) - break; + // we inherit the signal mask (SIGUSR1) + uint8_t packet[2048], *pktp; + ssize_t nread; + pthread_t timer_requester; + pthread_create(&timer_requester, NULL, &rtp_timing_sender, NULL); + // struct timespec att; + uint64_t distant_receive_time, distant_transmit_time, arrival_time, return_time, transit_time, + processing_time; + local_to_remote_time_jitters = 0; + local_to_remote_time_jitters_count = 0; + uint64_t l2rtd = 0; + while (1) { + if (please_shutdown) + break; + nread = recv(timing_socket, packet, sizeof(packet), 0); + arrival_time = get_absolute_time_in_fp(); + // clock_gettime(CLOCK_MONOTONIC,&att); - ssize_t plen = nread; - //debug(1,"Packet Received on Timing Port."); - if (packet[1] == 0xd3) { // timing reply - /* - char obf[4096]; - char *obfp = obf; - int obfc; - for (obfc=0;obfc>32; debug(1,"Time ping turnaround time: %lld us.",rtus); - - //distant_receive_time = ((uint64_t)ntohl(*((uint32_t*)&packet[16])))<<32+ntohl(*((uint32_t*)&packet[20])); - - distant_receive_time = (uint64_t)ntohl(*((uint32_t*)&packet[16]))<<32; - distant_receive_time += ntohl(*((uint32_t*)&packet[20])); - - //distant_transmit_time = ((uint64_t)ntohl(*((uint32_t*)&packet[24])))<<32+ntohl(*((uint32_t*)&packet[28])); - - distant_transmit_time = (uint64_t)ntohl(*((uint32_t*)&packet[24]))<<32; - distant_transmit_time += ntohl(*((uint32_t*)&packet[28])); - - processing_time = distant_transmit_time-distant_receive_time; - - // debug(1,"Return trip time: %lluuS, remote processing time: %lluuS.",(return_time*1000000)>>32,(processing_time*1000000)>>32); + ssize_t plen = nread; + // debug(1,"Packet Received on Timing Port."); + if (packet[1] == 0xd3) { // timing reply + /* + char obf[4096]; + char *obfp = obf; + int obfc; + for (obfc=0;obfc0;cc--) { - time_pings[cc]=time_pings[cc-1]; - time_pings[cc].dispersion = (time_pings[cc].dispersion*133)/100; // make the dispersions 'age' by this rational factor - } - time_pings[0].local_to_remote_difference = local_time_by_remote_clock-arrival_time; - time_pings[0].dispersion = return_time; - if (time_ping_count1) { - if (l2rtd>local_to_remote_time_difference) { - local_to_remote_time_jitters=local_to_remote_time_jitters+l2rtd-local_to_remote_time_difference; - ji = l2rtd-local_to_remote_time_difference; - } else { - local_to_remote_time_jitters=local_to_remote_time_jitters+local_to_remote_time_difference-l2rtd; - ji = - (local_to_remote_time_difference-l2rtd); - } - local_to_remote_time_jitters_count+=1; - } - // uncomment below to print jitter between client's clock and oour clock - // int64_t rtus = (tld*1000000)>>32; ji = (ji*1000000)>>32; debug(1,"Choosing time difference with dispersion of %lld us with delta of %lld us",rtus,ji); + return_time = arrival_time - departure_time; - local_to_remote_time_difference=l2rtd; - } else { - debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %d.", packet[1], nread); + // uint64_t rtus = (return_time*1000000)>>32; debug(1,"Time ping turnaround time: %lld + // us.",rtus); + + // distant_receive_time = + // ((uint64_t)ntohl(*((uint32_t*)&packet[16])))<<32+ntohl(*((uint32_t*)&packet[20])); + + distant_receive_time = (uint64_t)ntohl(*((uint32_t *)&packet[16])) << 32; + distant_receive_time += ntohl(*((uint32_t *)&packet[20])); + + // distant_transmit_time = + // ((uint64_t)ntohl(*((uint32_t*)&packet[24])))<<32+ntohl(*((uint32_t*)&packet[28])); + + distant_transmit_time = (uint64_t)ntohl(*((uint32_t *)&packet[24])) << 32; + distant_transmit_time += ntohl(*((uint32_t *)&packet[28])); + + processing_time = distant_transmit_time - distant_receive_time; + + // debug(1,"Return trip time: %lluuS, remote processing time: + // %lluuS.",(return_time*1000000)>>32,(processing_time*1000000)>>32); + + uint64_t local_time_by_remote_clock = distant_transmit_time + return_time / 2; + + unsigned int cc; + for (cc = time_ping_history - 1; cc > 0; cc--) { + time_pings[cc] = time_pings[cc - 1]; + time_pings[cc].dispersion = (time_pings[cc].dispersion * 133) / + 100; // make the dispersions 'age' by this rational factor } + time_pings[0].local_to_remote_difference = local_time_by_remote_clock - arrival_time; + time_pings[0].dispersion = return_time; + if (time_ping_count < time_ping_history) + time_ping_count++; + + // now pick the timestamp with the lowest dispersion + uint64_t l2rtd = time_pings[0].local_to_remote_difference; + uint64_t tld = time_pings[0].dispersion; + for (cc = 1; cc < time_ping_count; cc++) + if (time_pings[cc].dispersion < tld) { + l2rtd = time_pings[cc].local_to_remote_difference; + tld = time_pings[cc].dispersion; + } + int64_t ji; + + if (time_ping_count > 1) { + if (l2rtd > local_to_remote_time_difference) { + local_to_remote_time_jitters = + local_to_remote_time_jitters + l2rtd - local_to_remote_time_difference; + ji = l2rtd - local_to_remote_time_difference; + } else { + local_to_remote_time_jitters = + local_to_remote_time_jitters + local_to_remote_time_difference - l2rtd; + ji = -(local_to_remote_time_difference - l2rtd); + } + local_to_remote_time_jitters_count += 1; + } + // uncomment below to print jitter between client's clock and oour clock + // int64_t rtus = (tld*1000000)>>32; ji = (ji*1000000)>>32; debug(1,"Choosing time difference + // with dispersion of %lld us with delta of %lld us",rtus,ji); + + local_to_remote_time_difference = l2rtd; + } else { + debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %d.", packet[1], nread); } + } - debug(1, "Timing RTP thread interrupted. terminating."); - void *retval; - pthread_kill(timer_requester, SIGUSR1); - pthread_join(timer_requester, &retval); - debug(1,"Closed and terminated timer requester thread."); - debug(1, "Timing RTP thread terminated."); - close(timing_socket); + debug(1, "Timing RTP thread interrupted. terminating."); + void *retval; + pthread_kill(timer_requester, SIGUSR1); + pthread_join(timer_requester, &retval); + debug(1, "Closed and terminated timer requester thread."); + debug(1, "Timing RTP thread terminated."); + close(timing_socket); - return NULL; + return NULL; } -static int bind_port(SOCKADDR *remote,int *sock, int desired_port ) { - struct - addrinfo hints, *info; +static int bind_port(SOCKADDR *remote, int *sock, int desired_port) { + struct addrinfo hints, *info; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = remote->SAFAMILY; - hints.ai_socktype = SOCK_DGRAM; - hints.ai_flags = AI_PASSIVE; - - char buffer[10]; - snprintf(buffer, 10, "%d", desired_port); - - int ret = getaddrinfo(NULL,buffer, &hints, &info); + memset(&hints, 0, sizeof(hints)); + hints.ai_family = remote->SAFAMILY; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE; - if (ret < 0) - die("failed to get usable addrinfo?! %s.", gai_strerror(ret)); + char buffer[10]; + snprintf(buffer, 10, "%d", desired_port); - *sock = socket(remote->SAFAMILY, SOCK_DGRAM, IPPROTO_UDP); - ret = bind(*sock, info->ai_addr, info->ai_addrlen); + int ret = getaddrinfo(NULL, buffer, &hints, &info); - freeaddrinfo(info); + if (ret < 0) + die("failed to get usable addrinfo?! %s.", gai_strerror(ret)); - if (ret < 0) - die("could not bind a UDP port!"); + *sock = socket(remote->SAFAMILY, SOCK_DGRAM, IPPROTO_UDP); + ret = bind(*sock, info->ai_addr, info->ai_addrlen); - int sport; - SOCKADDR local; - socklen_t local_len = sizeof(local); - getsockname(*sock, (struct sockaddr*)&local, &local_len); + freeaddrinfo(info); + + if (ret < 0) + die("could not bind a UDP port!"); + + int sport; + SOCKADDR local; + socklen_t local_len = sizeof(local); + getsockname(*sock, (struct sockaddr *)&local, &local_len); #ifdef AF_INET6 - if (local.SAFAMILY == AF_INET6) { - struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)&local; - sport = ntohs(sa6->sin6_port); - } else + if (local.SAFAMILY == AF_INET6) { + struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&local; + sport = ntohs(sa6->sin6_port); + } else #endif - { - struct sockaddr_in *sa = (struct sockaddr_in*)&local; - sport = ntohs(sa->sin_port); - } + { + struct sockaddr_in *sa = (struct sockaddr_in *)&local; + sport = ntohs(sa->sin_port); + } - return sport; + return sport; } +void rtp_setup(SOCKADDR *remote, int cport, int tport, uint32_t active_remote, int *lsport, + int *lcport, int *ltport) { + if (running) + die("rtp_setup called with active stream!"); -void rtp_setup(SOCKADDR *remote, int cport, int tport, uint32_t active_remote, int *lsport, int *lcport, int *ltport) { - if (running) - die("rtp_setup called with active stream!"); + debug(2, "rtp_setup: cport=%d tport=%d.", cport, tport); - debug(2, "rtp_setup: cport=%d tport=%d.", cport, tport); - - client_active_remote = active_remote; - - // print out what we know about the client - void *addr; - char *ipver; - int port; - char portstr[20]; - client_ip_family = remote->SAFAMILY; // keep information about the kind of ip of the client + client_active_remote = active_remote; + + // print out what we know about the client + void *addr; + char *ipver; + int port; + char portstr[20]; + client_ip_family = remote->SAFAMILY; // keep information about the kind of ip of the client #ifdef AF_INET6 - if (remote->SAFAMILY == AF_INET6) { - struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)remote; - addr = &(sa6->sin6_addr); - port = ntohs(sa6->sin6_port); - ipver = "IPv6"; - } + if (remote->SAFAMILY == AF_INET6) { + struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)remote; + addr = &(sa6->sin6_addr); + port = ntohs(sa6->sin6_port); + ipver = "IPv6"; + } #endif - if (remote->SAFAMILY == AF_INET) { - struct sockaddr_in *sa4 = (struct sockaddr_in*)remote; - addr = &(sa4->sin_addr); - port = ntohs(sa4->sin_port); - ipver = "IPv4"; - } - inet_ntop(remote->SAFAMILY,addr,client_ip_string,sizeof(client_ip_string)); // keep the client's ip number - debug(1,"Connection from %s: %s:%d",ipver,client_ip_string,port); - - + if (remote->SAFAMILY == AF_INET) { + struct sockaddr_in *sa4 = (struct sockaddr_in *)remote; + addr = &(sa4->sin_addr); + port = ntohs(sa4->sin_port); + ipver = "IPv4"; + } + inet_ntop(remote->SAFAMILY, addr, client_ip_string, + sizeof(client_ip_string)); // keep the client's ip number + debug(1, "Connection from %s: %s:%d", ipver, client_ip_string, port); - // set up a the record of the remote's control socket - struct addrinfo hints; - struct addrinfo *servinfo; + // set up a the record of the remote's control socket + struct addrinfo hints; + struct addrinfo *servinfo; - memset(&rtp_client_control_socket,0,sizeof(rtp_client_control_socket)); - memset(&hints,0, sizeof hints); - hints.ai_family = remote->SAFAMILY; - hints.ai_socktype = SOCK_DGRAM; - snprintf(portstr, 20, "%d", cport); - if (getaddrinfo(client_ip_string,portstr,&hints,&servinfo)!=0) - die("Can't get address of client's control port"); + memset(&rtp_client_control_socket, 0, sizeof(rtp_client_control_socket)); + memset(&hints, 0, sizeof hints); + hints.ai_family = remote->SAFAMILY; + hints.ai_socktype = SOCK_DGRAM; + snprintf(portstr, 20, "%d", cport); + if (getaddrinfo(client_ip_string, portstr, &hints, &servinfo) != 0) + die("Can't get address of client's control port"); #ifdef AF_INET6 - if (servinfo->ai_family == AF_INET6) - memcpy(&rtp_client_control_socket,servinfo->ai_addr,sizeof(struct sockaddr_in6)); - else + if (servinfo->ai_family == AF_INET6) + memcpy(&rtp_client_control_socket, servinfo->ai_addr, sizeof(struct sockaddr_in6)); + else #endif - memcpy(&rtp_client_control_socket,servinfo->ai_addr,sizeof(struct sockaddr_in)); - freeaddrinfo(servinfo); - - // set up a the record of the remote's timing socket - memset(&rtp_client_timing_socket,0,sizeof(rtp_client_timing_socket)); - memset(&hints,0, sizeof hints); - hints.ai_family = remote->SAFAMILY; - hints.ai_socktype = SOCK_DGRAM; - snprintf(portstr, 20, "%d", tport); - if (getaddrinfo(client_ip_string,portstr,&hints,&servinfo)!=0) - die("Can't get address of client's timing port"); + memcpy(&rtp_client_control_socket, servinfo->ai_addr, sizeof(struct sockaddr_in)); + freeaddrinfo(servinfo); + + // set up a the record of the remote's timing socket + memset(&rtp_client_timing_socket, 0, sizeof(rtp_client_timing_socket)); + memset(&hints, 0, sizeof hints); + hints.ai_family = remote->SAFAMILY; + hints.ai_socktype = SOCK_DGRAM; + snprintf(portstr, 20, "%d", tport); + if (getaddrinfo(client_ip_string, portstr, &hints, &servinfo) != 0) + die("Can't get address of client's timing port"); #ifdef AF_INET6 - if (servinfo->ai_family == AF_INET6) - memcpy(&rtp_client_timing_socket,servinfo->ai_addr,sizeof(struct sockaddr_in6)); - else + if (servinfo->ai_family == AF_INET6) + memcpy(&rtp_client_timing_socket, servinfo->ai_addr, sizeof(struct sockaddr_in6)); + else #endif - memcpy(&rtp_client_timing_socket,servinfo->ai_addr,sizeof(struct sockaddr_in)); - freeaddrinfo(servinfo); - - // now, we open three sockets -- one for the audio stream, one for the timing and one for the control - - *lsport = bind_port(remote,&audio_socket,0); - *lcport = bind_port(remote,&control_socket,0); - *ltport = bind_port(remote,&timing_socket,0); + memcpy(&rtp_client_timing_socket, servinfo->ai_addr, sizeof(struct sockaddr_in)); + freeaddrinfo(servinfo); - debug(2, "listening for audio, control and timing on ports %d, %d, %d.", *lsport, *lcport, *ltport); + // now, we open three sockets -- one for the audio stream, one for the timing and one for the + // control - please_shutdown = 0; - reference_timestamp=0; - pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, NULL); - pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, NULL); - pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, NULL); + *lsport = bind_port(remote, &audio_socket, 0); + *lcport = bind_port(remote, &control_socket, 0); + *ltport = bind_port(remote, &timing_socket, 0); - running = 1; - request_sent=0; + debug(2, "listening for audio, control and timing on ports %d, %d, %d.", *lsport, *lcport, + *ltport); + + please_shutdown = 0; + reference_timestamp = 0; + pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, NULL); + pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, NULL); + pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, NULL); + + running = 1; + request_sent = 0; } -void get_reference_timestamp_stuff(uint32_t *timestamp,uint64_t *timestamp_time) { +void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time) { pthread_mutex_lock(&reference_time_mutex); - *timestamp=reference_timestamp; - *timestamp_time = reference_timestamp_time; + *timestamp = reference_timestamp; + *timestamp_time = reference_timestamp_time; pthread_mutex_unlock(&reference_time_mutex); } void clear_reference_timestamp(void) { pthread_mutex_lock(&reference_time_mutex); - reference_timestamp=0; - reference_timestamp_time=0; + reference_timestamp = 0; + reference_timestamp_time = 0; pthread_mutex_unlock(&reference_time_mutex); } void rtp_shutdown(void) { - if (!running) - die("rtp_shutdown called without active stream!"); + if (!running) + die("rtp_shutdown called without active stream!"); - debug(2, "shutting down RTP thread"); - please_shutdown = 1; - void *retval; - reference_timestamp=0; - pthread_kill(rtp_audio_thread, SIGUSR1); - pthread_join(rtp_audio_thread, &retval); - pthread_kill(rtp_control_thread, SIGUSR1); - pthread_join(rtp_control_thread, &retval); - pthread_kill(rtp_timing_thread, SIGUSR1); - pthread_join(rtp_timing_thread, &retval); - running = 0; + debug(2, "shutting down RTP thread"); + please_shutdown = 1; + void *retval; + reference_timestamp = 0; + pthread_kill(rtp_audio_thread, SIGUSR1); + pthread_join(rtp_audio_thread, &retval); + pthread_kill(rtp_control_thread, SIGUSR1); + pthread_join(rtp_control_thread, &retval); + pthread_kill(rtp_timing_thread, SIGUSR1); + pthread_join(rtp_timing_thread, &retval); + running = 0; } void rtp_request_resend(seq_t first, uint32_t count) { - if (running) { - if (!request_sent) { - debug(2, "requesting resend on %d packets starting at %u.",count,first); - request_sent=1; - } - - char req[8]; // *not* a standard RTCP NACK - req[0] = 0x80; - req[1] = 0x55|0x80; // Apple 'resend' - *(unsigned short *)(req+2) = htons(1); // our seqnum - *(unsigned short *)(req+4) = htons(first); // missed seqnum - *(unsigned short *)(req+6) = htons(count); // count - socklen_t msgsize = sizeof(struct sockaddr_in); -#ifdef AF_INET6 - if (rtp_client_timing_socket.SAFAMILY==AF_INET6) { - msgsize = sizeof(struct sockaddr_in6); - } -#endif - if (sendto(audio_socket, req, sizeof(req), 0, (struct sockaddr*)&rtp_client_control_socket,msgsize)==-1) { - perror("Error sendto-ing to audio socket"); - } - } else { - if (!request_sent) { - debug(2,"rtp_request_resend called without active stream!"); - request_sent=1; - } + if (running) { + if (!request_sent) { + debug(2, "requesting resend on %d packets starting at %u.", count, first); + request_sent = 1; } + + char req[8]; // *not* a standard RTCP NACK + req[0] = 0x80; + req[1] = 0x55 | 0x80; // Apple 'resend' + *(unsigned short *)(req + 2) = htons(1); // our seqnum + *(unsigned short *)(req + 4) = htons(first); // missed seqnum + *(unsigned short *)(req + 6) = htons(count); // count + socklen_t msgsize = sizeof(struct sockaddr_in); +#ifdef AF_INET6 + if (rtp_client_timing_socket.SAFAMILY == AF_INET6) { + msgsize = sizeof(struct sockaddr_in6); + } +#endif + if (sendto(audio_socket, req, sizeof(req), 0, (struct sockaddr *)&rtp_client_control_socket, + msgsize) == -1) { + perror("Error sendto-ing to audio socket"); + } + } else { + if (!request_sent) { + debug(2, "rtp_request_resend called without active stream!"); + request_sent = 1; + } + } } void rtp_request_client_pause() { if (running) { - if (client_active_remote==0) { - debug(1,"Can't request a client pause: no valid active remote."); - } else { - // debug(1,"Send a client pause request to %s:3689 with active remote %u.",client_ip_string,client_active_remote); - - - struct addrinfo hints, *res; - int sockfd; - - char message[1000] , server_reply[2000]; + if (client_active_remote == 0) { + debug(1, "Can't request a client pause: no valid active remote."); + } else { + // debug(1,"Send a client pause request to %s:3689 with active remote + // %u.",client_ip_string,client_active_remote); - // first, load up address structs with getaddrinfo(): + struct addrinfo hints, *res; + int sockfd; - memset(&hints, 0, sizeof hints); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; + char message[1000], server_reply[2000]; - getaddrinfo(client_ip_string, "3689", &hints, &res); + // first, load up address structs with getaddrinfo(): - // make a socket: + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; - sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + getaddrinfo(client_ip_string, "3689", &hints, &res); - if (sockfd == -1) { - die("Could not create socket"); - } - // debug(1,"Socket created"); - - // connect! + // make a socket: - if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) { - die("connect failed. Error"); - } - // debug(1,"Connect successful"); - - sprintf(message,"GET /ctrl-int/1/pause HTTP/1.1\r\nHost: %s:3689\r\nActive-Remote: %u\r\n\r\n",client_ip_string,client_active_remote); - // debug(1,"Sending this message: \"%s\".",message); - - //Send some data - if( send(sockfd , message , strlen(message) , 0) < 0) { - debug(1,"Send failed"); - } - - //Receive a reply from the server - if( recv(sockfd , server_reply , 2000 , 0) < 0) { - debug(1,"recv failed"); - } - - // debug(1,"Server replied: \"%s\".",server_reply); - - if (strstr(server_reply,"HTTP/1.1 204 No Content")!=server_reply) - debug(1,"Client pause request failed."); - // debug(1,"Client pause request failed: \"%s\".",server_reply); - close(sockfd); + sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + + if (sockfd == -1) { + die("Could not create socket"); + } + // debug(1,"Socket created"); + + // connect! + + if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) { + die("connect failed. Error"); + } + // debug(1,"Connect successful"); + + sprintf(message, + "GET /ctrl-int/1/pause HTTP/1.1\r\nHost: %s:3689\r\nActive-Remote: %u\r\n\r\n", + client_ip_string, client_active_remote); + // debug(1,"Sending this message: \"%s\".",message); + + // Send some data + if (send(sockfd, message, strlen(message), 0) < 0) { + debug(1, "Send failed"); + } + + // Receive a reply from the server + if (recv(sockfd, server_reply, 2000, 0) < 0) { + debug(1, "recv failed"); + } + + // debug(1,"Server replied: \"%s\".",server_reply); + + if (strstr(server_reply, "HTTP/1.1 204 No Content") != server_reply) + debug(1, "Client pause request failed."); + // debug(1,"Client pause request failed: \"%s\".",server_reply); + close(sockfd); } } else { - debug(1,"Request to pause non-existent play stream -- ignored."); + debug(1, "Request to pause non-existent play stream -- ignored."); } } diff --git a/rtp.h b/rtp.h index 5b679a60..5fd67983 100644 --- a/rtp.h +++ b/rtp.h @@ -5,13 +5,14 @@ #include "player.h" -void rtp_setup(SOCKADDR *remote, int controlport, int timingport, uint32_t active_remote, int *local_server_port, int *local_control_port, int *local_timing_port); +void rtp_setup(SOCKADDR *remote, int controlport, int timingport, uint32_t active_remote, + int *local_server_port, int *local_control_port, int *local_timing_port); void rtp_shutdown(void); void rtp_request_resend(seq_t first, uint32_t count); void rtp_request_client_pause(void); // ask the client to pause -void get_reference_timestamp_stuff(uint32_t *timestamp,uint64_t *timestamp_time); -void clear_reference_timestamp(void); +void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time); +void clear_reference_timestamp(void); uint64_t static local_to_remote_time_jitters; uint64_t static local_to_remote_time_jitters_count; diff --git a/rtsp.c b/rtsp.c index 8bb7f150..0f9b1226 100644 --- a/rtsp.c +++ b/rtsp.c @@ -76,7 +76,6 @@ static pthread_mutex_t play_lock = PTHREAD_MUTEX_INITIALIZER; // if a reference count is read as zero, it means the it's being deallocated. static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER; - // only one thread is allowed to use the player at once. // it monitors the request variable (at least when interrupted) static pthread_mutex_t playing_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -84,11 +83,11 @@ static int please_shutdown = 0; static pthread_t playing_thread = 0; typedef struct { - int fd; - stream_cfg stream; - SOCKADDR remote; - int running; - pthread_t thread; + int fd; + stream_cfg stream; + SOCKADDR remote; + int running; + pthread_t thread; } rtsp_conn_info; #ifdef CONFIG_METADATA @@ -96,17 +95,17 @@ typedef struct { pthread_mutex_t pc_queue_lock; pthread_cond_t pc_queue_item_added_signal; pthread_cond_t pc_queue_item_removed_signal; - size_t item_size; // number of bytes in each item - uint32_t count; // number of items in the queue + size_t item_size; // number of bytes in each item + uint32_t count; // number of items in the queue uint32_t capacity; // maximum number of items - uint32_t toq; // first item to take - uint32_t eoq; // free space at end of queue - void *items; // a pointer to where the items are actually stored - } pc_queue; // producer-consumer queue + uint32_t toq; // first item to take + uint32_t eoq; // free space at end of queue + void *items; // a pointer to where the items are actually stored +} pc_queue; // producer-consumer queue #endif typedef struct { - uint32_t referenceCount; // we might start using this... + uint32_t referenceCount; // we might start using this... int nheaders; char *name[16]; char *value[16]; @@ -127,10 +126,10 @@ typedef struct { uint32_t code; char *data; uint32_t length; - rtsp_message* carrier; + rtsp_message *carrier; } metadata_package; -void pc_queue_init(pc_queue* the_queue, char* items, size_t item_size, uint32_t number_of_items) { +void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items) { the_queue->item_size = item_size; the_queue->items = items; the_queue->count = 0; @@ -139,85 +138,86 @@ void pc_queue_init(pc_queue* the_queue, char* items, size_t item_size, uint32_t the_queue->eoq = 0; } -int send_metadata(uint32_t type,uint32_t code,char *data,uint32_t length,rtsp_message* carrier,int block); +int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier, + int block); -int send_ssnc_metadata(uint32_t code,char *data,uint32_t length,int block) { - return send_metadata('ssnc',code,data,length,NULL,block); +int send_ssnc_metadata(uint32_t code, char *data, uint32_t length, int block) { + return send_metadata('ssnc', code, data, length, NULL, block); } -int pc_queue_add_item(pc_queue* the_queue,const void* the_stuff, int block) { +int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) { int rc; if (the_queue) { - if (block==0) { + if (block == 0) { rc = pthread_mutex_trylock(&the_queue->pc_queue_lock); - if (rc==EBUSY) + if (rc == EBUSY) return EBUSY; } else rc = pthread_mutex_lock(&the_queue->pc_queue_lock); if (rc) - debug(1,"Error locking for pc_queue_add_item"); - while(the_queue->count==the_queue->capacity) { - rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal,&the_queue->pc_queue_lock); + debug(1, "Error locking for pc_queue_add_item"); + while (the_queue->count == the_queue->capacity) { + rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal, &the_queue->pc_queue_lock); if (rc) - debug(1,"Error waiting for item to be removed"); + debug(1, "Error waiting for item to be removed"); } uint32_t i = the_queue->eoq; - void * p = the_queue->items + the_queue->item_size*i; -// void * p = &the_queue->qbase + the_queue->item_size*the_queue->eoq; - memcpy(p,the_stuff,the_queue->item_size); - + void *p = the_queue->items + the_queue->item_size * i; + // void * p = &the_queue->qbase + the_queue->item_size*the_queue->eoq; + memcpy(p, the_stuff, the_queue->item_size); + // update the pointer i++; - if (i==the_queue->capacity) + if (i == the_queue->capacity) // fold pointer if necessary - i=0; + i = 0; the_queue->eoq = i; the_queue->count++; - if (the_queue->count==the_queue->capacity) - debug(1,"pc_queue is full!"); + if (the_queue->count == the_queue->capacity) + debug(1, "pc_queue is full!"); rc = pthread_cond_signal(&the_queue->pc_queue_item_added_signal); if (rc) - debug(1,"Error signalling after pc_queue_add_item"); + debug(1, "Error signalling after pc_queue_add_item"); rc = pthread_mutex_unlock(&the_queue->pc_queue_lock); if (rc) - debug(1,"Error unlocking for pc_queue_add_item"); + debug(1, "Error unlocking for pc_queue_add_item"); } else { - debug(1,"Adding an item to a NULL queue"); + debug(1, "Adding an item to a NULL queue"); } return 0; } -int pc_queue_get_item(pc_queue* the_queue,void* the_stuff) { +int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) { int rc; if (the_queue) { rc = pthread_mutex_lock(&the_queue->pc_queue_lock); if (rc) - debug(1,"Error locking for pc_queue_get_item"); - while(the_queue->count==0) { - rc = pthread_cond_wait(&the_queue->pc_queue_item_added_signal,&the_queue->pc_queue_lock); + debug(1, "Error locking for pc_queue_get_item"); + while (the_queue->count == 0) { + rc = pthread_cond_wait(&the_queue->pc_queue_item_added_signal, &the_queue->pc_queue_lock); if (rc) - debug(1,"Error waiting for item to be added"); + debug(1, "Error waiting for item to be added"); } uint32_t i = the_queue->toq; -// void * p = &the_queue->qbase + the_queue->item_size*the_queue->toq; - void * p = the_queue->items + the_queue->item_size*i; - memcpy(the_stuff,p,the_queue->item_size); - + // void * p = &the_queue->qbase + the_queue->item_size*the_queue->toq; + void *p = the_queue->items + the_queue->item_size * i; + memcpy(the_stuff, p, the_queue->item_size); + // update the pointer i++; - if (i==the_queue->capacity) + if (i == the_queue->capacity) // fold pointer if necessary - i=0; + i = 0; the_queue->toq = i; the_queue->count--; rc = pthread_cond_signal(&the_queue->pc_queue_item_removed_signal); if (rc) - debug(1,"Error signalling after pc_queue_removed_item"); + debug(1, "Error signalling after pc_queue_removed_item"); rc = pthread_mutex_unlock(&the_queue->pc_queue_lock); if (rc) - debug(1,"Error unlocking for pc_queue_get_item"); + debug(1, "Error unlocking for pc_queue_get_item"); } else { - debug(1,"Removing an item from a NULL queue"); + debug(1, "Removing an item from a NULL queue"); } return 0; } @@ -226,12 +226,12 @@ int pc_queue_get_item(pc_queue* the_queue,void* the_stuff) { // determine if we are the currently playing thread static inline int rtsp_playing(void) { - if (pthread_mutex_trylock(&playing_mutex)) { - return pthread_equal(playing_thread, pthread_self()); - } else { - pthread_mutex_unlock(&playing_mutex); - return 0; - } + if (pthread_mutex_trylock(&playing_mutex)) { + return pthread_equal(playing_thread, pthread_self()); + } else { + pthread_mutex_unlock(&playing_mutex); + return 0; + } } void rtsp_request_shutdown_stream(void) { @@ -239,569 +239,578 @@ void rtsp_request_shutdown_stream(void) { pthread_kill(playing_thread, SIGUSR1); } - static void rtsp_take_player(void) { - if (rtsp_playing()) - return; + if (rtsp_playing()) + return; - if (pthread_mutex_trylock(&playing_mutex)) { - debug(1, "shutting down playing thread."); - // XXX minor race condition between please_shutdown and signal delivery - please_shutdown = 1; - pthread_kill(playing_thread, SIGUSR1); - pthread_mutex_lock(&playing_mutex); - } - playing_thread = pthread_self(); // make us the currently-playing thread (why?) + if (pthread_mutex_trylock(&playing_mutex)) { + debug(1, "shutting down playing thread."); + // XXX minor race condition between please_shutdown and signal delivery + please_shutdown = 1; + pthread_kill(playing_thread, SIGUSR1); + pthread_mutex_lock(&playing_mutex); + } + playing_thread = pthread_self(); // make us the currently-playing thread (why?) } void rtsp_shutdown_stream(void) { - rtsp_take_player(); - pthread_mutex_unlock(&playing_mutex); + rtsp_take_player(); + pthread_mutex_unlock(&playing_mutex); } // keep track of the threads we have spawned so we can join() them static rtsp_conn_info **conns = NULL; static int nconns = 0; static void track_thread(rtsp_conn_info *conn) { - conns = realloc(conns, sizeof(rtsp_conn_info*) * (nconns + 1)); - conns[nconns] = conn; - nconns++; + conns = realloc(conns, sizeof(rtsp_conn_info *) * (nconns + 1)); + conns[nconns] = conn; + nconns++; } static void cleanup_threads(void) { - void *retval; - int i; - debug(2, "culling threads."); - for (i=0; irunning == 0) { - pthread_join(conns[i]->thread, &retval); - free(conns[i]); - debug(2, "one joined..."); - nconns--; - if (nconns) - conns[i] = conns[nconns]; - } else { - i++; - } + void *retval; + int i; + debug(2, "culling threads."); + for (i = 0; i < nconns;) { + if (conns[i]->running == 0) { + pthread_join(conns[i]->thread, &retval); + free(conns[i]); + debug(2, "one joined..."); + nconns--; + if (nconns) + conns[i] = conns[nconns]; + } else { + i++; } + } } // park a null at the line ending, and return the next line pointer // accept \r, \n, or \r\n static char *nextline(char *in, int inbuf) { - char *out = NULL; - while (inbuf) { - if (*in == '\r') { - *in++ = 0; - out = in; - } - if (*in == '\n') { - *in++ = 0; - out = in; - } - - if (out) - break; - - in++; - inbuf--; + char *out = NULL; + while (inbuf) { + if (*in == '\r') { + *in++ = 0; + out = in; } - return out; + if (*in == '\n') { + *in++ = 0; + out = in; + } + + if (out) + break; + + in++; + inbuf--; + } + return out; } -static void msg_retain(rtsp_message * msg) { +static void msg_retain(rtsp_message *msg) { if (msg) { - int rc = pthread_mutex_lock(&reference_counter_lock); - if (rc) - debug(1,"Error %d locking reference counter lock"); - msg->referenceCount++; - rc = pthread_mutex_unlock(&reference_counter_lock); - if (rc) - debug(1,"Error %d unlocking reference counter lock"); + int rc = pthread_mutex_lock(&reference_counter_lock); + if (rc) + debug(1, "Error %d locking reference counter lock"); + msg->referenceCount++; + rc = pthread_mutex_unlock(&reference_counter_lock); + if (rc) + debug(1, "Error %d unlocking reference counter lock"); } else { - debug(1,"null rtsp_message pointer passed to retain"); + debug(1, "null rtsp_message pointer passed to retain"); } } -static rtsp_message * msg_init(void) { - rtsp_message *msg = malloc(sizeof(rtsp_message)); - memset(msg, 0, sizeof(rtsp_message)); - msg->referenceCount = 1; // from now on, any access to this must be protected with the lock - return msg; +static rtsp_message *msg_init(void) { + rtsp_message *msg = malloc(sizeof(rtsp_message)); + memset(msg, 0, sizeof(rtsp_message)); + msg->referenceCount = 1; // from now on, any access to this must be protected with the lock + return msg; } static int msg_add_header(rtsp_message *msg, char *name, char *value) { - if (msg->nheaders >= sizeof(msg->name)/sizeof(char*)) { - warn("too many headers?!"); - return 1; - } + if (msg->nheaders >= sizeof(msg->name) / sizeof(char *)) { + warn("too many headers?!"); + return 1; + } - msg->name[msg->nheaders] = strdup(name); - msg->value[msg->nheaders] = strdup(value); - msg->nheaders++; + msg->name[msg->nheaders] = strdup(name); + msg->value[msg->nheaders] = strdup(value); + msg->nheaders++; - return 0; + return 0; } static char *msg_get_header(rtsp_message *msg, char *name) { - int i; - for (i=0; inheaders; i++) - if (!strcasecmp(msg->name[i], name)) - return msg->value[i]; - return NULL; + int i; + for (i = 0; i < msg->nheaders; i++) + if (!strcasecmp(msg->name[i], name)) + return msg->value[i]; + return NULL; } static void msg_print_debug_headers(rtsp_message *msg) { int i; - for (i=0; inheaders; i++) { - debug(1," Type: \"%s\", content: \"%s\"",msg->name[i],msg->value[i]); + for (i = 0; i < msg->nheaders; i++) { + debug(1, " Type: \"%s\", content: \"%s\"", msg->name[i], msg->value[i]); } } static void msg_free(rtsp_message *msg) { - + if (msg) { int rc = pthread_mutex_lock(&reference_counter_lock); if (rc) - debug(1,"Error %d locking reference counter lock during msg_free()",rc); + debug(1, "Error %d locking reference counter lock during msg_free()", rc); msg->referenceCount--; rc = pthread_mutex_unlock(&reference_counter_lock); if (rc) - debug(1,"Error %d unlocking reference counter lock during msg_free()",rc); - if (msg->referenceCount==0) { + debug(1, "Error %d unlocking reference counter lock during msg_free()", rc); + if (msg->referenceCount == 0) { int i; - for (i=0; inheaders; i++) { - free(msg->name[i]); - free(msg->value[i]); + for (i = 0; i < msg->nheaders; i++) { + free(msg->name[i]); + free(msg->value[i]); } if (msg->content) free(msg->content); free(msg); } // else { // debug(1,"rtsp_message reference count non-zero: %d!",msg->referenceCount); - //} + //} } else { - debug(1,"null rtsp_message pointer passed to msg_free()"); - } + debug(1, "null rtsp_message pointer passed to msg_free()"); + } } - static int msg_handle_line(rtsp_message **pmsg, char *line) { - rtsp_message *msg = *pmsg; + rtsp_message *msg = *pmsg; - if (!msg) { - msg = msg_init(); - *pmsg = msg; - char *sp, *p; + if (!msg) { + msg = msg_init(); + *pmsg = msg; + char *sp, *p; - // debug(1, "received request: %s", line); + // debug(1, "received request: %s", line); - p = strtok_r(line, " ", &sp); - if (!p) - goto fail; - strncpy(msg->method, p, sizeof(msg->method)-1); + p = strtok_r(line, " ", &sp); + if (!p) + goto fail; + strncpy(msg->method, p, sizeof(msg->method) - 1); - p = strtok_r(NULL, " ", &sp); - if (!p) - goto fail; + p = strtok_r(NULL, " ", &sp); + if (!p) + goto fail; - p = strtok_r(NULL, " ", &sp); - if (!p) - goto fail; - if (strcmp(p, "RTSP/1.0")) - goto fail; + p = strtok_r(NULL, " ", &sp); + if (!p) + goto fail; + if (strcmp(p, "RTSP/1.0")) + goto fail; - return -1; - } + return -1; + } - if (strlen(line)) { - char *p; - p = strstr(line, ": "); - if (!p) { - warn("bad header: >>%s<<", line); - goto fail; - } - *p = 0; - p += 2; - msg_add_header(msg, line, p); - debug(2, " %s: %s.", line, p); - return -1; - } else { - char *cl = msg_get_header(msg, "Content-Length"); - if (cl) - return atoi(cl); - else - return 0; + if (strlen(line)) { + char *p; + p = strstr(line, ": "); + if (!p) { + warn("bad header: >>%s<<", line); + goto fail; } + *p = 0; + p += 2; + msg_add_header(msg, line, p); + debug(2, " %s: %s.", line, p); + return -1; + } else { + char *cl = msg_get_header(msg, "Content-Length"); + if (cl) + return atoi(cl); + else + return 0; + } fail: - *pmsg = NULL; - msg_free(msg); - return 0; + *pmsg = NULL; + msg_free(msg); + return 0; } -static enum rtsp_read_request_response rtsp_read_request(int fd, rtsp_message** the_packet) { - enum rtsp_read_request_response reply=rtsp_read_request_response_ok; - ssize_t buflen = 512; - char *buf = malloc(buflen+1); +static enum rtsp_read_request_response rtsp_read_request(int fd, rtsp_message **the_packet) { + enum rtsp_read_request_response reply = rtsp_read_request_response_ok; + ssize_t buflen = 512; + char *buf = malloc(buflen + 1); - rtsp_message *msg = NULL; + rtsp_message *msg = NULL; - ssize_t nread; - ssize_t inbuf = 0; - int msg_size = -1; - - while (msg_size < 0) { - if (please_shutdown) { - debug(1, "RTSP shutdown requested."); - reply = rtsp_read_request_response_shutdown_requested; - goto shutdown; - } - nread = read(fd, buf+inbuf, buflen - inbuf); - if (!nread) { - debug(1, "RTSP connection closed."); - reply = rtsp_read_request_response_shutdown_requested; - goto shutdown; - } - if (nread < 0) { - if (errno==EINTR) - continue; - perror("read failure"); - reply = rtsp_read_request_response_error; - goto shutdown; - } - inbuf += nread; + ssize_t nread; + ssize_t inbuf = 0; + int msg_size = -1; - char *next; - while (msg_size < 0 && (next = nextline(buf, inbuf))) { - msg_size = msg_handle_line(&msg, buf); - - if (!msg) { - warn("no RTSP header received"); - reply = rtsp_read_request_response_bad_packet; - goto shutdown; - } - - inbuf -= next-buf; - if (inbuf) - memmove(buf, next, inbuf); - } + while (msg_size < 0) { + if (please_shutdown) { + debug(1, "RTSP shutdown requested."); + reply = rtsp_read_request_response_shutdown_requested; + goto shutdown; } - - if (msg_size > buflen) { - buf = realloc(buf, msg_size); - if (!buf) { - warn("too much content"); - reply = rtsp_read_request_response_error; - goto shutdown; - } - buflen = msg_size; + nread = read(fd, buf + inbuf, buflen - inbuf); + if (!nread) { + debug(1, "RTSP connection closed."); + reply = rtsp_read_request_response_shutdown_requested; + goto shutdown; } - - uint64_t threshold_time = get_absolute_time_in_fp() + ((uint64_t)5<<32); // i.e. five seconds from now - int warning_message_sent = 0; - - const size_t max_read_chunk = 50000; - while (inbuf < msg_size) { - - // we are going to read the stream in chunks and time how long it takes to do so. - // If it's taking too long, (and we find out about it), we will send an error message as metadata - - if (warning_message_sent==0) { - uint64_t time_now = get_absolute_time_in_fp(); - if (time_now>threshold_time) { // it's taking too long - debug(1,"Error receiving metadata from source -- transmission seems to be stalled."); -#ifdef CONFIG_METADATA - send_ssnc_metadata('stal',NULL,0,1); -#endif - warning_message_sent = 1; - } - } - ssize_t read_chunk = msg_size-inbuf; - if (read_chunk > max_read_chunk) - read_chunk = max_read_chunk; - nread = read(fd, buf+inbuf, read_chunk); - if (!nread) { - reply = rtsp_read_request_response_error; - goto shutdown; - } - if (nread==EINTR) + if (nread < 0) { + if (errno == EINTR) continue; - if (nread < 0) { - perror("read failure"); - reply = rtsp_read_request_response_error; + perror("read failure"); + reply = rtsp_read_request_response_error; + goto shutdown; + } + inbuf += nread; + + char *next; + while (msg_size < 0 && (next = nextline(buf, inbuf))) { + msg_size = msg_handle_line(&msg, buf); + + if (!msg) { + warn("no RTSP header received"); + reply = rtsp_read_request_response_bad_packet; goto shutdown; } - inbuf += nread; - } - msg->contentlength = inbuf; - msg->content = buf; - *the_packet = msg; - return reply; + inbuf -= next - buf; + if (inbuf) + memmove(buf, next, inbuf); + } + } + + if (msg_size > buflen) { + buf = realloc(buf, msg_size); + if (!buf) { + warn("too much content"); + reply = rtsp_read_request_response_error; + goto shutdown; + } + buflen = msg_size; + } + + uint64_t threshold_time = + get_absolute_time_in_fp() + ((uint64_t)5 << 32); // i.e. five seconds from now + int warning_message_sent = 0; + + const size_t max_read_chunk = 50000; + while (inbuf < msg_size) { + + // we are going to read the stream in chunks and time how long it takes to do so. + // If it's taking too long, (and we find out about it), we will send an error message as + // metadata + + if (warning_message_sent == 0) { + uint64_t time_now = get_absolute_time_in_fp(); + if (time_now > threshold_time) { // it's taking too long + debug(1, "Error receiving metadata from source -- transmission seems to be stalled."); +#ifdef CONFIG_METADATA + send_ssnc_metadata('stal', NULL, 0, 1); +#endif + warning_message_sent = 1; + } + } + ssize_t read_chunk = msg_size - inbuf; + if (read_chunk > max_read_chunk) + read_chunk = max_read_chunk; + nread = read(fd, buf + inbuf, read_chunk); + if (!nread) { + reply = rtsp_read_request_response_error; + goto shutdown; + } + if (nread == EINTR) + continue; + if (nread < 0) { + perror("read failure"); + reply = rtsp_read_request_response_error; + goto shutdown; + } + inbuf += nread; + } + + msg->contentlength = inbuf; + msg->content = buf; + *the_packet = msg; + return reply; shutdown: - if (msg) { - msg_free(msg); // which will free the content and everything else - } - // in case the message wasn't formed or wasn't fully initialised - if ((msg) && (msg-> content == NULL) || (!msg)) - free(buf); - *the_packet = NULL; - return reply; + if (msg) { + msg_free(msg); // which will free the content and everything else + } + // in case the message wasn't formed or wasn't fully initialised + if ((msg) && (msg->content == NULL) || (!msg)) + free(buf); + *the_packet = NULL; + return reply; } static void msg_write_response(int fd, rtsp_message *resp) { - char pkt[1024]; - int pktfree = sizeof(pkt); - char *p = pkt; - int i, n; + char pkt[1024]; + int pktfree = sizeof(pkt); + char *p = pkt; + int i, n; - n = snprintf(p, pktfree, - "RTSP/1.0 %d %s\r\n", resp->respcode, - resp->respcode==200 ? "OK" : "Error"); - // debug(1, "sending response: %s", pkt); + n = snprintf(p, pktfree, "RTSP/1.0 %d %s\r\n", resp->respcode, + resp->respcode == 200 ? "OK" : "Error"); + // debug(1, "sending response: %s", pkt); + pktfree -= n; + p += n; + + for (i = 0; i < resp->nheaders; i++) { + debug(2, " %s: %s.", resp->name[i], resp->value[i]); + n = snprintf(p, pktfree, "%s: %s\r\n", resp->name[i], resp->value[i]); pktfree -= n; p += n; + if (pktfree <= 0) + die("Attempted to write overlong RTSP packet"); + } - for (i=0; inheaders; i++) { - debug(2, " %s: %s.", resp->name[i], resp->value[i]); - n = snprintf(p, pktfree, "%s: %s\r\n", resp->name[i], resp->value[i]); - pktfree -= n; - p += n; - if (pktfree <= 0) - die("Attempted to write overlong RTSP packet"); - } + if (pktfree < 3) + die("Attempted to write overlong RTSP packet"); - if (pktfree < 3) - die("Attempted to write overlong RTSP packet"); - - strcpy(p, "\r\n"); - int ignore = write(fd, pkt, p-pkt+2); + strcpy(p, "\r\n"); + int ignore = write(fd, pkt, p - pkt + 2); } -static void handle_record(rtsp_conn_info *conn, - rtsp_message *req, rtsp_message *resp) { +static void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { resp->respcode = 200; - msg_add_header(resp, "Audio-Latency","88200"); + msg_add_header(resp, "Audio-Latency", "88200"); } -static void handle_options(rtsp_conn_info *conn, - rtsp_message *req, rtsp_message *resp) { - resp->respcode = 200; - msg_add_header(resp, "Public", - "ANNOUNCE, SETUP, RECORD, " - "PAUSE, FLUSH, TEARDOWN, " - "OPTIONS, GET_PARAMETER, SET_PARAMETER"); +static void handle_options(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { + resp->respcode = 200; + msg_add_header(resp, "Public", "ANNOUNCE, SETUP, RECORD, " + "PAUSE, FLUSH, TEARDOWN, " + "OPTIONS, GET_PARAMETER, SET_PARAMETER"); } -static void handle_teardown(rtsp_conn_info *conn, - rtsp_message *req, rtsp_message *resp) { - if (!rtsp_playing()) - return; - resp->respcode = 200; - msg_add_header(resp, "Connection", "close"); - please_shutdown = 1; -} - -static void handle_flush(rtsp_conn_info *conn, - rtsp_message *req, rtsp_message *resp) { - if (!rtsp_playing()) - return; - char *p; - uint32_t rtptime=0; - char * hdr = msg_get_header(req,"RTP-Info"); - - if (hdr) { - // debug(1,"FLUSH message received: \"%s\".",hdr); - // get the rtp timestamp - p = strstr(hdr, "rtptime="); - if (p) { - p = strchr(p, '=') + 1; - if (p) - rtptime = uatoi(p); // unsigned integer -- up to 2^32-1 - } - } - // debug(1,"RTSP Flush Requested: %u.",rtptime); - player_flush(rtptime); - resp->respcode = 200; -} - -static void handle_setup(rtsp_conn_info *conn, - rtsp_message *req, rtsp_message *resp) { - int cport, tport; - int lsport,lcport,ltport; - uint32_t active_remote=0; - - char * ar = msg_get_header(req,"Active-Remote"); - if (ar) { - // debug(1,"Active-Remote string seen: \"%s\".",ar); - // get the active remote - char *p; - active_remote = strtoul(ar,&p,10); - // debug(1,"Active Remote is %u.",active_remote); - } - - // select latency - // if iTunes V10 or later is detected, use the iTunes latency setting - // if AirPlay is detected, use the AirPlay latency setting - // for everything else, use the general latency setting, if given, or - // else use the default latency setting - - config.latency=88200; - - if (config.userSuppliedLatency) - config.latency=config.userSuppliedLatency; - - char * ua = msg_get_header(req,"User-Agent"); - if (ua==0) { - debug(1,"No User-Agent string found in the SETUP message. Using latency of %d frames.",config.latency); - } else { - if (strstr(ua,"iTunes")==ua) { - int iTunesVersion=0; - // now check it's version 10 or later - char *pp = strchr(ua,'/') + 1; - if (pp) - iTunesVersion=atoi(pp); - else - debug(2,"iTunes Version Number not found."); - if (iTunesVersion>=10) { - debug(2,"User-Agent is iTunes 10 or better, (actual version is %d); selecting the iTunes latency of %d frames.",iTunesVersion,config.iTunesLatency); - config.latency=config.iTunesLatency; - } - } else if (strstr(ua,"AirPlay")==ua) { - debug(2,"User-Agent is AirPlay; selecting the AirPlay latency of %d frames.",config.AirPlayLatency); - config.latency=config.AirPlayLatency; - } else if (strstr(ua,"forked-daapd")==ua) { - debug(2,"User-Agent is forked-daapd; selecting the forked-daapd latency of %d frames.",config.ForkedDaapdLatency); - config.latency=config.ForkedDaapdLatency; - } else { - debug(2,"Unrecognised User-Agent. Using latency of %d frames.",config.latency); - } - } - char *hdr = msg_get_header(req, "Transport"); - if (!hdr) - goto error; - - char *p; - p = strstr(hdr, "control_port="); - if (!p) - goto error; - p = strchr(p, '=') + 1; - cport = atoi(p); - - p = strstr(hdr, "timing_port="); - if (!p) - goto error; - p = strchr(p, '=') + 1; - tport = atoi(p); - - rtsp_take_player(); - rtp_setup(&conn->remote, cport, tport, active_remote, &lsport,&lcport,<port); - if (!lsport) - goto error; - char *q; - p = strstr(hdr,"control_port="); - if (p) { - q = strchr(p,';'); // get past the control port entry - *p++=0; - if (q++) - strcat(hdr,q); // should unsplice the control port entry - } - p = strstr(hdr,"timing_port="); - if (p) { - q = strchr(p,';'); // get past the timing port entry - *p++=0; - if (q++) - strcat(hdr,q); // should unsplice the timing port entry - } - - player_play(&conn->stream); - - char *resphdr = alloca(200); - *resphdr=0; - sprintf(resphdr, "RTP/AVP/UDP;unicast;interleaved=0-1;mode=record;control_port=%d;timing_port=%d;server_port=%d", lcport, ltport, lsport); - - msg_add_header(resp, "Transport", resphdr); - - msg_add_header(resp, "Session", "1"); - - resp->respcode = 200; +static void handle_teardown(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { + if (!rtsp_playing()) return; + resp->respcode = 200; + msg_add_header(resp, "Connection", "close"); + please_shutdown = 1; +} + +static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { + if (!rtsp_playing()) + return; + char *p; + uint32_t rtptime = 0; + char *hdr = msg_get_header(req, "RTP-Info"); + + if (hdr) { + // debug(1,"FLUSH message received: \"%s\".",hdr); + // get the rtp timestamp + p = strstr(hdr, "rtptime="); + if (p) { + p = strchr(p, '=') + 1; + if (p) + rtptime = uatoi(p); // unsigned integer -- up to 2^32-1 + } + } + // debug(1,"RTSP Flush Requested: %u.",rtptime); + player_flush(rtptime); + resp->respcode = 200; +} + +static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { + int cport, tport; + int lsport, lcport, ltport; + uint32_t active_remote = 0; + + char *ar = msg_get_header(req, "Active-Remote"); + if (ar) { + // debug(1,"Active-Remote string seen: \"%s\".",ar); + // get the active remote + char *p; + active_remote = strtoul(ar, &p, 10); + // debug(1,"Active Remote is %u.",active_remote); + } + + // select latency + // if iTunes V10 or later is detected, use the iTunes latency setting + // if AirPlay is detected, use the AirPlay latency setting + // for everything else, use the general latency setting, if given, or + // else use the default latency setting + + config.latency = 88200; + + if (config.userSuppliedLatency) + config.latency = config.userSuppliedLatency; + + char *ua = msg_get_header(req, "User-Agent"); + if (ua == 0) { + debug(1, "No User-Agent string found in the SETUP message. Using latency of %d frames.", + config.latency); + } else { + if (strstr(ua, "iTunes") == ua) { + int iTunesVersion = 0; + // now check it's version 10 or later + char *pp = strchr(ua, '/') + 1; + if (pp) + iTunesVersion = atoi(pp); + else + debug(2, "iTunes Version Number not found."); + if (iTunesVersion >= 10) { + debug(2, "User-Agent is iTunes 10 or better, (actual version is %d); selecting the iTunes " + "latency of %d frames.", + iTunesVersion, config.iTunesLatency); + config.latency = config.iTunesLatency; + } + } else if (strstr(ua, "AirPlay") == ua) { + debug(2, "User-Agent is AirPlay; selecting the AirPlay latency of %d frames.", + config.AirPlayLatency); + config.latency = config.AirPlayLatency; + } else if (strstr(ua, "forked-daapd") == ua) { + debug(2, "User-Agent is forked-daapd; selecting the forked-daapd latency of %d frames.", + config.ForkedDaapdLatency); + config.latency = config.ForkedDaapdLatency; + } else { + debug(2, "Unrecognised User-Agent. Using latency of %d frames.", config.latency); + } + } + char *hdr = msg_get_header(req, "Transport"); + if (!hdr) + goto error; + + char *p; + p = strstr(hdr, "control_port="); + if (!p) + goto error; + p = strchr(p, '=') + 1; + cport = atoi(p); + + p = strstr(hdr, "timing_port="); + if (!p) + goto error; + p = strchr(p, '=') + 1; + tport = atoi(p); + + rtsp_take_player(); + rtp_setup(&conn->remote, cport, tport, active_remote, &lsport, &lcport, <port); + if (!lsport) + goto error; + char *q; + p = strstr(hdr, "control_port="); + if (p) { + q = strchr(p, ';'); // get past the control port entry + *p++ = 0; + if (q++) + strcat(hdr, q); // should unsplice the control port entry + } + p = strstr(hdr, "timing_port="); + if (p) { + q = strchr(p, ';'); // get past the timing port entry + *p++ = 0; + if (q++) + strcat(hdr, q); // should unsplice the timing port entry + } + + player_play(&conn->stream); + + char *resphdr = alloca(200); + *resphdr = 0; + sprintf(resphdr, "RTP/AVP/" + "UDP;unicast;interleaved=0-1;mode=record;control_port=%d;timing_port=%d;server_" + "port=%d", + lcport, ltport, lsport); + + msg_add_header(resp, "Transport", resphdr); + + msg_add_header(resp, "Session", "1"); + + resp->respcode = 200; + return; error: - warn("Error in setup request."); - pthread_mutex_unlock(&play_lock); - resp->respcode = 451; // invalid arguments + warn("Error in setup request."); + pthread_mutex_unlock(&play_lock); + resp->respcode = 451; // invalid arguments } -static void handle_ignore(rtsp_conn_info *conn, - rtsp_message *req, rtsp_message *resp) { - resp->respcode = 200; +static void handle_ignore(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { + resp->respcode = 200; } -static void handle_set_parameter_parameter(rtsp_conn_info *conn, - rtsp_message *req, rtsp_message *resp) { - char *cp = req->content; - int cp_left = req->contentlength; - char *next; - while (cp_left && cp) { - next = nextline(cp, cp_left); - cp_left -= next-cp; +static void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req, + rtsp_message *resp) { + char *cp = req->content; + int cp_left = req->contentlength; + char *next; + while (cp_left && cp) { + next = nextline(cp, cp_left); + cp_left -= next - cp; - if (!strncmp(cp, "volume: ", 8)) { - if (config.ignore_volume_control==0) { - float volume = atof(cp + 8); - debug(2, "volume: %f\n", volume); - player_volume(volume); - } - } else -#ifdef CONFIG_METADATA - if(!strncmp(cp, "progress: ", 10)) { - char *progress = cp + 10; - debug(2, "progress: \"%s\"\n", progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per second - send_ssnc_metadata('prgr',strdup(progress),strlen(progress),1); - } else -#endif - { - debug(1, "unrecognised parameter: \"%s\" (%d)\n", cp, strlen(cp)); - } - cp = next; + if (!strncmp(cp, "volume: ", 8)) { + if (config.ignore_volume_control == 0) { + float volume = atof(cp + 8); + debug(2, "volume: %f\n", volume); + player_volume(volume); + } + } else +#ifdef CONFIG_METADATA + if (!strncmp(cp, "progress: ", 10)) { + char *progress = cp + 10; + debug(2, "progress: \"%s\"\n", + progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per second + send_ssnc_metadata('prgr', strdup(progress), strlen(progress), 1); + } else +#endif + { + debug(1, "unrecognised parameter: \"%s\" (%d)\n", cp, strlen(cp)); } + cp = next; + } } - #ifdef CONFIG_METADATA // Metadata is not used by shairport-sync. -// Instead we send all metadata to a fifo pipe, so that other apps can listen to the pipe and use the metadata. +// Instead we send all metadata to a fifo pipe, so that other apps can listen to the pipe and use +// the metadata. -// We use two 4-character codes to identify each piece of data and we send the data itself, if any, in base64 form. +// We use two 4-character codes to identify each piece of data and we send the data itself, if any, +// in base64 form. // The first 4-character code, called the "type", is either: -// 'core' for all the regular metadadata coming from iTunes, etc., or -// 'ssnc' (for 'shairport-sync') for all metadata coming from Shairport Sync itself, such as start/end delimiters, etc. +// 'core' for all the regular metadadata coming from iTunes, etc., or +// 'ssnc' (for 'shairport-sync') for all metadata coming from Shairport Sync itself, such as +// start/end delimiters, etc. -// For 'core' metadata, the second 4-character code is the 4-character metadata code coming from iTunes etc. +// For 'core' metadata, the second 4-character code is the 4-character metadata code coming from +// iTunes etc. // For 'ssnc' metadata, the second 4-character code is used to distinguish the messages. -// Cover art is not tagged in the same way as other metadata, it seems, so is sent as an 'ssnc' type metadata message with the code 'PICT' +// Cover art is not tagged in the same way as other metadata, it seems, so is sent as an 'ssnc' type +// metadata message with the code 'PICT' // Here are the 'ssnc' codes defined so far: -// 'PICT' -- the payload is a picture, either a JPEG or a PNG. Check the first few bytes to see which. +// 'PICT' -- the payload is a picture, either a JPEG or a PNG. Check the first few bytes to see +// which. // 'pbeg' -- play stream begin. No arguments // 'pend' -- play stream end. No arguments // 'pfls' -- play stream flush. No arguments // 'prsm' -- play stream resume. No arguments -// 'pvol' -- play volume. The volume is sent as a string -- "airplay_volume,volume,lowest_volume,highest_volume,has_true_mute,is_muted" +// 'pvol' -- play volume. The volume is sent as a string -- +// "airplay_volume,volume,lowest_volume,highest_volume,has_true_mute,is_muted" // volume, lowest_volume and highest_volume are given in dB -// is_muted is 1 if [true] mute is enabled, 0 otherwise. -// The "airplay_volume" is what's sent to the player, and is from 0.00 down to -30.00, with -144.00 meaning mute. +// is_muted is 1 if [true] mute is enabled, 0 otherwise. +// The "airplay_volume" is what's sent to the player, and is from 0.00 down to -30.00, +// with -144.00 meaning mute. // This is linear on the volume control slider of iTunes or iOS AirPLay -// 'prgr' -- progress -- this is metadata from AirPlay consisting of RTP timestamps for the start of the current play sequence, the current play point and the end of the play sequence. +// 'prgr' -- progress -- this is metadata from AirPlay consisting of RTP timestamps for the start +// of the current play sequence, the current play point and the end of the play sequence. // I guess the timestamps wrap at 2^32. // 'mdst' -- a sequence of metadata is about to start // 'mden' -- a sequence of metadata has ended // 'snam' -- the name of the originator -- e.g. "Joe's iPhone" or "iTunes...". -// +// // including a simple base64 encoder to minimise malloc/free activity // From Stack Overflow, with thanks: @@ -812,49 +821,45 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, // add _so to end of name to avoid confusion with polarssl's implementation -static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', - 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', - 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', - 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', - '4', '5', '6', '7', '8', '9', '+', '/'}; +static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; static int mod_table[] = {0, 2, 1}; -// pass in a pointer to the data, its length, a pointer to the output buffer and a pointer to an int containing its maximum length +// pass in a pointer to the data, its length, a pointer to the output buffer and a pointer to an int +// containing its maximum length // the actual length will be returned. -char *base64_encode_so(const unsigned char *data, - size_t input_length, - char *encoded_data, - size_t *output_length) { +char *base64_encode_so(const unsigned char *data, size_t input_length, char *encoded_data, + size_t *output_length) { - size_t calculated_output_length = 4 * ((input_length + 2) / 3); - if (calculated_output_length> *output_length) - return(NULL); - *output_length = calculated_output_length; - - int i,j; - for (i = 0, j = 0; i < input_length;) { + size_t calculated_output_length = 4 * ((input_length + 2) / 3); + if (calculated_output_length > *output_length) + return (NULL); + *output_length = calculated_output_length; - uint32_t octet_a = i < input_length ? (unsigned char)data[i++] : 0; - uint32_t octet_b = i < input_length ? (unsigned char)data[i++] : 0; - uint32_t octet_c = i < input_length ? (unsigned char)data[i++] : 0; + int i, j; + for (i = 0, j = 0; i < input_length;) { - uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; + uint32_t octet_a = i < input_length ? (unsigned char)data[i++] : 0; + uint32_t octet_b = i < input_length ? (unsigned char)data[i++] : 0; + uint32_t octet_c = i < input_length ? (unsigned char)data[i++] : 0; - encoded_data[j++] = encoding_table[(triple >> 3 * 6) & 0x3F]; - encoded_data[j++] = encoding_table[(triple >> 2 * 6) & 0x3F]; - encoded_data[j++] = encoding_table[(triple >> 1 * 6) & 0x3F]; - encoded_data[j++] = encoding_table[(triple >> 0 * 6) & 0x3F]; - } + uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; - for (i = 0; i < mod_table[input_length % 3]; i++) - encoded_data[*output_length - 1 - i] = '='; + encoded_data[j++] = encoding_table[(triple >> 3 * 6) & 0x3F]; + encoded_data[j++] = encoding_table[(triple >> 2 * 6) & 0x3F]; + encoded_data[j++] = encoding_table[(triple >> 1 * 6) & 0x3F]; + encoded_data[j++] = encoding_table[(triple >> 0 * 6) & 0x3F]; + } - return encoded_data; + for (i = 0; i < mod_table[input_length % 3]; i++) + encoded_data[*output_length - 1 - i] = '='; + + return encoded_data; } // with thanks! @@ -869,68 +874,67 @@ metadata_package metadata_queue_items[metadata_queue_size]; static pthread_t metadata_thread; void metadata_create(void) { - if (config.metadata_enabled==0) - return; + if (config.metadata_enabled == 0) + return; - size_t pl = strlen(config.metadata_pipename) + 1; + size_t pl = strlen(config.metadata_pipename) + 1; - char* path = malloc(pl+1); - snprintf(path, pl+1, "%s", config.metadata_pipename); + char *path = malloc(pl + 1); + snprintf(path, pl + 1, "%s", config.metadata_pipename); - if (mkfifo(path, 0644) && errno != EEXIST) - die("Could not create metadata FIFO %s", path); + if (mkfifo(path, 0644) && errno != EEXIST) + die("Could not create metadata FIFO %s", path); - free(path); + free(path); } void metadata_open(void) { - if (config.metadata_enabled==0) - return; + if (config.metadata_enabled == 0) + return; - size_t pl = strlen(config.metadata_pipename) + 1; + size_t pl = strlen(config.metadata_pipename) + 1; - char* path = malloc(pl+1); - snprintf(path, pl+1, "%s", config.metadata_pipename); + char *path = malloc(pl + 1); + snprintf(path, pl + 1, "%s", config.metadata_pipename); - fd = open(path, O_WRONLY | O_NONBLOCK); - //if (fd < 0) - // debug(1, "Could not open metadata FIFO %s. Will try again later.", path); + fd = open(path, O_WRONLY | O_NONBLOCK); + // if (fd < 0) + // debug(1, "Could not open metadata FIFO %s. Will try again later.", path); - free(path); + free(path); } static void metadata_close(void) { - close(fd); - fd = -1; + close(fd); + fd = -1; } ssize_t non_blocking_write(int fd, const void *buf, size_t count) { // debug(1,"writing %u to pipe...",count); // we are assuming that the count is always smaller than the FIFO's buffer struct pollfd ufds[1]; - ssize_t reply; + ssize_t reply; do { - ufds[0].fd=fd; + ufds[0].fd = fd; ufds[0].events = POLLOUT; - int rv = poll(ufds,1,5000); - if (rv==-1) - debug(1,"error waiting for pipe to unblock..."); - if (rv==0) - debug(1,"timeout waiting for pipe to unblock"); - reply=write(fd,buf,count); - if ((reply==-1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) - debug(1,"writing to pipe will block..."); -// else -// debug(1,"writing %u to pipe done...",reply); - } while ((reply==-1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))); + int rv = poll(ufds, 1, 5000); + if (rv == -1) + debug(1, "error waiting for pipe to unblock..."); + if (rv == 0) + debug(1, "timeout waiting for pipe to unblock"); + reply = write(fd, buf, count); + if ((reply == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) + debug(1, "writing to pipe will block..."); + // else + // debug(1,"writing %u to pipe done...",reply); + } while ((reply == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))); return reply; - -// return write(fd,buf,count); + // return write(fd,buf,count); } -void metadata_process(uint32_t type,uint32_t code,char *data,uint32_t length) { - debug(2,"Process metadata with type %x, code %x and length %u.",type,code,length); +void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) { + debug(2, "Process metadata with type %x, code %x and length %u.", type, code, length); int ret; // readers may go away and come back if (fd < 0) @@ -938,14 +942,15 @@ void metadata_process(uint32_t type,uint32_t code,char *data,uint32_t length) { if (fd < 0) return; char thestring[1024]; - snprintf(thestring,1024,"%x%x%u\n",type,code,length); + snprintf(thestring, 1024, "%x%x%u\n", type, code, + length); ret = non_blocking_write(fd, thestring, strlen(thestring)); if (ret < 1) return; - if ((data!=NULL) && (length>0)) { - snprintf(thestring,1024,"\n"); + if ((data != NULL) && (length > 0)) { + snprintf(thestring, 1024, "\n"); ret = non_blocking_write(fd, thestring, strlen(thestring)); - if (ret < 1) // no reader + if (ret < 1) // no reader return; // here, we write the data in base64 form using our nice base64 encoder // but, we break it into lines of 76 output characters, except for the last one. @@ -954,37 +959,37 @@ void metadata_process(uint32_t type,uint32_t code,char *data,uint32_t length) { char *remaining_data = data; size_t towrite_count; char outbuf[76]; - while ((remaining_count) && (ret>=0)) { - size_t towrite_count = remaining_count; - if (towrite_count>57) - towrite_count = 57; - size_t outbuf_size = 76; // size of output buffer on entry, length of result on exit - if (base64_encode_so(remaining_data, towrite_count, outbuf, &outbuf_size)==NULL) - debug(1,"Error encoding base64 data."); - //debug(1,"Remaining count: %d ret: %d, outbuf_size: %d.",remaining_count,ret,outbuf_size); - ret = non_blocking_write(fd,outbuf,outbuf_size); - if (ret<0) - return; - remaining_data+=towrite_count; - remaining_count-=towrite_count; + while ((remaining_count) && (ret >= 0)) { + size_t towrite_count = remaining_count; + if (towrite_count > 57) + towrite_count = 57; + size_t outbuf_size = 76; // size of output buffer on entry, length of result on exit + if (base64_encode_so(remaining_data, towrite_count, outbuf, &outbuf_size) == NULL) + debug(1, "Error encoding base64 data."); + // debug(1,"Remaining count: %d ret: %d, outbuf_size: %d.",remaining_count,ret,outbuf_size); + ret = non_blocking_write(fd, outbuf, outbuf_size); + if (ret < 0) + return; + remaining_data += towrite_count; + remaining_count -= towrite_count; // ret = write(fd,"\r\n",2); // if (ret<0) - // return; + // return; } - snprintf(thestring,1024,"\n"); + snprintf(thestring, 1024, "\n"); ret = non_blocking_write(fd, thestring, strlen(thestring)); - if (ret < 1) // no reader + if (ret < 1) // no reader return; } } -void* metadata_thread_function(void *ignore) { +void *metadata_thread_function(void *ignore) { metadata_create(); metadata_package pack; while (1) { pc_queue_get_item(&metadata_queue, &pack); if (config.metadata_enabled) - metadata_process(pack.type,pack.code,pack.data,pack.length); + metadata_process(pack.type, pack.code, pack.data, pack.length); if (pack.carrier) msg_free(pack.carrier); // release the message else if (pack.data) @@ -995,13 +1000,15 @@ void* metadata_thread_function(void *ignore) { void metadata_init(void) { // create a pc_queue for passing information to a threaded metadata handler - pc_queue_init(&metadata_queue,(char *)&metadata_queue_items,sizeof(metadata_package),metadata_queue_size); + pc_queue_init(&metadata_queue, (char *)&metadata_queue_items, sizeof(metadata_package), + metadata_queue_size); int ret = pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL); if (ret) - debug(1,"Failed to create metadata thread!"); + debug(1, "Failed to create metadata thread!"); } -int send_metadata(uint32_t type,uint32_t code,char *data,uint32_t length, rtsp_message* carrier, int block) { +int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier, + int block) { metadata_package pack; pack.type = type; pack.code = code; @@ -1010,102 +1017,104 @@ int send_metadata(uint32_t type,uint32_t code,char *data,uint32_t length, rtsp_m if (carrier) msg_retain(carrier); pack.carrier = carrier; - int rc = pc_queue_add_item(&metadata_queue,&pack,block); - if ((rc==EBUSY) && (carrier)) + int rc = pc_queue_add_item(&metadata_queue, &pack, block); + if ((rc == EBUSY) && (carrier)) msg_free(carrier); - if (rc==EBUSY) - warn("Metadata queue is busy, dropping message of type 0x%08X, code 0x%08X.",type,code); + if (rc == EBUSY) + warn("Metadata queue is busy, dropping message of type 0x%08X, code 0x%08X.", type, code); return rc; } -static void handle_set_parameter_metadata(rtsp_conn_info *conn, - rtsp_message *req, - rtsp_message *resp) { - char *cp = req->content; - int cl = req->contentlength; +static void handle_set_parameter_metadata(rtsp_conn_info *conn, rtsp_message *req, + rtsp_message *resp) { + char *cp = req->content; + int cl = req->contentlength; - unsigned int off = 8; - - // inform the listener that a set of metadata is starting - // this doesn't include the cover art though... - - // parameters: type, code, pointer to data or NULL, length of data or NULL, the rtsp_message or NULL - // the rtsp_message is sent for 'core' messages, because it contains the data and must not be - // freed until the data has been read. So, it is passed to send_metadata to be retained, - // sent to the thread where metadata is processed and released (and probably freed). - - // The reading of the parameters is a bit complex - // If the rtsp_message field is non-null, then it represents an rtsp_message which should be freed in the thread handler when the parameter pointed to by the pointer and specified by the length is finished with - // If the rtsp_message is NULL, then if the pointer is non-null, it points to a malloc'ed block and should be freed when the thread is finished with it. The length of the data in the block is given in length - // If the rtsp_message is NULL and the pointer is also NULL, nothing further is done. - - send_metadata('ssnc','mdst',NULL,0,NULL,1); + unsigned int off = 8; - while (off < cl) { - // pick up the metadata tag as an unsigned longint - uint32_t itag = ntohl(*(uint32_t *)(cp+off)); - off += sizeof(uint32_t); + // inform the listener that a set of metadata is starting + // this doesn't include the cover art though... - // pick up the length of the data - uint32_t vl = ntohl(*(uint32_t *)(cp+off)); - off += sizeof(uint32_t); - - // pass the data over - if (vl==0) - send_metadata('core',itag,NULL,0,NULL,1); - else - send_metadata('core',itag,(char *)(cp+off),vl,req,1); - - // move on to the next item - off += vl; - } - - // inform the listener that a set of metadata is ending - send_metadata('ssnc','mden',NULL,0,NULL,1); + // parameters: type, code, pointer to data or NULL, length of data or NULL, the rtsp_message or + // NULL + // the rtsp_message is sent for 'core' messages, because it contains the data and must not be + // freed until the data has been read. So, it is passed to send_metadata to be retained, + // sent to the thread where metadata is processed and released (and probably freed). + + // The reading of the parameters is a bit complex + // If the rtsp_message field is non-null, then it represents an rtsp_message which should be freed + // in the thread handler when the parameter pointed to by the pointer and specified by the length + // is finished with + // If the rtsp_message is NULL, then if the pointer is non-null, it points to a malloc'ed block + // and should be freed when the thread is finished with it. The length of the data in the block is + // given in length + // If the rtsp_message is NULL and the pointer is also NULL, nothing further is done. + + send_metadata('ssnc', 'mdst', NULL, 0, NULL, 1); + + while (off < cl) { + // pick up the metadata tag as an unsigned longint + uint32_t itag = ntohl(*(uint32_t *)(cp + off)); + off += sizeof(uint32_t); + + // pick up the length of the data + uint32_t vl = ntohl(*(uint32_t *)(cp + off)); + off += sizeof(uint32_t); + + // pass the data over + if (vl == 0) + send_metadata('core', itag, NULL, 0, NULL, 1); + else + send_metadata('core', itag, (char *)(cp + off), vl, req, 1); + + // move on to the next item + off += vl; + } + + // inform the listener that a set of metadata is ending + send_metadata('ssnc', 'mden', NULL, 0, NULL, 1); // send the user some shairport-originated metadata // send the name of the player, e.g. "Joe's iPhone" or "iTunes" - send_metadata('ssnc','sndr',strdup(sender_name),strlen(sender_name),NULL,1); + send_metadata('ssnc', 'sndr', strdup(sender_name), strlen(sender_name), NULL, 1); } #endif -static void handle_set_parameter(rtsp_conn_info *conn, - rtsp_message *req, rtsp_message *resp) { - //if (!req->contentlength) - // debug(1, "received empty SET_PARAMETER request."); +static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { + // if (!req->contentlength) + // debug(1, "received empty SET_PARAMETER request."); - char *ct = msg_get_header(req, "Content-Type"); + char *ct = msg_get_header(req, "Content-Type"); - if (ct) { - debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct); + if (ct) { + debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct); #ifdef CONFIG_METADATA - if (!strncmp(ct, "application/x-dmap-tagged", 25)) { - debug(2, "received metadata tags in SET_PARAMETER request."); - handle_set_parameter_metadata(conn, req, resp); - } else if (!strncmp(ct, "image", 5)) { - // debug(1, "received image in SET_PARAMETER request."); - // note: the image/type tag isn't reliable, so it's not being sent - // -- best look at the first few bytes of the image - send_metadata('ssnc','PICT',req->content,req->contentlength,req,1); - } else -#endif - if (!strncmp(ct, "text/parameters", 15)) { - debug(2, "received parameters in SET_PARAMETER request."); - handle_set_parameter_parameter(conn, req, resp); - } else { - debug(1, "received unknown Content-Type \"%s\" in SET_PARAMETER request.", ct); - } + if (!strncmp(ct, "application/x-dmap-tagged", 25)) { + debug(2, "received metadata tags in SET_PARAMETER request."); + handle_set_parameter_metadata(conn, req, resp); + } else if (!strncmp(ct, "image", 5)) { + // debug(1, "received image in SET_PARAMETER request."); + // note: the image/type tag isn't reliable, so it's not being sent + // -- best look at the first few bytes of the image + send_metadata('ssnc', 'PICT', req->content, req->contentlength, req, 1); + } else +#endif + if (!strncmp(ct, "text/parameters", 15)) { + debug(2, "received parameters in SET_PARAMETER request."); + handle_set_parameter_parameter(conn, req, resp); } else { - debug(1, "missing Content-Type header in SET_PARAMETER request."); + debug(1, "received unknown Content-Type \"%s\" in SET_PARAMETER request.", ct); } + } else { + debug(1, "missing Content-Type header in SET_PARAMETER request."); + } - resp->respcode = 200; + resp->respcode = 200; } -static void handle_announce(rtsp_conn_info *conn, - rtsp_message *req, rtsp_message *resp) { +static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { // interrupt session if permitted - if ((config.allow_session_interruption==1) || (pthread_mutex_trylock(&play_lock) == 0)) { + if ((config.allow_session_interruption == 1) || (pthread_mutex_trylock(&play_lock) == 0)) { char *paesiv = NULL; char *prsaaeskey = NULL; char *pfmtp = NULL; @@ -1114,16 +1123,16 @@ static void handle_announce(rtsp_conn_info *conn, char *next; while (cp_left && cp) { next = nextline(cp, cp_left); - cp_left -= next-cp; + cp_left -= next - cp; if (!strncmp(cp, "a=fmtp:", 7)) - pfmtp = cp+7; + pfmtp = cp + 7; if (!strncmp(cp, "a=aesiv:", 8)) - paesiv = cp+8; + paesiv = cp + 8; if (!strncmp(cp, "a=rsaaeskey:", 12)) - prsaaeskey = cp+12; + prsaaeskey = cp + 12; cp = next; } @@ -1155,25 +1164,25 @@ static void handle_announce(rtsp_conn_info *conn, free(aeskey); int i; - for (i=0; istream.fmtp)/sizeof(conn->stream.fmtp[0]); i++) + for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++) conn->stream.fmtp[i] = atoi(strsep(&pfmtp, " \t")); - + char *hdr = msg_get_header(req, "X-Apple-Client-Name"); if (hdr) { - strncpy(sender_name,hdr,1024); - debug(1,"Play connection from \"%s\".",hdr); + strncpy(sender_name, hdr, 1024); + debug(1, "Play connection from \"%s\".", hdr); } else { hdr = msg_get_header(req, "User-Agent"); if (hdr) { - debug(1,"Play connection from \"%s\".",hdr); - strncpy(sender_name,hdr,1024); - } else - sender_name[0]=0; - } + debug(1, "Play connection from \"%s\".", hdr); + strncpy(sender_name, hdr, 1024); + } else + sender_name[0] = 0; + } resp->respcode = 200; } else { resp->respcode = 453; - debug(1,"Already playing."); + debug(1, "Already playing."); } out: @@ -1182,422 +1191,416 @@ out: } } - static struct method_handler { - char *method; - void (*handler)(rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp); -} method_handlers[] = { - {"OPTIONS", handle_options}, - {"ANNOUNCE", handle_announce}, - {"FLUSH", handle_flush}, - {"TEARDOWN", handle_teardown}, - {"SETUP", handle_setup}, - {"GET_PARAMETER", handle_ignore}, - {"SET_PARAMETER", handle_set_parameter}, - {"RECORD", handle_record}, - {NULL, NULL} -}; + char *method; + void (*handler)(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp); +} method_handlers[] = {{"OPTIONS", handle_options}, + {"ANNOUNCE", handle_announce}, + {"FLUSH", handle_flush}, + {"TEARDOWN", handle_teardown}, + {"SETUP", handle_setup}, + {"GET_PARAMETER", handle_ignore}, + {"SET_PARAMETER", handle_set_parameter}, + {"RECORD", handle_record}, + {NULL, NULL}}; static void apple_challenge(int fd, rtsp_message *req, rtsp_message *resp) { - char *hdr = msg_get_header(req, "Apple-Challenge"); - if (!hdr) - return; + char *hdr = msg_get_header(req, "Apple-Challenge"); + if (!hdr) + return; - SOCKADDR fdsa; - socklen_t sa_len = sizeof(fdsa); - getsockname(fd, (struct sockaddr*)&fdsa, &sa_len); + SOCKADDR fdsa; + socklen_t sa_len = sizeof(fdsa); + getsockname(fd, (struct sockaddr *)&fdsa, &sa_len); - int chall_len; - uint8_t *chall = base64_dec(hdr, &chall_len); - uint8_t buf[48], *bp = buf; - int i; - memset(buf, 0, sizeof(buf)); + int chall_len; + uint8_t *chall = base64_dec(hdr, &chall_len); + uint8_t buf[48], *bp = buf; + int i; + memset(buf, 0, sizeof(buf)); - if (chall_len > 16) { - warn("oversized Apple-Challenge!"); - free(chall); - return; - } - memcpy(bp, chall, chall_len); + if (chall_len > 16) { + warn("oversized Apple-Challenge!"); free(chall); - bp += chall_len; + return; + } + memcpy(bp, chall, chall_len); + free(chall); + bp += chall_len; #ifdef AF_INET6 - if (fdsa.SAFAMILY == AF_INET6) { - struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)(&fdsa); - memcpy(bp, sa6->sin6_addr.s6_addr, 16); - bp += 16; - } else + if (fdsa.SAFAMILY == AF_INET6) { + struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)(&fdsa); + memcpy(bp, sa6->sin6_addr.s6_addr, 16); + bp += 16; + } else #endif - { - struct sockaddr_in *sa = (struct sockaddr_in*)(&fdsa); - memcpy(bp, &sa->sin_addr.s_addr, 4); - bp += 4; - } + { + struct sockaddr_in *sa = (struct sockaddr_in *)(&fdsa); + memcpy(bp, &sa->sin_addr.s_addr, 4); + bp += 4; + } - for (i=0; i<6; i++) - *bp++ = config.hw_addr[i]; + for (i = 0; i < 6; i++) + *bp++ = config.hw_addr[i]; - int buflen, resplen; - buflen = bp-buf; - if (buflen < 0x20) - buflen = 0x20; + int buflen, resplen; + buflen = bp - buf; + if (buflen < 0x20) + buflen = 0x20; - uint8_t *challresp = rsa_apply(buf, buflen, &resplen, RSA_MODE_AUTH); - char *encoded = base64_enc(challresp, resplen); + uint8_t *challresp = rsa_apply(buf, buflen, &resplen, RSA_MODE_AUTH); + char *encoded = base64_enc(challresp, resplen); - // strip the padding. - char *padding = strchr(encoded, '='); - if (padding) - *padding = 0; + // strip the padding. + char *padding = strchr(encoded, '='); + if (padding) + *padding = 0; - msg_add_header(resp, "Apple-Response", encoded); - free(challresp); - free(encoded); + msg_add_header(resp, "Apple-Response", encoded); + free(challresp); + free(encoded); } static char *make_nonce(void) { - uint8_t random[8]; - int fd = open("/dev/random", O_RDONLY); - if (fd < 0) - die("could not open /dev/random!"); - int ignore = read(fd, random, sizeof(random)); - close(fd); - return base64_enc(random, 8); + uint8_t random[8]; + int fd = open("/dev/random", O_RDONLY); + if (fd < 0) + die("could not open /dev/random!"); + int ignore = read(fd, random, sizeof(random)); + close(fd); + return base64_enc(random, 8); } static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { - if (!config.password) - return 0; - if (!*nonce) { - *nonce = make_nonce(); - goto authenticate; - } + if (!config.password) + return 0; + if (!*nonce) { + *nonce = make_nonce(); + goto authenticate; + } - char *hdr = msg_get_header(req, "Authorization"); - if (!hdr || strncmp(hdr, "Digest ", 7)) - goto authenticate; + char *hdr = msg_get_header(req, "Authorization"); + if (!hdr || strncmp(hdr, "Digest ", 7)) + goto authenticate; - char *realm = strstr(hdr, "realm=\""); - char *username = strstr(hdr, "username=\""); - char *response = strstr(hdr, "response=\""); - char *uri = strstr(hdr, "uri=\""); + char *realm = strstr(hdr, "realm=\""); + char *username = strstr(hdr, "username=\""); + char *response = strstr(hdr, "response=\""); + char *uri = strstr(hdr, "uri=\""); - if (!realm || !username || !response || !uri) - goto authenticate; + if (!realm || !username || !response || !uri) + goto authenticate; - char *quote; - realm = strchr(realm, '"') + 1; - if (!(quote = strchr(realm, '"'))) - goto authenticate; - *quote = 0; - username = strchr(username, '"') + 1; - if (!(quote = strchr(username, '"'))) - goto authenticate; - *quote = 0; - response = strchr(response, '"') + 1; - if (!(quote = strchr(response, '"'))) - goto authenticate; - *quote = 0; - uri = strchr(uri, '"') + 1; - if (!(quote = strchr(uri, '"'))) - goto authenticate; - *quote = 0; + char *quote; + realm = strchr(realm, '"') + 1; + if (!(quote = strchr(realm, '"'))) + goto authenticate; + *quote = 0; + username = strchr(username, '"') + 1; + if (!(quote = strchr(username, '"'))) + goto authenticate; + *quote = 0; + response = strchr(response, '"') + 1; + if (!(quote = strchr(response, '"'))) + goto authenticate; + *quote = 0; + uri = strchr(uri, '"') + 1; + if (!(quote = strchr(uri, '"'))) + goto authenticate; + *quote = 0; + + uint8_t digest_urp[16], digest_mu[16], digest_total[16]; - uint8_t digest_urp[16], digest_mu[16], digest_total[16]; - #ifdef HAVE_LIBSSL - MD5_CTX ctx; - - MD5_Init(&ctx); - MD5_Update(&ctx, username, strlen(username)); - MD5_Update(&ctx, ":", 1); - MD5_Update(&ctx, realm, strlen(realm)); - MD5_Update(&ctx, ":", 1); - MD5_Update(&ctx, config.password, strlen(config.password)); - MD5_Final(digest_urp, &ctx); - MD5_Init(&ctx); - MD5_Update(&ctx, req->method, strlen(req->method)); - MD5_Update(&ctx, ":", 1); - MD5_Update(&ctx, uri, strlen(uri)); - MD5_Final(digest_mu, &ctx); + MD5_CTX ctx; + + MD5_Init(&ctx); + MD5_Update(&ctx, username, strlen(username)); + MD5_Update(&ctx, ":", 1); + MD5_Update(&ctx, realm, strlen(realm)); + MD5_Update(&ctx, ":", 1); + MD5_Update(&ctx, config.password, strlen(config.password)); + MD5_Final(digest_urp, &ctx); + MD5_Init(&ctx); + MD5_Update(&ctx, req->method, strlen(req->method)); + MD5_Update(&ctx, ":", 1); + MD5_Update(&ctx, uri, strlen(uri)); + MD5_Final(digest_mu, &ctx); #endif - #ifdef HAVE_LIBPOLARSSL - md5_context tctx; - md5_starts(&tctx); - md5_update(&tctx, (const unsigned char *)username, strlen(username)); - md5_update(&tctx, (unsigned char *) ":", 1); - md5_update(&tctx, (const unsigned char *)realm, strlen(realm)); - md5_update(&tctx, (unsigned char *) ":", 1); - md5_update(&tctx, (const unsigned char *)config.password, strlen(config.password)); - md5_finish(&tctx,digest_urp); - md5_starts(&tctx); - md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method)); - md5_update(&tctx, (unsigned char *) ":", 1); - md5_update(&tctx, (const unsigned char *)uri, strlen(uri)); - md5_finish(&tctx,digest_mu); + md5_context tctx; + md5_starts(&tctx); + md5_update(&tctx, (const unsigned char *)username, strlen(username)); + md5_update(&tctx, (unsigned char *)":", 1); + md5_update(&tctx, (const unsigned char *)realm, strlen(realm)); + md5_update(&tctx, (unsigned char *)":", 1); + md5_update(&tctx, (const unsigned char *)config.password, strlen(config.password)); + md5_finish(&tctx, digest_urp); + md5_starts(&tctx); + md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method)); + md5_update(&tctx, (unsigned char *)":", 1); + md5_update(&tctx, (const unsigned char *)uri, strlen(uri)); + md5_finish(&tctx, digest_mu); #endif - - int i; - unsigned char buf[33]; - for (i=0; i<16; i++) - sprintf((char *)buf + 2*i, "%02X", digest_urp[i]); - + + int i; + unsigned char buf[33]; + for (i = 0; i < 16; i++) + sprintf((char *)buf + 2 * i, "%02X", digest_urp[i]); + #ifdef HAVE_LIBSSL - MD5_Init(&ctx); - MD5_Update(&ctx, buf, 32); - MD5_Update(&ctx, ":", 1); - MD5_Update(&ctx, *nonce, strlen(*nonce)); - MD5_Update(&ctx, ":", 1); - for (i=0; i<16; i++) - sprintf(buf + 2*i, "%02X", digest_mu[i]); - MD5_Update(&ctx, buf, 32); - MD5_Final(digest_total, &ctx); - #endif - - -#ifdef HAVE_LIBPOLARSSL - md5_starts(&tctx); - md5_update(&tctx, buf, 32); - md5_update(&tctx, (unsigned char *) ":", 1); - md5_update(&tctx, (const unsigned char *)*nonce, strlen(*nonce)); - md5_update(&tctx, (unsigned char *) ":", 1); - for (i=0; i<16; i++) - sprintf((char *)buf + 2*i,"%02X", digest_mu[i]); - md5_update(&tctx, buf, 32); - md5_finish(&tctx,digest_total); + MD5_Init(&ctx); + MD5_Update(&ctx, buf, 32); + MD5_Update(&ctx, ":", 1); + MD5_Update(&ctx, *nonce, strlen(*nonce)); + MD5_Update(&ctx, ":", 1); + for (i = 0; i < 16; i++) + sprintf(buf + 2 * i, "%02X", digest_mu[i]); + MD5_Update(&ctx, buf, 32); + MD5_Final(digest_total, &ctx); #endif - for (i=0; i<16; i++) - sprintf((char *)buf + 2*i,"%02X", digest_total[i]); +#ifdef HAVE_LIBPOLARSSL + md5_starts(&tctx); + md5_update(&tctx, buf, 32); + md5_update(&tctx, (unsigned char *)":", 1); + md5_update(&tctx, (const unsigned char *)*nonce, strlen(*nonce)); + md5_update(&tctx, (unsigned char *)":", 1); + for (i = 0; i < 16; i++) + sprintf((char *)buf + 2 * i, "%02X", digest_mu[i]); + md5_update(&tctx, buf, 32); + md5_finish(&tctx, digest_total); +#endif - if (!strcmp(response, (const char *)buf)) - return 0; - warn("auth failed"); + for (i = 0; i < 16; i++) + sprintf((char *)buf + 2 * i, "%02X", digest_total[i]); + + if (!strcmp(response, (const char *)buf)) + return 0; + warn("auth failed"); authenticate: - resp->respcode = 401; - int hdrlen = strlen(*nonce) + 40; - char *authhdr = malloc(hdrlen); - snprintf(authhdr, hdrlen, "Digest realm=\"taco\", nonce=\"%s\"", *nonce); - msg_add_header(resp, "WWW-Authenticate", authhdr); - free(authhdr); - return 1; + resp->respcode = 401; + int hdrlen = strlen(*nonce) + 40; + char *authhdr = malloc(hdrlen); + snprintf(authhdr, hdrlen, "Digest realm=\"taco\", nonce=\"%s\"", *nonce); + msg_add_header(resp, "WWW-Authenticate", authhdr); + free(authhdr); + return 1; } static void *rtsp_conversation_thread_func(void *pconn) { - // SIGUSR1 is used to interrupt this thread if blocked for read - sigset_t set; - sigemptyset(&set); - sigaddset(&set, SIGUSR1); - pthread_sigmask(SIG_UNBLOCK, &set, NULL); + // SIGUSR1 is used to interrupt this thread if blocked for read + sigset_t set; + sigemptyset(&set); + sigaddset(&set, SIGUSR1); + pthread_sigmask(SIG_UNBLOCK, &set, NULL); - rtsp_conn_info *conn = pconn; + rtsp_conn_info *conn = pconn; - rtsp_message *req, *resp; - char *hdr, *auth_nonce = NULL; - - enum rtsp_read_request_response reply; - - do { - reply=rtsp_read_request(conn->fd,&req); - if (reply==rtsp_read_request_response_ok) { - resp = msg_init(); - resp->respcode = 400; + rtsp_message *req, *resp; + char *hdr, *auth_nonce = NULL; - apple_challenge(conn->fd, req, resp); - hdr = msg_get_header(req, "CSeq"); - if (hdr) - msg_add_header(resp, "CSeq", hdr); - msg_add_header(resp, "Audio-Jack-Status", "connected; type=analog"); + enum rtsp_read_request_response reply; - if (rtsp_auth(&auth_nonce, req, resp)) - goto respond; + do { + reply = rtsp_read_request(conn->fd, &req); + if (reply == rtsp_read_request_response_ok) { + resp = msg_init(); + resp->respcode = 400; - struct method_handler *mh; - for (mh=method_handlers; mh->method; mh++) { - if (!strcmp(mh->method, req->method)) { - // debug(1,"RTSP Packet received of type \"%s\":",mh->method), - // msg_print_debug_headers(req); - mh->handler(conn, req, resp); - // debug(1,"RTSP Response:"); - // msg_print_debug_headers(resp); - break; - } + apple_challenge(conn->fd, req, resp); + hdr = msg_get_header(req, "CSeq"); + if (hdr) + msg_add_header(resp, "CSeq", hdr); + msg_add_header(resp, "Audio-Jack-Status", "connected; type=analog"); + + if (rtsp_auth(&auth_nonce, req, resp)) + goto respond; + + struct method_handler *mh; + for (mh = method_handlers; mh->method; mh++) { + if (!strcmp(mh->method, req->method)) { + // debug(1,"RTSP Packet received of type \"%s\":",mh->method), + // msg_print_debug_headers(req); + mh->handler(conn, req, resp); + // debug(1,"RTSP Response:"); + // msg_print_debug_headers(resp); + break; } - -respond: - msg_write_response(conn->fd, resp); - msg_free(req); - msg_free(resp); - } else { - if (reply!=rtsp_read_request_response_shutdown_requested) - debug(1,"rtsp_read_request error %d, packet ignored.",(int)reply); } - } while (reply!=rtsp_read_request_response_shutdown_requested); - debug(1, "closing RTSP connection."); - if (conn->fd > 0) - close(conn->fd); - if (rtsp_playing()) { - rtp_shutdown(); - player_stop(); - pthread_mutex_unlock(&play_lock); - please_shutdown = 0; - pthread_mutex_unlock(&playing_mutex); + respond: + msg_write_response(conn->fd, resp); + msg_free(req); + msg_free(resp); + } else { + if (reply != rtsp_read_request_response_shutdown_requested) + debug(1, "rtsp_read_request error %d, packet ignored.", (int)reply); } - if (auth_nonce) - free(auth_nonce); - conn->running = 0; - debug(2, "terminating RTSP thread."); - return NULL; + } while (reply != rtsp_read_request_response_shutdown_requested); + + debug(1, "closing RTSP connection."); + if (conn->fd > 0) + close(conn->fd); + if (rtsp_playing()) { + rtp_shutdown(); + player_stop(); + pthread_mutex_unlock(&play_lock); + please_shutdown = 0; + pthread_mutex_unlock(&playing_mutex); + } + if (auth_nonce) + free(auth_nonce); + conn->running = 0; + debug(2, "terminating RTSP thread."); + return NULL; } // this function is not thread safe. -static const char* format_address(struct sockaddr *fsa) { - static char string[INETx_ADDRSTRLEN]; - void *addr; +static const char *format_address(struct sockaddr *fsa) { + static char string[INETx_ADDRSTRLEN]; + void *addr; #ifdef AF_INET6 - if (fsa->sa_family == AF_INET6) { - struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)(fsa); - addr = &(sa6->sin6_addr); - } else + if (fsa->sa_family == AF_INET6) { + struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)(fsa); + addr = &(sa6->sin6_addr); + } else #endif - { - struct sockaddr_in *sa = (struct sockaddr_in*)(fsa); - addr = &(sa->sin_addr); - } - return inet_ntop(fsa->sa_family, addr, string, sizeof(string)); + { + struct sockaddr_in *sa = (struct sockaddr_in *)(fsa); + addr = &(sa->sin_addr); + } + return inet_ntop(fsa->sa_family, addr, string, sizeof(string)); } void rtsp_listen_loop(void) { - struct addrinfo hints, *info, *p; - char portstr[6]; - int *sockfd = NULL; - int nsock = 0; - int i, ret; + struct addrinfo hints, *info, *p; + char portstr[6]; + int *sockfd = NULL; + int nsock = 0; + int i, ret; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_PASSIVE; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; - snprintf(portstr, 6, "%d", config.port); - - // debug(1,"listen socket port request is \"%s\".",portstr); + snprintf(portstr, 6, "%d", config.port); - ret = getaddrinfo(NULL, portstr, &hints, &info); - if (ret) { - die("getaddrinfo failed: %s", gai_strerror(ret)); + // debug(1,"listen socket port request is \"%s\".",portstr); + + ret = getaddrinfo(NULL, portstr, &hints, &info); + if (ret) { + die("getaddrinfo failed: %s", gai_strerror(ret)); + } + + for (p = info; p; p = p->ai_next) { + int fd = socket(p->ai_family, p->ai_socktype, IPPROTO_TCP); + int yes = 1; + + // Handle socket open failures if protocol unavailable (or IPV6 not handled) + if (fd == -1) { + // debug(1, "Failed to get socket: fam=%d, %s\n", p->ai_family, strerror(errno)); + continue; } - for (p=info; p; p=p->ai_next) { - int fd = socket(p->ai_family, p->ai_socktype, IPPROTO_TCP); - int yes = 1; - - // Handle socket open failures if protocol unavailable (or IPV6 not handled) - if (fd == -1) { - // debug(1, "Failed to get socket: fam=%d, %s\n", p->ai_family, strerror(errno)); - continue; - } - - ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); #ifdef IPV6_V6ONLY - // some systems don't support v4 access on v6 sockets, but some do. - // since we need to account for two sockets we might as well - // always. - if (p->ai_family == AF_INET6) { - ret |= setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)); - } + // some systems don't support v4 access on v6 sockets, but some do. + // since we need to account for two sockets we might as well + // always. + if (p->ai_family == AF_INET6) { + ret |= setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)); + } #endif - if (!ret) - ret = bind(fd, p->ai_addr, p->ai_addrlen); + if (!ret) + ret = bind(fd, p->ai_addr, p->ai_addrlen); - // one of the address families will fail on some systems that - // report its availability. do not complain. - if (ret) { - debug(1, "Failed to bind to address %s.", format_address(p->ai_addr)); - continue; - } - - listen(fd, 5); - nsock++; - sockfd = realloc(sockfd, nsock*sizeof(int)); - sockfd[nsock-1] = fd; + // one of the address families will fail on some systems that + // report its availability. do not complain. + if (ret) { + debug(1, "Failed to bind to address %s.", format_address(p->ai_addr)); + continue; } - freeaddrinfo(info); + listen(fd, 5); + nsock++; + sockfd = realloc(sockfd, nsock * sizeof(int)); + sockfd[nsock - 1] = fd; + } - if (!nsock) - die("could not bind any listen sockets!"); + freeaddrinfo(info); - int maxfd = -1; - fd_set fds; - FD_ZERO(&fds); - for (i=0; i maxfd) - maxfd = sockfd[i]; + if (!nsock) + die("could not bind any listen sockets!"); + + int maxfd = -1; + fd_set fds; + FD_ZERO(&fds); + for (i = 0; i < nsock; i++) { + if (sockfd[i] > maxfd) + maxfd = sockfd[i]; + } + + mdns_register(); + + // printf("Listening for connections."); + // shairport_startup_complete(); + + int acceptfd; + struct timeval tv; + while (1) { + tv.tv_sec = 300; + tv.tv_usec = 0; + + for (i = 0; i < nsock; i++) + FD_SET(sockfd[i], &fds); + + ret = select(maxfd + 1, &fds, 0, 0, &tv); + if (ret < 0) { + if (errno == EINTR) + continue; + break; } - mdns_register(); + cleanup_threads(); - // printf("Listening for connections."); - // shairport_startup_complete(); - - int acceptfd; - struct timeval tv; - while (1) { - tv.tv_sec = 300; - tv.tv_usec = 0; - - for (i=0; iremote); - - debug(1, "new RTSP connection."); - conn->fd = accept(acceptfd, (struct sockaddr *)&conn->remote, &slen); - if (conn->fd < 0) { - perror("failed to accept connection"); - free(conn); - } else { - pthread_t rtsp_conversation_thread; - ret = pthread_create(&rtsp_conversation_thread, NULL, rtsp_conversation_thread_func, conn); - if (ret) - die("Failed to create RTSP receiver thread!"); - - conn->thread = rtsp_conversation_thread; - conn->running = 1; - track_thread(conn); - } + acceptfd = -1; + for (i = 0; i < nsock; i++) { + if (FD_ISSET(sockfd[i], &fds)) { + acceptfd = sockfd[i]; + break; + } } - perror("select"); - die("fell out of the RTSP select loop"); + if (acceptfd < 0) // timeout + continue; + + rtsp_conn_info *conn = malloc(sizeof(rtsp_conn_info)); + memset(conn, 0, sizeof(rtsp_conn_info)); + socklen_t slen = sizeof(conn->remote); + + debug(1, "new RTSP connection."); + conn->fd = accept(acceptfd, (struct sockaddr *)&conn->remote, &slen); + if (conn->fd < 0) { + perror("failed to accept connection"); + free(conn); + } else { + pthread_t rtsp_conversation_thread; + ret = pthread_create(&rtsp_conversation_thread, NULL, rtsp_conversation_thread_func, conn); + if (ret) + die("Failed to create RTSP receiver thread!"); + + conn->thread = rtsp_conversation_thread; + conn->running = 1; + track_thread(conn); + } + } + perror("select"); + die("fell out of the RTSP select loop"); } diff --git a/rtsp.h b/rtsp.h index 74fdd52e..672eefee 100644 --- a/rtsp.h +++ b/rtsp.h @@ -11,6 +11,6 @@ void rtsp_request_shutdown_stream(void); // e.g. it it's malloced, to free it, etc. // nothing is done automatically -int send_ssnc_metadata(uint32_t code,char *data,uint32_t length, int block); +int send_ssnc_metadata(uint32_t code, char *data, uint32_t length, int block); #endif // _RTSP_H diff --git a/shairport.c b/shairport.c index cf8ce069..5ab2638c 100644 --- a/shairport.c +++ b/shairport.c @@ -60,518 +60,536 @@ #include #include - static int shutting_down = 0; -static char* appName = NULL; +static char *appName = NULL; void shairport_shutdown() { - if (shutting_down) - return; - shutting_down = 1; - mdns_unregister(); - rtsp_shutdown_stream(); - if (config.output) - config.output->deinit(); + if (shutting_down) + return; + shutting_down = 1; + mdns_unregister(); + rtsp_shutdown_stream(); + if (config.output) + config.output->deinit(); } -static void sig_ignore(int foo, siginfo_t *bar, void *baz) { -} +static void sig_ignore(int foo, siginfo_t *bar, void *baz) {} static void sig_shutdown(int foo, siginfo_t *bar, void *baz) { - debug(1, "shutdown requested..."); - shairport_shutdown(); - daemon_log(LOG_NOTICE, "exit..."); - daemon_retval_send(255); - daemon_pid_file_remove(); - exit(0); + debug(1, "shutdown requested..."); + shairport_shutdown(); + daemon_log(LOG_NOTICE, "exit..."); + daemon_retval_send(255); + daemon_pid_file_remove(); + exit(0); } static void sig_child(int foo, siginfo_t *bar, void *baz) { - pid_t pid; - while ((pid = waitpid((pid_t)-1, 0, WNOHANG)) > 0) { - if (pid == mdns_pid && !shutting_down) { - die("MDNS child process died unexpectedly!"); - } - } + pid_t pid; + while ((pid = waitpid((pid_t)-1, 0, WNOHANG)) > 0) { + if (pid == mdns_pid && !shutting_down) { + die("MDNS child process died unexpectedly!"); + } + } } static void sig_disconnect_audio_output(int foo, siginfo_t *bar, void *baz) { - debug(1,"disconnect audio output requested."); - set_requested_connection_state_to_output(0); + debug(1, "disconnect audio output requested."); + set_requested_connection_state_to_output(0); } static void sig_connect_audio_output(int foo, siginfo_t *bar, void *baz) { - debug(1,"connect audio output requested."); - set_requested_connection_state_to_output(1); + debug(1, "connect audio output requested."); + set_requested_connection_state_to_output(1); } void print_version(void) { char version_string[200]; - strcpy(version_string,PACKAGE_VERSION); + strcpy(version_string, PACKAGE_VERSION); #ifdef HAVE_LIBPOLARSSL - strcat(version_string,"-polarssl"); + strcat(version_string, "-polarssl"); #endif #ifdef HAVE_LIBSSL - strcat(version_string,"-openssl"); + strcat(version_string, "-openssl"); #endif #ifdef CONFIG_TINYSVCMDNS - strcat(version_string,"-tinysvcmdns"); + strcat(version_string, "-tinysvcmdns"); #endif #ifdef CONFIG_AVAHI - strcat(version_string,"-Avahi"); + strcat(version_string, "-Avahi"); #endif #ifdef CONFIG_DNS_SD - strcat(version_string,"-dns_sd"); + strcat(version_string, "-dns_sd"); #endif #ifdef CONFIG_ALSA - strcat(version_string,"-ALSA"); + strcat(version_string, "-ALSA"); #endif #ifdef CONFIG_SNDIO - strcat(version_string,"-sndio"); + strcat(version_string, "-sndio"); #endif #ifdef CONFIG_AO - strcat(version_string,"-ao"); + strcat(version_string, "-ao"); #endif #ifdef CONFIG_PULSE - strcat(version_string,"-pulse"); + strcat(version_string, "-pulse"); #endif #ifdef HAVE_LIBSOXR - strcat(version_string,"-soxr"); + strcat(version_string, "-soxr"); #endif #ifdef CONFIG_METADATA - strcat(version_string,"-metadata"); + strcat(version_string, "-metadata"); #endif #ifdef SUPPORT_CONFIG_FILES - strcat(version_string,"-configfile"); + strcat(version_string, "-configfile"); #endif - printf("%s\n",version_string); + printf("%s\n", version_string); } void usage(char *progname) { - 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(" -d, --daemon daemonise.\n"); - printf(" -V, --version show version information\n"); - printf(" -k, --kill kill the existing shairport daemon.\n"); - printf(" -D, --disconnectFromOutput disconnect immediately from the output device.\n"); - printf(" -R, --reconnectToOutput reconnect to the output device.\n"); - printf(" -c, --configfile=FILE read configuration settings from FILE. Default is /etc/shairport-sync.conf.\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(" -d, --daemon daemonise.\n"); + printf(" -V, --version show version information\n"); + printf(" -k, --kill kill the existing shairport daemon.\n"); + printf(" -D, --disconnectFromOutput disconnect immediately from the output device.\n"); + printf(" -R, --reconnectToOutput reconnect to the output device.\n"); + printf(" -c, --configfile=FILE read configuration settings from FILE. Default is " + "/etc/shairport-sync.conf.\n"); #ifdef COMMAND_LINE_ARGUMENT_SUPPORT - printf(" -v, --verbose -v print debug information; -vv more; -vvv lots\n"); - printf(" -p, --port=PORT set RTSP listening port\n"); - printf(" -a, --name=NAME set advertised name\n"); - printf(" -A, --AirPlayLatency=FRAMES set the latency for audio sent from an AirPlay device.\n"); - printf(" The default value is %u frames.\n", config.AirPlayLatency); - printf(" -i, --iTunesLatency=FRAMES set the latency for audio sent from iTunes 10 or later.\n"); - printf(" The default value is %u frames.\n", config.iTunesLatency); - printf(" -L, --latency=FRAMES set the latency for audio sent from an unknown device\n"); - printf(" or from an old version of iTunes. Default is %d frames.\n",config.latency); - printf(" --forkedDaapdLatency=FRAMES set the latency for audio sent from forked-daapd.\n"); - printf(" -S, --stuffing=MODE set how to adjust current latency to match desired latency \n"); - printf(" \"basic\" (default) inserts or deletes audio frames from packet frames with low processor overhead.\n"); - printf(" \"soxr\" uses libsoxr to minimally resample packet frames -- moderate processor overhead.\n"); - printf(" \"soxr\" option 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, e.g. /usr/bin/logger.\n"); - printf(" Executable scripts work, but must have #!/bin/sh (or whatever) in the headline.\n"); - printf(" -w, --wait-cmd wait until the -B or -E programs finish before continuing\n"); - printf(" -o, --output=BACKEND select audio output method\n"); - printf(" -m, --mdns=BACKEND force the use of BACKEND to advertize the service\n"); - printf(" if no mdns provider is specified,\n"); - printf(" shairport tries them all until one works.\n"); - printf(" -r, --resync=THRESHOLD 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(" --statistics print some interesting statistics -- output to the logfile if running as a daemon.\n"); - printf(" --tolerance=TOLERANCE allow a synchronization error of TOLERANCE frames (default 88) before trying to correct it.\n"); - printf(" --password=PASSWORD require PASSWORD to connect. Default is not to require a password.\n"); + printf(" -v, --verbose -v print debug information; -vv more; -vvv lots\n"); + printf(" -p, --port=PORT set RTSP listening port\n"); + printf(" -a, --name=NAME set advertised name\n"); + printf( + " -A, --AirPlayLatency=FRAMES set the latency for audio sent from an AirPlay device.\n"); + printf(" The default value is %u frames.\n", config.AirPlayLatency); + printf( + " -i, --iTunesLatency=FRAMES set the latency for audio sent from iTunes 10 or later.\n"); + printf(" The default value is %u frames.\n", config.iTunesLatency); + printf(" -L, --latency=FRAMES set the latency for audio sent from an unknown device\n"); + printf(" or from an old version of iTunes. Default is %d frames.\n", + config.latency); + printf(" --forkedDaapdLatency=FRAMES set the latency for audio sent from forked-daapd.\n"); + printf(" -S, --stuffing=MODE set how to adjust current latency to match desired latency \n"); + printf(" \"basic\" (default) inserts or deletes audio frames from " + "packet frames with low processor overhead.\n"); + printf(" \"soxr\" uses libsoxr to minimally resample packet frames -- " + "moderate processor overhead.\n"); + printf( + " \"soxr\" option 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, " + "e.g. /usr/bin/logger.\n"); + printf(" Executable scripts work, but must have #!/bin/sh (or " + "whatever) in the headline.\n"); + printf(" -w, --wait-cmd wait until the -B or -E programs finish before continuing\n"); + printf(" -o, --output=BACKEND select audio output method\n"); + printf(" -m, --mdns=BACKEND force the use of BACKEND to advertize the service\n"); + printf(" if no mdns provider is specified,\n"); + printf(" shairport tries them all until one works.\n"); + printf(" -r, --resync=THRESHOLD 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(" --statistics print some interesting statistics -- output to the logfile " + "if running as a daemon.\n"); + printf(" --tolerance=TOLERANCE allow a synchronization error of TOLERANCE frames (default " + "88) before trying to correct it.\n"); + printf(" --password=PASSWORD require PASSWORD to connect. Default is not to require a " + "password.\n"); #ifdef CONFIG_METADATA - printf(" --metadata-pipename=PIPE send metadata to PIPE, e.g. --metadata-pipename=/tmp/shairport-sync-metadata.\n"); - printf(" --get-coverart send cover art through the metadata pipe.\n"); + printf(" --metadata-pipename=PIPE send metadata to PIPE, e.g. " + "--metadata-pipename=/tmp/shairport-sync-metadata.\n"); + printf(" --get-coverart send cover art through the metadata pipe.\n"); #endif #endif #ifdef SUPPORT_CONFIG_FILES - printf("\nGeneral options can be configured in /etc/%s.conf.\n",appName); + printf("\nGeneral options can be configured in /etc/%s.conf.\n", appName); #endif - printf("\n"); - mdns_ls_backends(); - printf("\n"); - audio_ls_outputs(); + printf("\n"); + mdns_ls_backends(); + printf("\n"); + audio_ls_outputs(); } int parse_options(int argc, char **argv) { - char *stuffing = NULL; /* used for picking up the stuffing option */ - - - + char *stuffing = NULL; /* used for picking up the stuffing option */ - signed char c; /* used for argument parsing */ - int i = 0; /* used for tracking options */ - poptContext optCon; /* context for parsing command-line options */ + signed char c; /* used for argument parsing */ + int i = 0; /* used for tracking options */ + poptContext optCon; /* context for parsing command-line options */ struct poptOption optionsTable[] = { - { "verbose", 'v', POPT_ARG_NONE, NULL, 'v', NULL }, - { "disconnectFromOutput", 'D', POPT_ARG_NONE, NULL, 0, NULL }, - { "reconnectToOutput", 'R', POPT_ARG_NONE, NULL, 0, NULL }, - { "kill", 'k', POPT_ARG_NONE, NULL, 0, NULL }, - { "daemon", 'd', POPT_ARG_NONE, &config.daemonise, 0, NULL }, + {"verbose", 'v', POPT_ARG_NONE, NULL, 'v', NULL}, + {"disconnectFromOutput", 'D', POPT_ARG_NONE, NULL, 0, NULL}, + {"reconnectToOutput", 'R', POPT_ARG_NONE, NULL, 0, NULL}, + {"kill", 'k', POPT_ARG_NONE, NULL, 0, NULL}, + {"daemon", 'd', POPT_ARG_NONE, &config.daemonise, 0, NULL}, #ifdef SUPPORT_CONFIG_FILES - { "configfile", 'c', POPT_ARG_STRING, &config.configfile, 0, NULL }, + {"configfile", 'c', POPT_ARG_STRING, &config.configfile, 0, NULL}, #endif #ifdef COMMAND_LINE_ARGUMENT_SUPPORT - { "statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL}, - { "version", 'V', POPT_ARG_NONE, NULL, 0, NULL}, - { "port", 'p', POPT_ARG_INT, &config.port, 0, NULL } , - { "name", 'a', POPT_ARG_STRING, &config.apname, 0, NULL } , - { "output", 'o', POPT_ARG_STRING, &config.output_name, 0, NULL } , - { "on-start", 'B', POPT_ARG_STRING, &config.cmd_start, 0, NULL } , - { "on-stop", 'E', POPT_ARG_STRING, &config.cmd_stop, 0, NULL } , - { "wait-cmd", 'w', POPT_ARG_NONE, &config.cmd_blocking, 0, NULL } , - { "mdns", 'm', POPT_ARG_STRING, &config.mdns_name, 0, NULL } , - { "latency", 'L', POPT_ARG_INT, &config.userSuppliedLatency, 0, NULL } , - { "AirPlayLatency", 'A', POPT_ARG_INT, &config.AirPlayLatency, 0, NULL } , - { "iTunesLatency", 'i', POPT_ARG_INT, &config.iTunesLatency, 0, NULL } , - { "forkedDaapdLatency", 0, POPT_ARG_INT, &config.ForkedDaapdLatency, 0, NULL } , - { "stuffing", 'S', POPT_ARG_STRING, &stuffing, 'S', NULL } , - { "resync", 'r', POPT_ARG_INT, &config.resyncthreshold, 0, NULL } , - { "timeout", 't', POPT_ARG_INT, &config.timeout, 't', NULL } , - { "password", 0, POPT_ARG_STRING, &config.password, 0, NULL } , - { "tolerance", 0, POPT_ARG_INT, &config.tolerance, 0, NULL } , + {"statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL}, + {"version", 'V', POPT_ARG_NONE, NULL, 0, NULL}, + {"port", 'p', POPT_ARG_INT, &config.port, 0, NULL}, + {"name", 'a', POPT_ARG_STRING, &config.apname, 0, NULL}, + {"output", 'o', POPT_ARG_STRING, &config.output_name, 0, NULL}, + {"on-start", 'B', POPT_ARG_STRING, &config.cmd_start, 0, NULL}, + {"on-stop", 'E', POPT_ARG_STRING, &config.cmd_stop, 0, NULL}, + {"wait-cmd", 'w', POPT_ARG_NONE, &config.cmd_blocking, 0, NULL}, + {"mdns", 'm', POPT_ARG_STRING, &config.mdns_name, 0, NULL}, + {"latency", 'L', POPT_ARG_INT, &config.userSuppliedLatency, 0, NULL}, + {"AirPlayLatency", 'A', POPT_ARG_INT, &config.AirPlayLatency, 0, NULL}, + {"iTunesLatency", 'i', POPT_ARG_INT, &config.iTunesLatency, 0, NULL}, + {"forkedDaapdLatency", 0, POPT_ARG_INT, &config.ForkedDaapdLatency, 0, NULL}, + {"stuffing", 'S', POPT_ARG_STRING, &stuffing, 'S', NULL}, + {"resync", 'r', POPT_ARG_INT, &config.resyncthreshold, 0, NULL}, + {"timeout", 't', POPT_ARG_INT, &config.timeout, 't', NULL}, + {"password", 0, POPT_ARG_STRING, &config.password, 0, NULL}, + {"tolerance", 0, POPT_ARG_INT, &config.tolerance, 0, NULL}, #ifdef CONFIG_METADATA - { "metadata-pipename", 'M', POPT_ARG_STRING, &config.metadata_pipename, 'M', NULL } , - { "get-coverart", 'g', POPT_ARG_NONE, &config.get_coverart, 'g', NULL } , + {"metadata-pipename", 'M', POPT_ARG_STRING, &config.metadata_pipename, 'M', NULL}, + {"get-coverart", 'g', POPT_ARG_NONE, &config.get_coverart, 'g', NULL}, #endif - POPT_AUTOHELP + POPT_AUTOHELP #endif - { NULL, 0, 0, NULL, 0 } - }; + {NULL, 0, 0, NULL, 0}}; - - int optind=argc; + int optind = argc; int j; - for (j=0;j= 0) { switch (c) { - case 'v': - debuglev++; - break; - case 't': - if (config.timeout==0) { - config.dont_check_timeout=1; - config.allow_session_interruption=1; - } else { - config.dont_check_timeout=0; - config.allow_session_interruption=0; - } - break; + case 'v': + debuglev++; + break; + case 't': + if (config.timeout == 0) { + config.dont_check_timeout = 1; + config.allow_session_interruption = 1; + } else { + config.dont_check_timeout = 0; + config.allow_session_interruption = 0; + } + break; #ifdef CONFIG_METADATA - case 'M': - config.metadata_enabled=1; - break; - case 'g': - if (config.metadata_enabled==0) - die("If you want to get cover art, you must also select the --metadata-pipename option."); - break; + case 'M': + config.metadata_enabled = 1; + break; + case 'g': + if (config.metadata_enabled == 0) + die("If you want to get cover art, you must also select the --metadata-pipename option."); + break; #endif - case 'S': - if (strcmp(stuffing,"basic")==0) - config.packet_stuffing = ST_basic; - else if (strcmp(stuffing,"soxr")==0) + case 'S': + if (strcmp(stuffing, "basic") == 0) + config.packet_stuffing = ST_basic; + else if (strcmp(stuffing, "soxr") == 0) #ifdef HAVE_LIBSOXR - config.packet_stuffing = ST_soxr; + config.packet_stuffing = ST_soxr; #else - die("soxr option not available -- this version of shairport-sync was built without libsoxr support"); + die("soxr option not available -- this version of shairport-sync was built without libsoxr " + "support"); #endif - else - die("Illegal stuffing option \"%s\" -- must be \"basic\" or \"soxr\"",stuffing); - break; + else + die("Illegal stuffing option \"%s\" -- must be \"basic\" or \"soxr\"", stuffing); + break; } } if (c < -1) { - die("%s: %s",poptBadOption(optCon, POPT_BADOPTION_NOALIAS),poptStrerror(c)); + die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c)); } #ifdef SUPPORT_CONFIG_FILES - char configuration_file_path[4096]; - strcpy(configuration_file_path,"/etc/"); - strcat(configuration_file_path,appName); - strcat(configuration_file_path,".conf"); - debug(2,"Looking for file \"%s\"",configuration_file_path); - config_setting_t *setting; - const char *str; - int value; + char configuration_file_path[4096]; + strcpy(configuration_file_path, "/etc/"); + strcat(configuration_file_path, appName); + strcat(configuration_file_path, ".conf"); + debug(2, "Looking for file \"%s\"", configuration_file_path); + config_setting_t *setting; + const char *str; + int value; - config_init(&config_file_stuff); - char *cfp = configuration_file_path; - // use the configuration file path given in the -c option, if any - if (config.configfile) - cfp = config.configfile; - /* Read the file. If there is an error, report it and exit. */ - if(config_read_file(&config_file_stuff,cfp)) { - // make config.cfg point to it - config.cfg = &config_file_stuff; - /* Get the Service Name. */ - if(config_lookup_string(config.cfg, "general.name", &str)) - config.apname=(char *)str; + config_init(&config_file_stuff); + char *cfp = configuration_file_path; + // use the configuration file path given in the -c option, if any + if (config.configfile) + cfp = config.configfile; + /* Read the file. If there is an error, report it and exit. */ + if (config_read_file(&config_file_stuff, cfp)) { + // make config.cfg point to it + config.cfg = &config_file_stuff; + /* Get the Service Name. */ + if (config_lookup_string(config.cfg, "general.name", &str)) + config.apname = (char *)str; - /* Get the Daemonize setting. */ - if(config_lookup_string(config.cfg, "general.daemonize", &str)) { - if (strcasecmp(str,"no")==0) - config.daemonise=0; - else if (strcasecmp(str,"yes")==0) - config.daemonise=1; - else - die("Invalid daemonize option choice \"%s\". It should be \"yes\" or \"no\""); - } + /* Get the Daemonize setting. */ + if (config_lookup_string(config.cfg, "general.daemonize", &str)) { + if (strcasecmp(str, "no") == 0) + config.daemonise = 0; + else if (strcasecmp(str, "yes") == 0) + config.daemonise = 1; + else + die("Invalid daemonize option choice \"%s\". It should be \"yes\" or \"no\""); + } - /* Get the mdns_backend setting. */ - if(config_lookup_string(config.cfg, "general.mdns_backend", &str)) - config.mdns_name=(char *)str; + /* 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 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)) - die("Invalid port number \"%sd\". It should be between 0 and 65535, default is 5000",value); - else - config.port=value; - } + /* Get the port setting. */ + if (config_lookup_int(config.cfg, "general.port", &value)) { + if ((value < 0) || (value > 65535)) + die("Invalid port number \"%sd\". It should be between 0 and 65535, default is 5000", + value); + else + config.port = value; + } - /* Get the password setting. */ - if(config_lookup_string(config.cfg, "general.password", &str)) - config.password=(char *)str; + /* 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,"soxr")==0) - config.packet_stuffing=ST_soxr; - else - die("Invalid interpolation option choice \"%s\". It should be \"basic\" or \"soxr\""); - } + if (config_lookup_string(config.cfg, "general.interpolation", &str)) { + if (strcasecmp(str, "basic") == 0) + config.packet_stuffing = ST_basic; + else if (strcasecmp(str, "soxr") == 0) + config.packet_stuffing = ST_soxr; + else + die("Invalid interpolation option choice \"%s\". It should be \"basic\" or \"soxr\""); + } - /* Get the statistics setting. */ - if(config_lookup_string(config.cfg, "general.statistics", &str)) { - if (strcasecmp(str,"no")==0) - config.statistics_requested=0; - else if (strcasecmp(str,"yes")==0) - config.statistics_requested=1; - else - die("Invalid statistics option choice \"%s\". It should be \"yes\" or \"no\""); - } + /* Get the statistics setting. */ + if (config_lookup_string(config.cfg, "general.statistics", &str)) { + if (strcasecmp(str, "no") == 0) + config.statistics_requested = 0; + else if (strcasecmp(str, "yes") == 0) + config.statistics_requested = 1; + else + die("Invalid statistics option choice \"%s\". It should be \"yes\" or \"no\""); + } - /* Get the drift tolerance setting. */ - if(config_lookup_int(config.cfg, "general.drift", &value)) - config.tolerance=value; + /* Get the drift tolerance setting. */ + if (config_lookup_int(config.cfg, "general.drift", &value)) + config.tolerance = value; - /* Get the resync setting. */ - if(config_lookup_int(config.cfg, "general.resync_threshold", &value)) - config.resyncthreshold=value; + /* Get the resync setting. */ + if (config_lookup_int(config.cfg, "general.resync_threshold", &value)) + config.resyncthreshold = value; - /* Get the verbosity setting. */ - if(config_lookup_int(config.cfg, "general.log_verbosity", &value)) - 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 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\""); - } - - /* Get the default latency. */ - if(config_lookup_int(config.cfg, "latencies.default", &value)) - config.latency=value; + /* Get the verbosity setting. */ + if (config_lookup_int(config.cfg, "general.log_verbosity", &value)) + 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 itunes latency. */ - if(config_lookup_int(config.cfg, "latencies.itunes", &value)) - config.iTunesLatency=value; + /* 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\""); + } - /* Get the AirPlay latency. */ - if(config_lookup_int(config.cfg, "latencies.airplay", &value)) - config.AirPlayLatency=value; + /* Get the default latency. */ + if (config_lookup_int(config.cfg, "latencies.default", &value)) + config.latency = value; - /* Get the forkedDaapd latency. */ - if(config_lookup_int(config.cfg, "latencies.forkedDaapd", &value)) - config.ForkedDaapdLatency=value; + /* Get the itunes latency. */ + if (config_lookup_int(config.cfg, "latencies.itunes", &value)) + config.iTunesLatency = value; + + /* Get the AirPlay latency. */ + if (config_lookup_int(config.cfg, "latencies.airplay", &value)) + config.AirPlayLatency = value; + + /* Get the forkedDaapd latency. */ + if (config_lookup_int(config.cfg, "latencies.forkedDaapd", &value)) + config.ForkedDaapdLatency = value; #ifdef CONFIG_METADATA - /* Get the metadata setting. */ - if(config_lookup_string(config.cfg, "metadata.enabled", &str)) { - if (strcasecmp(str,"no")==0) - config.metadata_enabled=0; - else if (strcasecmp(str,"yes")==0) - config.metadata_enabled=1; - else - die("Invalid metadata enabled option choice \"%s\". It should be \"yes\" or \"no\""); - } - - if(config_lookup_string(config.cfg, "metadata.include_cover_art", &str)) { - if (strcasecmp(str,"no")==0) - config.get_coverart=0; - else if (strcasecmp(str,"yes")==0) - config.get_coverart=1; - else - die("Invalid metadata include_cover_art option choice \"%s\". It should be \"yes\" or \"no\""); - } - - if(config_lookup_string(config.cfg, "metadata.pipe_name", &str)) { - config.metadata_pipename=(char *)str; - } + /* Get the metadata setting. */ + if (config_lookup_string(config.cfg, "metadata.enabled", &str)) { + if (strcasecmp(str, "no") == 0) + config.metadata_enabled = 0; + else if (strcasecmp(str, "yes") == 0) + config.metadata_enabled = 1; + else + die("Invalid metadata enabled option choice \"%s\". It should be \"yes\" or \"no\""); + } + + if (config_lookup_string(config.cfg, "metadata.include_cover_art", &str)) { + if (strcasecmp(str, "no") == 0) + config.get_coverart = 0; + else if (strcasecmp(str, "yes") == 0) + config.get_coverart = 1; + else + die("Invalid metadata include_cover_art option choice \"%s\". It should be \"yes\" or " + "\"no\""); + } + + if (config_lookup_string(config.cfg, "metadata.pipe_name", &str)) { + config.metadata_pipename = (char *)str; + } #endif - if(config_lookup_string(config.cfg, "sessioncontrol.run_this_before_play_begins", &str)) { - config.cmd_start=(char *)str; - } - - if(config_lookup_string(config.cfg, "sessioncontrol.run_this_after_play_ends", &str)) { - config.cmd_stop=(char *)str; - } - - if(config_lookup_string(config.cfg, "sessioncontrol.wait_for_completion", &str)) { - if (strcasecmp(str,"no")==0) - config.cmd_blocking=0; - else if (strcasecmp(str,"yes")==0) - config.cmd_blocking=1; - else - die("Invalid session control wait_for_completion option choice \"%s\". It should be \"yes\" or \"no\""); - } - - if(config_lookup_string(config.cfg, "sessioncontrol.allow_session_interruption", &str)) { - config.dont_check_timeout=0; // this is for legacy -- only set by -t 0 - if (strcasecmp(str,"no")==0) - config.allow_session_interruption=0; - else if (strcasecmp(str,"yes")==0) - config.allow_session_interruption=1; - else - die("Invalid session control allow_interruption option choice \"%s\". It should be \"yes\" or \"no\""); - } - - if(config_lookup_int(config.cfg, "sessioncontrol.session_timeout", &value)) { - config.timeout=value; - config.dont_check_timeout=0; // this is for legacy -- only set by -t 0 - } - - } else { - if (config_error_type (&config_file_stuff)==CONFIG_ERR_FILE_IO) - debug(1,"Error reading configuration file \"%s\": \"%s\".",config_error_file(&config_file_stuff),config_error_text(&config_file_stuff)); - else { - die("Line %d of the configuration file \"%s\":\n%s", - config_error_line(&config_file_stuff),config_error_file(&config_file_stuff),config_error_text(&config_file_stuff)); - } + if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_play_begins", &str)) { + config.cmd_start = (char *)str; } - + + if (config_lookup_string(config.cfg, "sessioncontrol.run_this_after_play_ends", &str)) { + config.cmd_stop = (char *)str; + } + + if (config_lookup_string(config.cfg, "sessioncontrol.wait_for_completion", &str)) { + if (strcasecmp(str, "no") == 0) + config.cmd_blocking = 0; + else if (strcasecmp(str, "yes") == 0) + config.cmd_blocking = 1; + else + die("Invalid session control wait_for_completion option choice \"%s\". It should be " + "\"yes\" or \"no\""); + } + + if (config_lookup_string(config.cfg, "sessioncontrol.allow_session_interruption", &str)) { + config.dont_check_timeout = 0; // this is for legacy -- only set by -t 0 + if (strcasecmp(str, "no") == 0) + config.allow_session_interruption = 0; + else if (strcasecmp(str, "yes") == 0) + config.allow_session_interruption = 1; + else + die("Invalid session control allow_interruption option choice \"%s\". It should be \"yes\" " + "or \"no\""); + } + + if (config_lookup_int(config.cfg, "sessioncontrol.session_timeout", &value)) { + config.timeout = value; + config.dont_check_timeout = 0; // this is for legacy -- only set by -t 0 + } + + } else { + if (config_error_type(&config_file_stuff) == CONFIG_ERR_FILE_IO) + debug(1, "Error reading configuration file \"%s\": \"%s\".", + config_error_file(&config_file_stuff), config_error_text(&config_file_stuff)); + else { + die("Line %d of the configuration file \"%s\":\n%s", config_error_line(&config_file_stuff), + config_error_file(&config_file_stuff), config_error_text(&config_file_stuff)); + } + } + #endif /* Print out options */ - - debug(2,"statistics_requester status is %d.",config.statistics_requested); - debug(2,"daemon status is %d.",config.daemonise); - debug(2,"rtsp listening port is %d.",config.port); - debug(2,"Shairport Sync player name is \"%s\".",config.apname); - debug(2,"Audio Output name is \"%s\".",config.output_name); - debug(2,"on-start action is \"%s\".",config.cmd_start); - debug(2,"on-stop action is \"%s\".",config.cmd_stop); - debug(2,"wait-cmd status is %d.",config.cmd_blocking); - debug(2,"mdns backend \"%s\".",config.mdns_name); - debug(2,"userSuppliedLatency is %d.",config.userSuppliedLatency); - debug(2,"AirPlayLatency is %d.",config.AirPlayLatency); - debug(2,"iTunesLatency is %d.",config.iTunesLatency); - debug(2,"forkedDaapdLatency is %d.",config.ForkedDaapdLatency); - debug(2,"stuffing option is \"%d\".",config.packet_stuffing); - debug(2,"resync time is %d.",config.resyncthreshold); - debug(2,"allow a session to be interrupted: %d.",config.allow_session_interruption); - debug(2,"busy timeout time is %d.",config.timeout); - debug(2,"tolerance is %d frames.",config.tolerance); - debug(2,"password is \"%s\".",config.password); - debug(2,"ignore_volume_control is %d.",config.ignore_volume_control); - debug(2,"audio backend desired buffer length is %d.",config.audio_backend_buffer_desired_length); - debug(2,"audio backend latency offset is %d.",config.audio_backend_latency_offset); + + debug(2, "statistics_requester status is %d.", config.statistics_requested); + debug(2, "daemon status is %d.", config.daemonise); + debug(2, "rtsp listening port is %d.", config.port); + debug(2, "Shairport Sync player name is \"%s\".", config.apname); + debug(2, "Audio Output name is \"%s\".", config.output_name); + debug(2, "on-start action is \"%s\".", config.cmd_start); + debug(2, "on-stop action is \"%s\".", config.cmd_stop); + debug(2, "wait-cmd status is %d.", config.cmd_blocking); + debug(2, "mdns backend \"%s\".", config.mdns_name); + debug(2, "userSuppliedLatency is %d.", config.userSuppliedLatency); + debug(2, "AirPlayLatency is %d.", config.AirPlayLatency); + debug(2, "iTunesLatency is %d.", config.iTunesLatency); + debug(2, "forkedDaapdLatency is %d.", config.ForkedDaapdLatency); + debug(2, "stuffing option is \"%d\".", config.packet_stuffing); + debug(2, "resync time is %d.", config.resyncthreshold); + debug(2, "allow a session to be interrupted: %d.", config.allow_session_interruption); + debug(2, "busy timeout time is %d.", config.timeout); + debug(2, "tolerance is %d frames.", config.tolerance); + debug(2, "password is \"%s\".", config.password); + debug(2, "ignore_volume_control is %d.", config.ignore_volume_control); + debug(2, "audio backend desired buffer length is %d.", + config.audio_backend_buffer_desired_length); + debug(2, "audio backend latency offset is %d.", config.audio_backend_latency_offset); #ifdef CONFIG_METADATA - debug(2,"metdata enabled is %d.",config.metadata_enabled); - debug(2,"metadata pipename is \"%s\".",config.metadata_pipename); - debug(2,"get-coverart is %d.",config.get_coverart); + debug(2, "metdata enabled is %d.", config.metadata_enabled); + debug(2, "metadata pipename is \"%s\".", config.metadata_pipename); + debug(2, "get-coverart is %d.", config.get_coverart); #endif - return optind+1; + return optind + 1; } void signal_setup(void) { - // mask off all signals before creating threads. - // this way we control which thread gets which signals. - // for now, we don't care which thread gets the following. - sigset_t set; - sigfillset(&set); - sigdelset(&set, SIGINT); - sigdelset(&set, SIGTERM); - sigdelset(&set, SIGHUP); - sigdelset(&set, SIGSTOP); - sigdelset(&set, SIGCHLD); - sigdelset(&set, SIGUSR2); - pthread_sigmask(SIG_BLOCK, &set, NULL); + // mask off all signals before creating threads. + // this way we control which thread gets which signals. + // for now, we don't care which thread gets the following. + sigset_t set; + sigfillset(&set); + sigdelset(&set, SIGINT); + sigdelset(&set, SIGTERM); + sigdelset(&set, SIGHUP); + sigdelset(&set, SIGSTOP); + sigdelset(&set, SIGCHLD); + sigdelset(&set, SIGUSR2); + pthread_sigmask(SIG_BLOCK, &set, NULL); - // setting this to SIG_IGN would prevent signalling any threads. - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_flags = SA_SIGINFO; - sa.sa_sigaction = &sig_ignore; - sigaction(SIGUSR1, &sa, NULL); + // setting this to SIG_IGN would prevent signalling any threads. + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = &sig_ignore; + sigaction(SIGUSR1, &sa, NULL); - sa.sa_flags = SA_SIGINFO | SA_RESTART; - sa.sa_sigaction = &sig_shutdown; - sigaction(SIGINT, &sa, NULL); - sigaction(SIGTERM, &sa, NULL); + sa.sa_flags = SA_SIGINFO | SA_RESTART; + sa.sa_sigaction = &sig_shutdown; + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); - sa.sa_sigaction = &sig_disconnect_audio_output; - sigaction(SIGUSR2, &sa, NULL); + sa.sa_sigaction = &sig_disconnect_audio_output; + sigaction(SIGUSR2, &sa, NULL); - sa.sa_sigaction = &sig_connect_audio_output; - sigaction(SIGHUP, &sa, NULL); + sa.sa_sigaction = &sig_connect_audio_output; + sigaction(SIGHUP, &sa, NULL); - sa.sa_sigaction = &sig_child; - sigaction(SIGCHLD, &sa, NULL); + sa.sa_sigaction = &sig_child; + sigaction(SIGCHLD, &sa, NULL); } // forked daemon lets the spawner know it's up and running OK // should be called only once! void shairport_startup_complete(void) { - if (config.daemonise) { -// daemon_ready(); - } + if (config.daemonise) { + // daemon_ready(); + } } #ifdef USE_CUSTOM_PID_DIR const char *pid_file_proc(void) { #ifdef HAVE_ASPRINTF - static char *fn = NULL; - free(fn); - asprintf(&fn, "%s/%s.pid", PIDDIR, daemon_pid_file_ident ? daemon_pid_file_ident : "unknown"); + static char *fn = NULL; + free(fn); + asprintf(&fn, "%s/%s.pid", PIDDIR, daemon_pid_file_ident ? daemon_pid_file_ident : "unknown"); #else - static char fn[8192]; - snprintf(fn, sizeof(fn), "%s/%s.pid", PIDDIR, daemon_pid_file_ident ? daemon_pid_file_ident : "unknown"); + static char fn[8192]; + snprintf(fn, sizeof(fn), "%s/%s.pid", PIDDIR, + daemon_pid_file_ident ? daemon_pid_file_ident : "unknown"); #endif - return fn; + return fn; } #endif @@ -579,222 +597,234 @@ void exit_function() { #ifdef SUPPORT_CONFIG_FILES if (config.cfg) config_destroy(config.cfg); -#endif +#endif } int main(int argc, char **argv) { - daemon_set_verbosity(LOG_DEBUG); - memset(&config, 0, sizeof(config)); // also clears all strings, BTW - atexit(exit_function); + daemon_set_verbosity(LOG_DEBUG); + memset(&config, 0, sizeof(config)); // also clears all strings, BTW + atexit(exit_function); - // set defaults - config.statistics_requested - 0; // don't print stats in the log - config.latency = 88200; // AirPlay. Is also reset in rtsp.c when play is about to start - config.userSuppliedLatency = 0; // zero means none supplied - config.iTunesLatency = 99400; // this seems to work pretty well for iTunes from Version 10 (?) upwards-- two left-ear headphones, one from the iMac jack, one from an NSLU2 running a cheap "3D Sound" USB Soundcard - config.AirPlayLatency = 88200; // this seems to work pretty well for AirPlay -- Syncs sound and vision on AppleTV, but also used for iPhone/iPod/iPad sources - config.ForkedDaapdLatency = 99400; // Seems to be right - config.resyncthreshold = 441*5; // this number of frames is 50 ms - config.timeout = 120; // this number of seconds to wait for [more] audio before switching to idle. - config.tolerance = 88; // this number of frames of error before attempting to correct it. - config.buffer_start_fill = 220; - config.port = 5000; - config.packet_stuffing = ST_basic; // simple interpolation or deletion - char hostname[100]; - gethostname(hostname, 100); - config.apname = malloc(20 + 100); - snprintf(config.apname, 20 + 100, "Shairport Sync on %s", hostname); - set_requested_connection_state_to_output(1); // we expect to be able to connect to the output device - config.audio_backend_buffer_desired_length = 6615; // 0.15 seconds. - - // this is a bit weird, but apparently necessary - char* basec = strdup(argv[0]); - char* bname = basename(basec); - appName = strdup(bname); - free(basec); - - /* Check if we are called with -V or --version parameter */ - if (argc >= 2 && ((strcmp(argv[1], "-V")==0) || (strcmp(argv[1], "--version")==0))) { - print_version(); - exit(1); - } + // set defaults + config.statistics_requested - 0; // don't print stats in the log + config.latency = 88200; // AirPlay. Is also reset in rtsp.c when play is about to start + config.userSuppliedLatency = 0; // zero means none supplied + config.iTunesLatency = 99400; // this seems to work pretty well for iTunes from Version 10 (?) + // upwards-- two left-ear headphones, one from the iMac jack, one + // from an NSLU2 running a cheap "3D Sound" USB Soundcard + config.AirPlayLatency = 88200; // this seems to work pretty well for AirPlay -- Syncs sound and + // vision on AppleTV, but also used for iPhone/iPod/iPad sources + config.ForkedDaapdLatency = 99400; // Seems to be right + config.resyncthreshold = 441 * 5; // this number of frames is 50 ms + config.timeout = 120; // this number of seconds to wait for [more] audio before switching to idle. + config.tolerance = 88; // this number of frames of error before attempting to correct it. + config.buffer_start_fill = 220; + config.port = 5000; + config.packet_stuffing = ST_basic; // simple interpolation or deletion + char hostname[100]; + gethostname(hostname, 100); + config.apname = malloc(20 + 100); + snprintf(config.apname, 20 + 100, "Shairport Sync on %s", hostname); + set_requested_connection_state_to_output( + 1); // we expect to be able to connect to the output device + config.audio_backend_buffer_desired_length = 6615; // 0.15 seconds. - /* Check if we are called with -h or --help parameter */ - if (argc >= 2 && ((strcmp(argv[1], "-h")==0) || (strcmp(argv[1], "--help")==0))) { - usage(argv[0]); - exit(1); - } + // this is a bit weird, but apparently necessary + char *basec = strdup(argv[0]); + char *bname = basename(basec); + appName = strdup(bname); + free(basec); - pid_t pid; + /* Check if we are called with -V or --version parameter */ + if (argc >= 2 && ((strcmp(argv[1], "-V") == 0) || (strcmp(argv[1], "--version") == 0))) { + print_version(); + exit(1); + } - /* Reset signal handlers */ - if (daemon_reset_sigs(-1) < 0) { - daemon_log(LOG_ERR, "Failed to reset all signal handlers: %s", strerror(errno)); - return 1; - } + /* Check if we are called with -h or --help parameter */ + if (argc >= 2 && ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0))) { + usage(argv[0]); + exit(1); + } + + pid_t pid; + + /* Reset signal handlers */ + if (daemon_reset_sigs(-1) < 0) { + daemon_log(LOG_ERR, "Failed to reset all signal handlers: %s", strerror(errno)); + return 1; + } + + /* Unblock signals */ + if (daemon_unblock_sigs(-1) < 0) { + daemon_log(LOG_ERR, "Failed to unblock all signals: %s", strerror(errno)); + return 1; + } - /* Unblock signals */ - if (daemon_unblock_sigs(-1) < 0) { - daemon_log(LOG_ERR, "Failed to unblock all signals: %s", strerror(errno)); - return 1; - } - - #if USE_CUSTOM_LOCAL_STATE_DIR - debug(1,"Locating localstatedir at \"%s\"",LOCALSTATEDIR); - /* Point to a function to help locate where the PID file will go */ - daemon_pid_file_proc = pid_file_proc; + debug(1, "Locating localstatedir at \"%s\"", LOCALSTATEDIR); + /* Point to a function to help locate where the PID file will go */ + daemon_pid_file_proc = pid_file_proc; #endif - /* Set indentification string for the daemon for both syslog and PID file */ - daemon_pid_file_ident = daemon_log_ident = daemon_ident_from_argv0(argv[0]); - - /* Check if we are called with -D or --disconnectFromOutput parameter */ - if (argc >= 2 && ((strcmp(argv[1], "-D")==0) || (strcmp(argv[1], "--disconnectFromOutput")==0))) { - if ((pid = daemon_pid_file_is_running()) >= 0) { - if (kill(pid,SIGUSR2)!=0) { // try to send the signal - daemon_log(LOG_WARNING, "Failed trying to send disconnectFromOutput command to daemon pid: %d: %s",pid, strerror(errno)); - } - } else { - daemon_log(LOG_WARNING, "Can't send a disconnectFromOutput request -- Failed to find daemon: %s", strerror(errno)); + /* Set indentification string for the daemon for both syslog and PID file */ + daemon_pid_file_ident = daemon_log_ident = daemon_ident_from_argv0(argv[0]); + + /* Check if we are called with -D or --disconnectFromOutput parameter */ + if (argc >= 2 && + ((strcmp(argv[1], "-D") == 0) || (strcmp(argv[1], "--disconnectFromOutput") == 0))) { + if ((pid = daemon_pid_file_is_running()) >= 0) { + if (kill(pid, SIGUSR2) != 0) { // try to send the signal + daemon_log(LOG_WARNING, + "Failed trying to send disconnectFromOutput command to daemon pid: %d: %s", pid, + strerror(errno)); } - exit(1); + } else { + daemon_log(LOG_WARNING, + "Can't send a disconnectFromOutput request -- Failed to find daemon: %s", + strerror(errno)); } - - /* Check if we are called with -R or --reconnectToOutput parameter */ - if (argc >= 2 && ((strcmp(argv[1], "-R")==0) || (strcmp(argv[1], "--reconnectToOutput")==0))) { - if ((pid = daemon_pid_file_is_running()) >= 0) { - if (kill(pid,SIGHUP)!=0) { // try to send the signal - daemon_log(LOG_WARNING, "Failed trying to send reconnectToOutput command to daemon pid: %d: %s",pid, strerror(errno)); - } - } else { - daemon_log(LOG_WARNING, "Can't send a reconnectToOutput request -- Failed to find daemon: %s", strerror(errno)); + exit(1); + } + + /* Check if we are called with -R or --reconnectToOutput parameter */ + if (argc >= 2 && + ((strcmp(argv[1], "-R") == 0) || (strcmp(argv[1], "--reconnectToOutput") == 0))) { + if ((pid = daemon_pid_file_is_running()) >= 0) { + if (kill(pid, SIGHUP) != 0) { // try to send the signal + daemon_log(LOG_WARNING, + "Failed trying to send reconnectToOutput command to daemon pid: %d: %s", pid, + strerror(errno)); } - exit(1); + } else { + daemon_log(LOG_WARNING, "Can't send a reconnectToOutput request -- Failed to find daemon: %s", + strerror(errno)); } - - /* Check if we are called with -k or --kill parameter */ - if (argc >= 2 && ((strcmp(argv[1], "-k")==0) || (strcmp(argv[1], "--kill")==0))) { + exit(1); + } + + /* Check if we are called with -k or --kill parameter */ + if (argc >= 2 && ((strcmp(argv[1], "-k") == 0) || (strcmp(argv[1], "--kill") == 0))) { + int ret; + + /* Kill daemon with SIGTERM */ + /* Check if the new function daemon_pid_file_kill_wait() is available, if it is, use it. */ + if ((ret = daemon_pid_file_kill_wait(SIGTERM, 5)) < 0) + daemon_log(LOG_WARNING, "Failed to kill daemon: %s", strerror(errno)); + else + daemon_pid_file_remove(); + return ret < 0 ? 1 : 0; + } + + /* Check that the daemon is not running twice at the same time */ + if ((pid = daemon_pid_file_is_running()) >= 0) { + daemon_log(LOG_ERR, "Daemon already running on PID file %u", pid); + return 1; + } + + // parse arguments into config + int audio_arg = parse_options(argc, argv); + + // mDNS supports maximum of 63-character names (we append 13). + if (strlen(config.apname) > 50) + die("Supplied name too long (max 50 characters)"); + + /* here, daemonise with libdaemon */ + + if (config.daemonise) { + /* Prepare for return value passing from the initialization procedure of the daemon process */ + if (daemon_retval_init() < 0) { + daemon_log(LOG_ERR, "Failed to create pipe."); + return 1; + } + + /* Do the fork */ + if ((pid = daemon_fork()) < 0) { + + /* Exit on error */ + daemon_retval_done(); + return 1; + + } else if (pid) { /* The parent */ int ret; - /* Kill daemon with SIGTERM */ - /* Check if the new function daemon_pid_file_kill_wait() is available, if it is, use it. */ - if ((ret = daemon_pid_file_kill_wait(SIGTERM, 5)) < 0) - daemon_log(LOG_WARNING, "Failed to kill daemon: %s", strerror(errno)); - else - daemon_pid_file_remove(); - return ret < 0 ? 1 : 0; - } - - /* Check that the daemon is not running twice at the same time */ - if ((pid = daemon_pid_file_is_running()) >= 0) { - daemon_log(LOG_ERR, "Daemon already running on PID file %u", pid); - return 1; - } - - // parse arguments into config - int audio_arg = parse_options(argc, argv); - - // mDNS supports maximum of 63-character names (we append 13). - if (strlen(config.apname) > 50) - die("Supplied name too long (max 50 characters)"); - - /* here, daemonise with libdaemon */ - - if (config.daemonise) { - /* Prepare for return value passing from the initialization procedure of the daemon process */ - if (daemon_retval_init() < 0) { - daemon_log(LOG_ERR, "Failed to create pipe."); - return 1; + /* Wait for 20 seconds for the return value passed from the daemon process */ + if ((ret = daemon_retval_wait(20)) < 0) { + daemon_log(LOG_ERR, "Could not receive return value from daemon process: %s", + strerror(errno)); + return 255; } - /* Do the fork */ - if ((pid = daemon_fork()) < 0) { + if (ret != 0) + daemon_log(ret != 0 ? LOG_ERR : LOG_INFO, "Daemon returned %i as return value.", ret); + return ret; - /* Exit on error */ - daemon_retval_done(); - return 1; + } else { /* The daemon */ - } else if (pid) { /* The parent */ - int ret; + /* Close FDs */ + if (daemon_close_all(-1) < 0) { + daemon_log(LOG_ERR, "Failed to close all file descriptors: %s", strerror(errno)); - /* Wait for 20 seconds for the return value passed from the daemon process */ - if ((ret = daemon_retval_wait(20)) < 0) { - daemon_log(LOG_ERR, "Could not receive return value from daemon process: %s", strerror(errno)); - return 255; - } - - if (ret != 0) - daemon_log(ret != 0 ? LOG_ERR : LOG_INFO, "Daemon returned %i as return value.", ret); - return ret; - - } else { /* The daemon */ - - /* Close FDs */ - if (daemon_close_all(-1) < 0) { - daemon_log(LOG_ERR, "Failed to close all file descriptors: %s", strerror(errno)); - - /* Send the error condition to the parent process */ - daemon_retval_send(1); - goto finish; - } - - /* Create the PID file */ - if (daemon_pid_file_create() < 0) { - daemon_log(LOG_ERR, "Could not create PID file (%s).", strerror(errno)); - daemon_retval_send(2); - goto finish; - } - - /* Send OK to parent process */ - daemon_retval_send(0); + /* Send the error condition to the parent process */ + daemon_retval_send(1); + goto finish; } - /* end libdaemon stuff */ + + /* Create the PID file */ + if (daemon_pid_file_create() < 0) { + daemon_log(LOG_ERR, "Could not create PID file (%s).", strerror(errno)); + daemon_retval_send(2); + goto finish; + } + + /* Send OK to parent process */ + daemon_retval_send(0); } + /* end libdaemon stuff */ + } - signal_setup(); - - // make sure the program can create files that group and world can read - umask(S_IWGRP | S_IWOTH); + signal_setup(); - config.output = audio_get_output(config.output_name); - if (!config.output) { - audio_ls_outputs(); - die("Invalid audio output specified!"); - } - config.output->init(argc-audio_arg, argv+audio_arg); + // make sure the program can create files that group and world can read + umask(S_IWGRP | S_IWOTH); - daemon_log(LOG_NOTICE, "startup"); + config.output = audio_get_output(config.output_name); + if (!config.output) { + audio_ls_outputs(); + die("Invalid audio output specified!"); + } + config.output->init(argc - audio_arg, argv + audio_arg); - uint8_t ap_md5[16]; + daemon_log(LOG_NOTICE, "startup"); + + uint8_t ap_md5[16]; #ifdef HAVE_LIBSSL - MD5_CTX ctx; - MD5_Init(&ctx); - MD5_Update(&ctx, config.apname, strlen(config.apname)); - MD5_Final(ap_md5, &ctx); + MD5_CTX ctx; + MD5_Init(&ctx); + MD5_Update(&ctx, config.apname, strlen(config.apname)); + MD5_Final(ap_md5, &ctx); #endif #ifdef HAVE_LIBPOLARSSL - md5_context tctx; - md5_starts(&tctx); - md5_update(&tctx, (unsigned char *)config.apname, strlen(config.apname)); - md5_finish(&tctx, ap_md5); + md5_context tctx; + md5_starts(&tctx); + md5_update(&tctx, (unsigned char *)config.apname, strlen(config.apname)); + md5_finish(&tctx, ap_md5); #endif - memcpy(config.hw_addr, ap_md5, sizeof(config.hw_addr)); + memcpy(config.hw_addr, ap_md5, sizeof(config.hw_addr)); #ifdef CONFIG_METADATA - metadata_init() ; // create the metadata pipe if necessary + metadata_init(); // create the metadata pipe if necessary #endif - rtsp_listen_loop(); + rtsp_listen_loop(); - // should not reach this... - shairport_shutdown(); + // should not reach this... + shairport_shutdown(); finish: - daemon_log(LOG_NOTICE, "Unexpected exit..."); - daemon_retval_send(255); - daemon_pid_file_remove(); - return 1; - + daemon_log(LOG_NOTICE, "Unexpected exit..."); + daemon_retval_send(255); + daemon_pid_file_remove(); + return 1; } diff --git a/tinysvcmdns.c b/tinysvcmdns.c index 8c03b38e..bdb6c090 100644 --- a/tinysvcmdns.c +++ b/tinysvcmdns.c @@ -34,18 +34,16 @@ #include "common.h" #define DEBUG_PRINTF(...) debug(1, __VA_ARGS__) -#define log_message(level, ...) \ - do { \ - switch(level) \ - { \ - case LOG_ERR: \ - warn(__VA_ARGS__); \ - break; \ - default: \ - debug(1, __VA_ARGS__); \ - } \ - } while (0) - +#define log_message(level, ...) \ + do { \ + switch (level) { \ + case LOG_ERR: \ + warn(__VA_ARGS__); \ + break; \ + default: \ + debug(1, __VA_ARGS__); \ + } \ + } while (0) //******************************************************// // mdns.c // @@ -63,311 +61,320 @@ #include #endif -// See RFC 6762 Section 10 for an account of two TTLs -- 120 seconds for rrs with a host name as the record's name +// See RFC 6762 Section 10 for an account of two TTLs -- 120 seconds for rrs with a host name as the +// record's name // or a host name in the record's rdata // 75 minutes for everything else. // https://tools.ietf.org/html/rfc6762 -#define DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME 120 -#define DEFAULT_TTL 4500 +#define DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME 120 +#define DEFAULT_TTL 4500 struct name_comp { - uint8_t *label; // label - size_t pos; // position in msg + uint8_t *label; // label + size_t pos; // position in msg - struct name_comp *next; + struct name_comp *next; }; // ----- label functions ----- // duplicates a name inline uint8_t *dup_nlabel(const uint8_t *n) { - assert(n[0] <= 63); // prevent mis-use - return (uint8_t *) strdup((char *) n); + assert(n[0] <= 63); // prevent mis-use + return (uint8_t *)strdup((char *)n); } // duplicates a label uint8_t *dup_label(const uint8_t *label) { - int len = *label + 1; - if (len > 63) - return NULL; - uint8_t *newlabel = malloc(len + 1); - strncpy((char *) newlabel, (char *) label, len); - newlabel[len] = '\0'; - return newlabel; + int len = *label + 1; + if (len > 63) + return NULL; + uint8_t *newlabel = malloc(len + 1); + strncpy((char *)newlabel, (char *)label, len); + newlabel[len] = '\0'; + return newlabel; } uint8_t *join_nlabel(const uint8_t *n1, const uint8_t *n2) { - int len1, len2; - uint8_t *s; + int len1, len2; + uint8_t *s; - assert(n1[0] <= 63 && n2[0] <= 63); // detect misuse + assert(n1[0] <= 63 && n2[0] <= 63); // detect misuse - len1 = strlen((char *) n1); - len2 = strlen((char *) n2); + len1 = strlen((char *)n1); + len2 = strlen((char *)n2); - s = malloc(len1 + len2 + 1); - strncpy((char *) s, (char *) n1, len1); - strncpy((char *) s+len1, (char *) n2, len2); - s[len1 + len2] = '\0'; - return s; + s = malloc(len1 + len2 + 1); + strncpy((char *)s, (char *)n1, len1); + strncpy((char *)s + len1, (char *)n2, len2); + s[len1 + len2] = '\0'; + return s; } // returns a human-readable name label in dotted form char *nlabel_to_str(const uint8_t *name) { - char *label, *labelp; - const uint8_t *p; + char *label, *labelp; + const uint8_t *p; - assert(name != NULL); + assert(name != NULL); - label = labelp = malloc(256); + label = labelp = malloc(256); - for (p = name; *p; p++) { - strncpy(labelp, (char *) p + 1, *p); - labelp += *p; - *labelp = '.'; - labelp++; + for (p = name; *p; p++) { + strncpy(labelp, (char *)p + 1, *p); + labelp += *p; + *labelp = '.'; + labelp++; - p += *p; - } + p += *p; + } - *labelp = '\0'; + *labelp = '\0'; - return label; + return label; } // returns the length of a label field // does NOT uncompress the field, so it could be as small as 2 bytes // or 1 for the root static size_t label_len(uint8_t *pkt_buf, size_t pkt_len, size_t off) { - uint8_t *p; - uint8_t *e = pkt_buf + pkt_len; - size_t len = 0; + uint8_t *p; + uint8_t *e = pkt_buf + pkt_len; + size_t len = 0; - for (p = pkt_buf + off; p < e; p++) { - if (*p == 0) { - return len + 1; - } else if ((*p & 0xC0) == 0xC0) { - return len + 2; - } else { - len += *p + 1; - p += *p; - } - } + for (p = pkt_buf + off; p < e; p++) { + if (*p == 0) { + return len + 1; + } else if ((*p & 0xC0) == 0xC0) { + return len + 2; + } else { + len += *p + 1; + p += *p; + } + } - return len; + return len; } // creates a label // free() after use uint8_t *create_label(const char *txt) { - int len; - uint8_t *s; + int len; + uint8_t *s; - assert(txt != NULL); - len = strlen(txt); - if (len > 63) - return NULL; + assert(txt != NULL); + len = strlen(txt); + if (len > 63) + return NULL; - s = malloc(len + 2); - s[0] = len; - strncpy((char *) s + 1, txt, len); - s[len + 1] = '\0'; + s = malloc(len + 2); + s[0] = len; + strncpy((char *)s + 1, txt, len); + s[len + 1] = '\0'; - return s; + return s; } // creates a uncompressed name label given a DNS name like "apple.b.com" // free() after use uint8_t *create_nlabel(const char *name) { - char *label; - char *p, *e, *lenpos; - int len = 0; + char *label; + char *p, *e, *lenpos; + int len = 0; - assert(name != NULL); + assert(name != NULL); - len = strlen(name); - label = malloc(len + 1 + 1); - if (label == NULL) - return NULL; + len = strlen(name); + label = malloc(len + 1 + 1); + if (label == NULL) + return NULL; - strncpy((char *) label + 1, name, len); - label[len + 1] = '\0'; + strncpy((char *)label + 1, name, len); + label[len + 1] = '\0'; - p = label; - e = p + len; - lenpos = p; + p = label; + e = p + len; + lenpos = p; - while (p < e) { - *lenpos = 0; - char *dot = memchr(p + 1, '.', e - p - 1); - if (dot == NULL) - dot = e + 1; - *lenpos = dot - p - 1; + while (p < e) { + *lenpos = 0; + char *dot = memchr(p + 1, '.', e - p - 1); + if (dot == NULL) + dot = e + 1; + *lenpos = dot - p - 1; - p = dot; - lenpos = dot; - } + p = dot; + lenpos = dot; + } - return (uint8_t *) label; + return (uint8_t *)label; } // copies a label from the buffer into a newly-allocated string // free() after use static uint8_t *copy_label(uint8_t *pkt_buf, size_t pkt_len, size_t off) { - int len; + int len; - if (off > pkt_len) - return NULL; + if (off > pkt_len) + return NULL; - len = pkt_buf[off] + 1; - if (off + len > pkt_len) { - DEBUG_PRINTF("label length exceeds packet buffer\n"); - return NULL; - } + len = pkt_buf[off] + 1; + if (off + len > pkt_len) { + DEBUG_PRINTF("label length exceeds packet buffer\n"); + return NULL; + } - return dup_label(pkt_buf + off); + return dup_label(pkt_buf + off); } // uncompresses a name // free() after use static uint8_t *uncompress_nlabel(uint8_t *pkt_buf, size_t pkt_len, size_t off) { - uint8_t *p; - uint8_t *e = pkt_buf + pkt_len; - size_t len = 0; - char *str, *sp; - if (off >= pkt_len) - return NULL; + uint8_t *p; + uint8_t *e = pkt_buf + pkt_len; + size_t len = 0; + char *str, *sp; + if (off >= pkt_len) + return NULL; - // calculate length of uncompressed label - for (p = pkt_buf + off; *p && p < e; p++) { - size_t llen = 0; - if ((*p & 0xC0) == 0xC0) { - uint8_t *p2 = pkt_buf + (((p[0] & ~0xC0) << 8) | p[1]); - llen = *p2 + 1; - p = p2 + llen - 1; - } else { - llen = *p + 1; - p += llen - 1; - } - len += llen; - } + // calculate length of uncompressed label + for (p = pkt_buf + off; *p && p < e; p++) { + size_t llen = 0; + if ((*p & 0xC0) == 0xC0) { + uint8_t *p2 = pkt_buf + (((p[0] & ~0xC0) << 8) | p[1]); + llen = *p2 + 1; + p = p2 + llen - 1; + } else { + llen = *p + 1; + p += llen - 1; + } + len += llen; + } - str = sp = malloc(len + 1); - if (str == NULL) - return NULL; + str = sp = malloc(len + 1); + if (str == NULL) + return NULL; - // FIXME: must merge this with above code - for (p = pkt_buf + off; *p && p < e; p++) { - size_t llen = 0; - if ((*p & 0xC0) == 0xC0) { - uint8_t *p2 = pkt_buf + (((p[0] & ~0xC0) << 8) | p[1]); - llen = *p2 + 1; - strncpy(sp, (char *) p2, llen); - p = p2 + llen - 1; - } else { - llen = *p + 1; - strncpy(sp, (char *) p, llen); - p += llen - 1; - } - sp += llen; - } - *sp = '\0'; + // FIXME: must merge this with above code + for (p = pkt_buf + off; *p && p < e; p++) { + size_t llen = 0; + if ((*p & 0xC0) == 0xC0) { + uint8_t *p2 = pkt_buf + (((p[0] & ~0xC0) << 8) | p[1]); + llen = *p2 + 1; + strncpy(sp, (char *)p2, llen); + p = p2 + llen - 1; + } else { + llen = *p + 1; + strncpy(sp, (char *)p, llen); + p += llen - 1; + } + sp += llen; + } + *sp = '\0'; - return (uint8_t *) str; + return (uint8_t *)str; } // ----- RR list & group functions ----- const char *rr_get_type_name(enum rr_type type) { - switch (type) { - case RR_A: return "A"; - case RR_PTR: return "PTR"; - case RR_TXT: return "TXT"; - case RR_AAAA: return "AAAA"; - case RR_SRV: return "SRV"; - case RR_NSEC: return "NSEC"; - case RR_ANY: return "ANY"; - } - return NULL; + switch (type) { + case RR_A: + return "A"; + case RR_PTR: + return "PTR"; + case RR_TXT: + return "TXT"; + case RR_AAAA: + return "AAAA"; + case RR_SRV: + return "SRV"; + case RR_NSEC: + return "NSEC"; + case RR_ANY: + return "ANY"; + } + return NULL; } void rr_entry_destroy(struct rr_entry *rr) { - struct rr_data_txt *txt_rec; - assert(rr); + struct rr_data_txt *txt_rec; + assert(rr); - // check rr_type and free data elements - switch (rr->type) { - case RR_PTR: - if (rr->data.PTR.name) - free(rr->data.PTR.name); - // don't free entry - break; + // check rr_type and free data elements + switch (rr->type) { + case RR_PTR: + if (rr->data.PTR.name) + free(rr->data.PTR.name); + // don't free entry + break; - case RR_TXT: - txt_rec = &rr->data.TXT; - while (txt_rec) { - struct rr_data_txt *next = txt_rec->next; - if (txt_rec->txt) - free(txt_rec->txt); + case RR_TXT: + txt_rec = &rr->data.TXT; + while (txt_rec) { + struct rr_data_txt *next = txt_rec->next; + if (txt_rec->txt) + free(txt_rec->txt); - // only free() if it wasn't part of the struct - if (txt_rec != &rr->data.TXT) - free(txt_rec); + // only free() if it wasn't part of the struct + if (txt_rec != &rr->data.TXT) + free(txt_rec); - txt_rec = next; - } - break; + txt_rec = next; + } + break; - case RR_SRV: - if (rr->data.SRV.target) - free(rr->data.SRV.target); - break; + case RR_SRV: + if (rr->data.SRV.target) + free(rr->data.SRV.target); + break; - default: - // nothing to free - break; - } + default: + // nothing to free + break; + } - free(rr->name); - free(rr); + free(rr->name); + free(rr); } // destroys an RR list (and optionally, items) void rr_list_destroy(struct rr_list *rr, char destroy_items) { - struct rr_list *rr_next; + struct rr_list *rr_next; - for (; rr; rr = rr_next) { - rr_next = rr->next; - if (destroy_items) - rr_entry_destroy(rr->e); - free(rr); - } + for (; rr; rr = rr_next) { + rr_next = rr->next; + if (destroy_items) + rr_entry_destroy(rr->e); + free(rr); + } } int rr_list_count(struct rr_list *rr) { - int i = 0; - for (; rr; i++, rr = rr->next); - return i; + int i = 0; + for (; rr; i++, rr = rr->next) + ; + return i; } struct rr_entry *rr_list_remove(struct rr_list **rr_head, struct rr_entry *rr) { - struct rr_list *le = *rr_head, *pe = NULL; - for (; le; le = le->next) { - if (le->e == rr) { - if (pe == NULL) { - *rr_head = le->next; - free(le); - return rr; - } else { - pe->next = le->next; - free(le); - return rr; - } - } - pe = le; - } - return NULL; + struct rr_list *le = *rr_head, *pe = NULL; + for (; le; le = le->next) { + if (le->e == rr) { + if (pe == NULL) { + *rr_head = le->next; + free(le); + return rr; + } else { + pe->next = le->next; + free(le); + return rr; + } + } + pe = le; + } + return NULL; } // appends an rr_entry to an RR list @@ -375,650 +382,642 @@ struct rr_entry *rr_list_remove(struct rr_list **rr_head, struct rr_entry *rr) { // RRs are compared by memory location - not its contents // return value of 0 means item not added int rr_list_append(struct rr_list **rr_head, struct rr_entry *rr) { - struct rr_list *node = malloc(sizeof(struct rr_list)); - node->e = rr; - node->next = NULL; + struct rr_list *node = malloc(sizeof(struct rr_list)); + node->e = rr; + node->next = NULL; - if (*rr_head == NULL) { - *rr_head = node; - } else { - struct rr_list *e = *rr_head, *taile; - for (; e; e = e->next) { - // already in list - don't add - if (e->e == rr) { - free(node); - return 0; - } - if (e->next == NULL) - taile = e; - } - taile->next = node; - } - return 1; + if (*rr_head == NULL) { + *rr_head = node; + } else { + struct rr_list *e = *rr_head, *taile; + for (; e; e = e->next) { + // already in list - don't add + if (e->e == rr) { + free(node); + return 0; + } + if (e->next == NULL) + taile = e; + } + taile->next = node; + } + return 1; } -#define FILL_RR_ENTRY(rr, _name, _type) \ - rr->name = _name; \ - rr->type = _type; \ - rr->ttl = DEFAULT_TTL; \ - rr->cache_flush = 1; \ - rr->rr_class = 1; +#define FILL_RR_ENTRY(rr, _name, _type) \ + rr->name = _name; \ + rr->type = _type; \ + rr->ttl = DEFAULT_TTL; \ + rr->cache_flush = 1; \ + rr->rr_class = 1; struct rr_entry *rr_create_a(uint8_t *name, uint32_t addr) { - DECL_MALLOC_ZERO_STRUCT(rr, rr_entry); - FILL_RR_ENTRY(rr, name, RR_A); - rr->data.A.addr = addr; - rr->ttl = DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME; // 120 seconds -- see RFC 6762 Section 10 - return rr; + DECL_MALLOC_ZERO_STRUCT(rr, rr_entry); + FILL_RR_ENTRY(rr, name, RR_A); + rr->data.A.addr = addr; + rr->ttl = DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME; // 120 seconds -- see RFC 6762 Section 10 + return rr; } struct rr_entry *rr_create_aaaa(uint8_t *name, struct in6_addr *addr) { - DECL_MALLOC_ZERO_STRUCT(rr, rr_entry); - FILL_RR_ENTRY(rr, name, RR_AAAA); - rr->data.AAAA.addr = addr; - rr->ttl = DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME; // 120 seconds -- see RFC 6762 Section 10 - return rr; + DECL_MALLOC_ZERO_STRUCT(rr, rr_entry); + FILL_RR_ENTRY(rr, name, RR_AAAA); + rr->data.AAAA.addr = addr; + rr->ttl = DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME; // 120 seconds -- see RFC 6762 Section 10 + return rr; } struct rr_entry *rr_create_srv(uint8_t *name, uint16_t port, uint8_t *target) { - DECL_MALLOC_ZERO_STRUCT(rr, rr_entry); - FILL_RR_ENTRY(rr, name, RR_SRV); - rr->data.SRV.port = port; - rr->data.SRV.target = target; - return rr; + DECL_MALLOC_ZERO_STRUCT(rr, rr_entry); + FILL_RR_ENTRY(rr, name, RR_SRV); + rr->data.SRV.port = port; + rr->data.SRV.target = target; + return rr; } struct rr_entry *rr_create_ptr(uint8_t *name, struct rr_entry *d_rr) { - DECL_MALLOC_ZERO_STRUCT(rr, rr_entry); - FILL_RR_ENTRY(rr, name, RR_PTR); - rr->cache_flush = 0; // PTRs shouldn't have their cache flush bit set - rr->data.PTR.entry = d_rr; - return rr; + DECL_MALLOC_ZERO_STRUCT(rr, rr_entry); + FILL_RR_ENTRY(rr, name, RR_PTR); + rr->cache_flush = 0; // PTRs shouldn't have their cache flush bit set + rr->data.PTR.entry = d_rr; + return rr; } struct rr_entry *rr_create(uint8_t *name, enum rr_type type) { - DECL_MALLOC_ZERO_STRUCT(rr, rr_entry); - FILL_RR_ENTRY(rr, name, type); - return rr; + DECL_MALLOC_ZERO_STRUCT(rr, rr_entry); + FILL_RR_ENTRY(rr, name, type); + return rr; } void rr_set_nsec(struct rr_entry *rr_nsec, enum rr_type type) { - assert(rr_nsec->type = RR_NSEC); - assert((type / 8) < sizeof(rr_nsec->data.NSEC.bitmap)); + assert(rr_nsec->type = RR_NSEC); + assert((type / 8) < sizeof(rr_nsec->data.NSEC.bitmap)); - rr_nsec->data.NSEC.bitmap[ type / 8 ] = 1 << (7 - (type % 8)); + rr_nsec->data.NSEC.bitmap[type / 8] = 1 << (7 - (type % 8)); } void rr_add_txt(struct rr_entry *rr_txt, const char *txt) { - struct rr_data_txt *txt_rec; - assert(rr_txt->type == RR_TXT); + struct rr_data_txt *txt_rec; + assert(rr_txt->type == RR_TXT); - txt_rec = &rr_txt->data.TXT; + txt_rec = &rr_txt->data.TXT; - // is current data filled? - if (txt_rec->txt == NULL) { - txt_rec->txt = create_label(txt); - return; - } + // is current data filled? + if (txt_rec->txt == NULL) { + txt_rec->txt = create_label(txt); + return; + } - // find the last node - for (; txt_rec->next; txt_rec = txt_rec->next); + // find the last node + for (; txt_rec->next; txt_rec = txt_rec->next) + ; - // create a new empty node - txt_rec->next = malloc(sizeof(struct rr_data_txt)); + // create a new empty node + txt_rec->next = malloc(sizeof(struct rr_data_txt)); - txt_rec = txt_rec->next; - txt_rec->txt = create_label(txt); - txt_rec->next = NULL; + txt_rec = txt_rec->next; + txt_rec->txt = create_label(txt); + txt_rec->next = NULL; } // adds a record to an rr_group void rr_group_add(struct rr_group **group, struct rr_entry *rr) { - struct rr_group *g; + struct rr_group *g; - assert(rr != NULL); + assert(rr != NULL); - if (*group) { - g = rr_group_find(*group, rr->name); - if (g) { - rr_list_append(&g->rr, rr); - return; - } - } + if (*group) { + g = rr_group_find(*group, rr->name); + if (g) { + rr_list_append(&g->rr, rr); + return; + } + } - MALLOC_ZERO_STRUCT(g, rr_group); - g->name = dup_nlabel(rr->name); - rr_list_append(&g->rr, rr); + MALLOC_ZERO_STRUCT(g, rr_group); + g->name = dup_nlabel(rr->name); + rr_list_append(&g->rr, rr); - // prepend to list - g->next = *group; - *group = g; + // prepend to list + g->next = *group; + *group = g; } // finds a rr_group matching the given name -struct rr_group *rr_group_find(struct rr_group* g, uint8_t *name) { - for (; g; g = g->next) { - if (cmp_nlabel(g->name, name) == 0) - return g; - } - return NULL; +struct rr_group *rr_group_find(struct rr_group *g, uint8_t *name) { + for (; g; g = g->next) { + if (cmp_nlabel(g->name, name) == 0) + return g; + } + return NULL; } struct rr_entry *rr_entry_find(struct rr_list *rr_list, uint8_t *name, uint16_t type) { - struct rr_list *rr = rr_list; - for (; rr; rr = rr->next) { - if (rr->e->type == type && cmp_nlabel(rr->e->name, name) == 0) - return rr->e; - } - return NULL; + struct rr_list *rr = rr_list; + for (; rr; rr = rr->next) { + if (rr->e->type == type && cmp_nlabel(rr->e->name, name) == 0) + return rr->e; + } + return NULL; } // looks for a matching entry in rr_list // if entry is a PTR, we need to check if the PTR target also matches struct rr_entry *rr_entry_match(struct rr_list *rr_list, struct rr_entry *entry) { - struct rr_list *rr = rr_list; - for (; rr; rr = rr->next) { - if (rr->e->type == entry->type && cmp_nlabel(rr->e->name, entry->name) == 0) { - if (entry->type != RR_PTR) { - return rr->e; - } else if (cmp_nlabel(MDNS_RR_GET_PTR_NAME(entry), MDNS_RR_GET_PTR_NAME(rr->e)) == 0) { - // if it's a PTR, we need to make sure PTR target also matches - return rr->e; - } - } - } - return NULL; + struct rr_list *rr = rr_list; + for (; rr; rr = rr->next) { + if (rr->e->type == entry->type && cmp_nlabel(rr->e->name, entry->name) == 0) { + if (entry->type != RR_PTR) { + return rr->e; + } else if (cmp_nlabel(MDNS_RR_GET_PTR_NAME(entry), MDNS_RR_GET_PTR_NAME(rr->e)) == 0) { + // if it's a PTR, we need to make sure PTR target also matches + return rr->e; + } + } + } + return NULL; } void rr_group_destroy(struct rr_group *group) { - struct rr_group *g = group; + struct rr_group *g = group; - while (g) { - struct rr_group *nextg = g->next; - free(g->name); - rr_list_destroy(g->rr, 1); - free(g); - g = nextg; - } + while (g) { + struct rr_group *nextg = g->next; + free(g->name); + rr_list_destroy(g->rr, 1); + free(g); + g = nextg; + } } uint8_t *mdns_write_u16(uint8_t *ptr, const uint16_t v) { - *ptr++ = (uint8_t) (v >> 8) & 0xFF; - *ptr++ = (uint8_t) (v >> 0) & 0xFF; - return ptr; + *ptr++ = (uint8_t)(v >> 8) & 0xFF; + *ptr++ = (uint8_t)(v >> 0) & 0xFF; + return ptr; } uint8_t *mdns_write_u32(uint8_t *ptr, const uint32_t v) { - *ptr++ = (uint8_t) (v >> 24) & 0xFF; - *ptr++ = (uint8_t) (v >> 16) & 0xFF; - *ptr++ = (uint8_t) (v >> 8) & 0xFF; - *ptr++ = (uint8_t) (v >> 0) & 0xFF; - return ptr; + *ptr++ = (uint8_t)(v >> 24) & 0xFF; + *ptr++ = (uint8_t)(v >> 16) & 0xFF; + *ptr++ = (uint8_t)(v >> 8) & 0xFF; + *ptr++ = (uint8_t)(v >> 0) & 0xFF; + return ptr; } uint16_t mdns_read_u16(const uint8_t *ptr) { - return ((ptr[0] & 0xFF) << 8) | - ((ptr[1] & 0xFF) << 0); + return ((ptr[0] & 0xFF) << 8) | ((ptr[1] & 0xFF) << 0); } uint32_t mdns_read_u32(const uint8_t *ptr) { - return ((ptr[0] & 0xFF) << 24) | - ((ptr[1] & 0xFF) << 16) | - ((ptr[2] & 0xFF) << 8) | - ((ptr[3] & 0xFF) << 0); + return ((ptr[0] & 0xFF) << 24) | ((ptr[1] & 0xFF) << 16) | ((ptr[2] & 0xFF) << 8) | + ((ptr[3] & 0xFF) << 0); } // initialize the packet for reply // clears the packet of list structures but not its list items void mdns_init_reply(struct mdns_pkt *pkt, uint16_t id) { - // copy transaction ID - pkt->id = id; + // copy transaction ID + pkt->id = id; - // response flags - pkt->flags = MDNS_FLAG_RESP | MDNS_FLAG_AA; + // response flags + pkt->flags = MDNS_FLAG_RESP | MDNS_FLAG_AA; - rr_list_destroy(pkt->rr_qn, 0); - rr_list_destroy(pkt->rr_ans, 0); - rr_list_destroy(pkt->rr_auth, 0); - rr_list_destroy(pkt->rr_add, 0); + rr_list_destroy(pkt->rr_qn, 0); + rr_list_destroy(pkt->rr_ans, 0); + rr_list_destroy(pkt->rr_auth, 0); + rr_list_destroy(pkt->rr_add, 0); - pkt->rr_qn = NULL; - pkt->rr_ans = NULL; - pkt->rr_auth = NULL; - pkt->rr_add = NULL; + pkt->rr_qn = NULL; + pkt->rr_ans = NULL; + pkt->rr_auth = NULL; + pkt->rr_add = NULL; - pkt->num_qn = 0; - pkt->num_ans_rr = 0; - pkt->num_auth_rr = 0; - pkt->num_add_rr = 0; + pkt->num_qn = 0; + pkt->num_ans_rr = 0; + pkt->num_auth_rr = 0; + pkt->num_add_rr = 0; } // destroys an mdns_pkt struct, including its contents void mdns_pkt_destroy(struct mdns_pkt *p) { - rr_list_destroy(p->rr_qn, 1); - rr_list_destroy(p->rr_ans, 1); - rr_list_destroy(p->rr_auth, 1); - rr_list_destroy(p->rr_add, 1); + rr_list_destroy(p->rr_qn, 1); + rr_list_destroy(p->rr_ans, 1); + rr_list_destroy(p->rr_auth, 1); + rr_list_destroy(p->rr_add, 1); - free(p); + free(p); } - // parse the MDNS questions section // stores the parsed data in the given mdns_pkt struct -static size_t mdns_parse_qn(uint8_t *pkt_buf, size_t pkt_len, size_t off, - struct mdns_pkt *pkt) { - const uint8_t *p = pkt_buf + off; - struct rr_entry *rr; - uint8_t *name; +static size_t mdns_parse_qn(uint8_t *pkt_buf, size_t pkt_len, size_t off, struct mdns_pkt *pkt) { + const uint8_t *p = pkt_buf + off; + struct rr_entry *rr; + uint8_t *name; - assert(pkt != NULL); + assert(pkt != NULL); - rr = malloc(sizeof(struct rr_entry)); - memset(rr, 0, sizeof(struct rr_entry)); + rr = malloc(sizeof(struct rr_entry)); + memset(rr, 0, sizeof(struct rr_entry)); - name = uncompress_nlabel(pkt_buf, pkt_len, off); - p += label_len(pkt_buf, pkt_len, off); - rr->name = name; + name = uncompress_nlabel(pkt_buf, pkt_len, off); + p += label_len(pkt_buf, pkt_len, off); + rr->name = name; - rr->type = mdns_read_u16(p); - p += sizeof(uint16_t); + rr->type = mdns_read_u16(p); + p += sizeof(uint16_t); - rr->unicast_query = (*p & 0x80) == 0x80; - rr->rr_class = mdns_read_u16(p) & ~0x80; - p += sizeof(uint16_t); + rr->unicast_query = (*p & 0x80) == 0x80; + rr->rr_class = mdns_read_u16(p) & ~0x80; + p += sizeof(uint16_t); - rr_list_append(&pkt->rr_qn, rr); + rr_list_append(&pkt->rr_qn, rr); - return p - (pkt_buf + off); + return p - (pkt_buf + off); } // parse the MDNS RR section // stores the parsed data in the given mdns_pkt struct -static size_t mdns_parse_rr(uint8_t *pkt_buf, size_t pkt_len, size_t off, - struct mdns_pkt *pkt) { - const uint8_t *p = pkt_buf + off; - const uint8_t *e = pkt_buf + pkt_len; - struct rr_entry *rr; - uint8_t *name; - size_t rr_data_len = 0; - struct rr_data_txt *txt_rec; - int parse_error = 0; +static size_t mdns_parse_rr(uint8_t *pkt_buf, size_t pkt_len, size_t off, struct mdns_pkt *pkt) { + const uint8_t *p = pkt_buf + off; + const uint8_t *e = pkt_buf + pkt_len; + struct rr_entry *rr; + uint8_t *name; + size_t rr_data_len = 0; + struct rr_data_txt *txt_rec; + int parse_error = 0; - assert(pkt != NULL); + assert(pkt != NULL); - if (off > pkt_len) - return 0; + if (off > pkt_len) + return 0; - rr = malloc(sizeof(struct rr_entry)); - memset(rr, 0, sizeof(struct rr_entry)); + rr = malloc(sizeof(struct rr_entry)); + memset(rr, 0, sizeof(struct rr_entry)); - name = uncompress_nlabel(pkt_buf, pkt_len, off); - p += label_len(pkt_buf, pkt_len, off); - rr->name = name; + name = uncompress_nlabel(pkt_buf, pkt_len, off); + p += label_len(pkt_buf, pkt_len, off); + rr->name = name; - rr->type = mdns_read_u16(p); - p += sizeof(uint16_t); + rr->type = mdns_read_u16(p); + p += sizeof(uint16_t); - rr->cache_flush = (*p & 0x80) == 0x80; - rr->rr_class = mdns_read_u16(p) & ~0x80; - p += sizeof(uint16_t); + rr->cache_flush = (*p & 0x80) == 0x80; + rr->rr_class = mdns_read_u16(p) & ~0x80; + p += sizeof(uint16_t); - rr->ttl = mdns_read_u32(p); - p += sizeof(uint32_t); + rr->ttl = mdns_read_u32(p); + p += sizeof(uint32_t); - // RR data - rr_data_len = mdns_read_u16(p); - p += sizeof(uint16_t); + // RR data + rr_data_len = mdns_read_u16(p); + p += sizeof(uint16_t); - if (p + rr_data_len > e) { - DEBUG_PRINTF("rr_data_len goes beyond packet buffer: %lu > %lu\n", rr_data_len, e - p); - rr_entry_destroy(rr); - return 0; - } + if (p + rr_data_len > e) { + DEBUG_PRINTF("rr_data_len goes beyond packet buffer: %lu > %lu\n", rr_data_len, e - p); + rr_entry_destroy(rr); + return 0; + } - e = p + rr_data_len; + e = p + rr_data_len; - // see if we can parse the RR data - switch (rr->type) { - case RR_A: - if (rr_data_len < sizeof(uint32_t)) { - DEBUG_PRINTF("invalid rr_data_len=%lu for A record\n", rr_data_len); - parse_error = 1; - break; - } - rr->data.A.addr = ntohl(mdns_read_u32(p)); /* addr already in net order */ - p += sizeof(uint32_t); - break; + // see if we can parse the RR data + switch (rr->type) { + case RR_A: + if (rr_data_len < sizeof(uint32_t)) { + DEBUG_PRINTF("invalid rr_data_len=%lu for A record\n", rr_data_len); + parse_error = 1; + break; + } + rr->data.A.addr = ntohl(mdns_read_u32(p)); /* addr already in net order */ + p += sizeof(uint32_t); + break; - case RR_AAAA: - if (rr_data_len < sizeof(struct in6_addr)) { - DEBUG_PRINTF("invalid rr_data_len=%lu for AAAA record\n", rr_data_len); - parse_error = 1; - break; - } - rr->data.AAAA.addr = malloc(sizeof(struct in6_addr)); - int i; - for (i = 0; i < sizeof(struct in6_addr); i++) - rr->data.AAAA.addr->s6_addr[i] = p[i]; - p += sizeof(struct in6_addr); - break; + case RR_AAAA: + if (rr_data_len < sizeof(struct in6_addr)) { + DEBUG_PRINTF("invalid rr_data_len=%lu for AAAA record\n", rr_data_len); + parse_error = 1; + break; + } + rr->data.AAAA.addr = malloc(sizeof(struct in6_addr)); + int i; + for (i = 0; i < sizeof(struct in6_addr); i++) + rr->data.AAAA.addr->s6_addr[i] = p[i]; + p += sizeof(struct in6_addr); + break; - case RR_PTR: - rr->data.PTR.name = uncompress_nlabel(pkt_buf, pkt_len, p - pkt_buf); - if (rr->data.PTR.name == NULL) { - DEBUG_PRINTF("unable to parse/uncompress label for PTR name\n"); - parse_error = 1; - break; - } - p += rr_data_len; - break; + case RR_PTR: + rr->data.PTR.name = uncompress_nlabel(pkt_buf, pkt_len, p - pkt_buf); + if (rr->data.PTR.name == NULL) { + DEBUG_PRINTF("unable to parse/uncompress label for PTR name\n"); + parse_error = 1; + break; + } + p += rr_data_len; + break; - case RR_TXT: - txt_rec = &rr->data.TXT; + case RR_TXT: + txt_rec = &rr->data.TXT; - // not supposed to happen, but we should handle it - if (rr_data_len == 0) { - DEBUG_PRINTF("WARN: rr_data_len for TXT is 0\n"); - txt_rec->txt = create_label(""); - break; - } + // not supposed to happen, but we should handle it + if (rr_data_len == 0) { + DEBUG_PRINTF("WARN: rr_data_len for TXT is 0\n"); + txt_rec->txt = create_label(""); + break; + } - while (1) { - txt_rec->txt = copy_label(pkt_buf, pkt_len, p - pkt_buf); - if (txt_rec->txt == NULL) { - DEBUG_PRINTF("unable to copy label for TXT record\n"); - parse_error = 1; - break; - } - p += txt_rec->txt[0] + 1; + while (1) { + txt_rec->txt = copy_label(pkt_buf, pkt_len, p - pkt_buf); + if (txt_rec->txt == NULL) { + DEBUG_PRINTF("unable to copy label for TXT record\n"); + parse_error = 1; + break; + } + p += txt_rec->txt[0] + 1; - if (p >= e) - break; + if (p >= e) + break; - // allocate another record - txt_rec->next = malloc(sizeof(struct rr_data_txt)); - txt_rec = txt_rec->next; - txt_rec->next = NULL; - } - break; + // allocate another record + txt_rec->next = malloc(sizeof(struct rr_data_txt)); + txt_rec = txt_rec->next; + txt_rec->next = NULL; + } + break; - default: - // skip to end of RR data - p = e; - } + default: + // skip to end of RR data + p = e; + } - // if there was a parse error, destroy partial rr_entry - if (parse_error) { - rr_entry_destroy(rr); - return 0; - } + // if there was a parse error, destroy partial rr_entry + if (parse_error) { + rr_entry_destroy(rr); + return 0; + } - rr_list_append(&pkt->rr_ans, rr); + rr_list_append(&pkt->rr_ans, rr); - return p - (pkt_buf + off); + return p - (pkt_buf + off); } // parse a MDNS packet into an mdns_pkt struct struct mdns_pkt *mdns_parse_pkt(uint8_t *pkt_buf, size_t pkt_len) { - uint8_t *p = pkt_buf; - size_t off; - struct mdns_pkt *pkt; - int i; + uint8_t *p = pkt_buf; + size_t off; + struct mdns_pkt *pkt; + int i; - if (pkt_len < 12) - return NULL; + if (pkt_len < 12) + return NULL; - MALLOC_ZERO_STRUCT(pkt, mdns_pkt); + MALLOC_ZERO_STRUCT(pkt, mdns_pkt); - // parse header - pkt->id = mdns_read_u16(p); p += sizeof(uint16_t); - pkt->flags = mdns_read_u16(p); p += sizeof(uint16_t); - pkt->num_qn = mdns_read_u16(p); p += sizeof(uint16_t); - pkt->num_ans_rr = mdns_read_u16(p); p += sizeof(uint16_t); - pkt->num_auth_rr = mdns_read_u16(p); p += sizeof(uint16_t); - pkt->num_add_rr = mdns_read_u16(p); p += sizeof(uint16_t); + // parse header + pkt->id = mdns_read_u16(p); + p += sizeof(uint16_t); + pkt->flags = mdns_read_u16(p); + p += sizeof(uint16_t); + pkt->num_qn = mdns_read_u16(p); + p += sizeof(uint16_t); + pkt->num_ans_rr = mdns_read_u16(p); + p += sizeof(uint16_t); + pkt->num_auth_rr = mdns_read_u16(p); + p += sizeof(uint16_t); + pkt->num_add_rr = mdns_read_u16(p); + p += sizeof(uint16_t); - off = p - pkt_buf; + off = p - pkt_buf; - // parse questions - for (i = 0; i < pkt->num_qn; i++) { - size_t l = mdns_parse_qn(pkt_buf, pkt_len, off, pkt); - if (! l) { - DEBUG_PRINTF("error parsing question #%d\n", i); - mdns_pkt_destroy(pkt); - return NULL; - } + // parse questions + for (i = 0; i < pkt->num_qn; i++) { + size_t l = mdns_parse_qn(pkt_buf, pkt_len, off, pkt); + if (!l) { + DEBUG_PRINTF("error parsing question #%d\n", i); + mdns_pkt_destroy(pkt); + return NULL; + } - off += l; - } + off += l; + } - // parse answer RRs - for (i = 0; i < pkt->num_ans_rr; i++) { - size_t l = mdns_parse_rr(pkt_buf, pkt_len, off, pkt); - if (! l) { - DEBUG_PRINTF("error parsing answer #%d\n", i); - mdns_pkt_destroy(pkt); - return NULL; - } + // parse answer RRs + for (i = 0; i < pkt->num_ans_rr; i++) { + size_t l = mdns_parse_rr(pkt_buf, pkt_len, off, pkt); + if (!l) { + DEBUG_PRINTF("error parsing answer #%d\n", i); + mdns_pkt_destroy(pkt); + return NULL; + } - off += l; - } + off += l; + } - // TODO: parse the authority and additional RR sections + // TODO: parse the authority and additional RR sections - return pkt; + return pkt; } // encodes a name (label) into a packet using the name compression scheme // encoded names will be added to the compression list for subsequent use -static size_t mdns_encode_name(uint8_t *pkt_buf, size_t pkt_len, size_t off, - const uint8_t *name, struct name_comp *comp) { - struct name_comp *c, *c_tail = NULL; - uint8_t *p = pkt_buf + off; - size_t len = 0; +static size_t mdns_encode_name(uint8_t *pkt_buf, size_t pkt_len, size_t off, const uint8_t *name, + struct name_comp *comp) { + struct name_comp *c, *c_tail = NULL; + uint8_t *p = pkt_buf + off; + size_t len = 0; - if (name) { - while (*name) { - // find match for compression - for (c = comp; c; c = c->next) { - if (cmp_nlabel(name, c->label) == 0) { - mdns_write_u16(p, 0xC000 | (c->pos & ~0xC000)); - return len + sizeof(uint16_t); - } - - if (c->next == NULL) - c_tail = c; - } - - // copy this segment - int segment_len = *name + 1; - strncpy((char *) p, (char *) name, segment_len); - - // cache the name for subsequent compression - DECL_MALLOC_ZERO_STRUCT(new_c, name_comp); - - new_c->label = (uint8_t *) name; - new_c->pos = p - pkt_buf; - c_tail->next = new_c; - - // advance to next name segment - p += segment_len; - len += segment_len; - name += segment_len; - } + if (name) { + while (*name) { + // find match for compression + for (c = comp; c; c = c->next) { + if (cmp_nlabel(name, c->label) == 0) { + mdns_write_u16(p, 0xC000 | (c->pos & ~0xC000)); + return len + sizeof(uint16_t); } - *p = '\0'; // root "label" - len += 1; + if (c->next == NULL) + c_tail = c; + } - return len; + // copy this segment + int segment_len = *name + 1; + strncpy((char *)p, (char *)name, segment_len); + + // cache the name for subsequent compression + DECL_MALLOC_ZERO_STRUCT(new_c, name_comp); + + new_c->label = (uint8_t *)name; + new_c->pos = p - pkt_buf; + c_tail->next = new_c; + + // advance to next name segment + p += segment_len; + len += segment_len; + name += segment_len; + } + } + + *p = '\0'; // root "label" + len += 1; + + return len; } // encodes an RR entry at the given offset // returns the size of the entire RR entry -static size_t mdns_encode_rr(uint8_t *pkt_buf, size_t pkt_len, size_t off, - struct rr_entry *rr, struct name_comp *comp) { - uint8_t *p = pkt_buf + off, *p_data; - size_t l; - struct rr_data_txt *txt_rec; - uint8_t *label; - int i; +static size_t mdns_encode_rr(uint8_t *pkt_buf, size_t pkt_len, size_t off, struct rr_entry *rr, + struct name_comp *comp) { + uint8_t *p = pkt_buf + off, *p_data; + size_t l; + struct rr_data_txt *txt_rec; + uint8_t *label; + int i; - assert(off < pkt_len); + assert(off < pkt_len); - // name - l = mdns_encode_name(pkt_buf, pkt_len, off, rr->name, comp); - assert(l != 0); - p += l; + // name + l = mdns_encode_name(pkt_buf, pkt_len, off, rr->name, comp); + assert(l != 0); + p += l; - // type - p = mdns_write_u16(p, rr->type); + // type + p = mdns_write_u16(p, rr->type); - // class & cache flush - p = mdns_write_u16(p, (rr->rr_class & ~0x8000) | (rr->cache_flush << 15)); + // class & cache flush + p = mdns_write_u16(p, (rr->rr_class & ~0x8000) | (rr->cache_flush << 15)); - // TTL - p = mdns_write_u32(p, rr->ttl); + // TTL + p = mdns_write_u32(p, rr->ttl); - // data length (filled in later) - p += sizeof(uint16_t); + // data length (filled in later) + p += sizeof(uint16_t); - // start of data marker - p_data = p; + // start of data marker + p_data = p; - switch (rr->type) { - case RR_A: - /* htonl() needed coz addr already in net order */ - p = mdns_write_u32(p, htonl(rr->data.A.addr)); - break; + switch (rr->type) { + case RR_A: + /* htonl() needed coz addr already in net order */ + p = mdns_write_u32(p, htonl(rr->data.A.addr)); + break; - case RR_AAAA: - for (i = 0; i < sizeof(struct in6_addr); i++) - *p++ = rr->data.AAAA.addr->s6_addr[i]; - break; + case RR_AAAA: + for (i = 0; i < sizeof(struct in6_addr); i++) + *p++ = rr->data.AAAA.addr->s6_addr[i]; + break; - case RR_PTR: - label = rr->data.PTR.name ? - rr->data.PTR.name : - rr->data.PTR.entry->name; - p += mdns_encode_name(pkt_buf, pkt_len, p - pkt_buf, label, comp); - break; + case RR_PTR: + label = rr->data.PTR.name ? rr->data.PTR.name : rr->data.PTR.entry->name; + p += mdns_encode_name(pkt_buf, pkt_len, p - pkt_buf, label, comp); + break; - case RR_TXT: - txt_rec = &rr->data.TXT; - for (; txt_rec; txt_rec = txt_rec->next) { - int len = txt_rec->txt[0] + 1; - strncpy((char *) p, (char *) txt_rec->txt, len); - p += len; - } - break; + case RR_TXT: + txt_rec = &rr->data.TXT; + for (; txt_rec; txt_rec = txt_rec->next) { + int len = txt_rec->txt[0] + 1; + strncpy((char *)p, (char *)txt_rec->txt, len); + p += len; + } + break; - case RR_SRV: - p = mdns_write_u16(p, rr->data.SRV.priority); + case RR_SRV: + p = mdns_write_u16(p, rr->data.SRV.priority); - p = mdns_write_u16(p, rr->data.SRV.weight); + p = mdns_write_u16(p, rr->data.SRV.weight); - p = mdns_write_u16(p, rr->data.SRV.port); + p = mdns_write_u16(p, rr->data.SRV.port); - p += mdns_encode_name(pkt_buf, pkt_len, p - pkt_buf, - rr->data.SRV.target, comp); - break; + p += mdns_encode_name(pkt_buf, pkt_len, p - pkt_buf, rr->data.SRV.target, comp); + break; - case RR_NSEC: - p += mdns_encode_name(pkt_buf, pkt_len, p - pkt_buf, - rr->name, comp); + case RR_NSEC: + p += mdns_encode_name(pkt_buf, pkt_len, p - pkt_buf, rr->name, comp); - *p++ = 0; // bitmap window/block number + *p++ = 0; // bitmap window/block number - *p++ = sizeof(rr->data.NSEC.bitmap); // bitmap length + *p++ = sizeof(rr->data.NSEC.bitmap); // bitmap length - for (i = 0; i < sizeof(rr->data.NSEC.bitmap); i++) - *p++ = rr->data.NSEC.bitmap[i]; + for (i = 0; i < sizeof(rr->data.NSEC.bitmap); i++) + *p++ = rr->data.NSEC.bitmap[i]; - break; + break; - default: - DEBUG_PRINTF("unhandled rr type 0x%02x\n", rr->type); - } + default: + DEBUG_PRINTF("unhandled rr type 0x%02x\n", rr->type); + } - // calculate data length based on p - l = p - p_data; + // calculate data length based on p + l = p - p_data; - // fill in the length - mdns_write_u16(p - l - sizeof(uint16_t), l); + // fill in the length + mdns_write_u16(p - l - sizeof(uint16_t), l); - return p - pkt_buf - off; + return p - pkt_buf - off; } // encodes a MDNS packet from the given mdns_pkt struct into a buffer // returns the size of the entire MDNS packet size_t mdns_encode_pkt(struct mdns_pkt *answer, uint8_t *pkt_buf, size_t pkt_len) { - struct name_comp *comp; - uint8_t *p = pkt_buf; - //uint8_t *e = pkt_buf + pkt_len; - size_t off; - int i; + struct name_comp *comp; + uint8_t *p = pkt_buf; + // uint8_t *e = pkt_buf + pkt_len; + size_t off; + int i; - assert(answer != NULL); - assert(pkt_len >= 12); + assert(answer != NULL); + assert(pkt_len >= 12); - if (p == NULL) - return -1; + if (p == NULL) + return -1; - // this is an Answer - number of qns should be zero - assert(answer->num_qn == 0); + // this is an Answer - number of qns should be zero + assert(answer->num_qn == 0); - p = mdns_write_u16(p, answer->id); - p = mdns_write_u16(p, answer->flags); - p = mdns_write_u16(p, answer->num_qn); - p = mdns_write_u16(p, answer->num_ans_rr); - p = mdns_write_u16(p, answer->num_auth_rr); - p = mdns_write_u16(p, answer->num_add_rr); + p = mdns_write_u16(p, answer->id); + p = mdns_write_u16(p, answer->flags); + p = mdns_write_u16(p, answer->num_qn); + p = mdns_write_u16(p, answer->num_ans_rr); + p = mdns_write_u16(p, answer->num_auth_rr); + p = mdns_write_u16(p, answer->num_add_rr); - off = p - pkt_buf; + off = p - pkt_buf; - // allocate list for name compression - comp = malloc(sizeof(struct name_comp)); - if (comp == NULL) - return -1; - memset(comp, 0, sizeof(struct name_comp)); + // allocate list for name compression + comp = malloc(sizeof(struct name_comp)); + if (comp == NULL) + return -1; + memset(comp, 0, sizeof(struct name_comp)); - // dummy entry - comp->label = (uint8_t *) ""; - comp->pos = 0; + // dummy entry + comp->label = (uint8_t *)""; + comp->pos = 0; - // skip encoding of qn + // skip encoding of qn - struct rr_list *rr_set[] = { - answer->rr_ans, - answer->rr_auth, - answer->rr_add - }; + struct rr_list *rr_set[] = {answer->rr_ans, answer->rr_auth, answer->rr_add}; - // encode answer, authority and additional RRs - for (i = 0; i < sizeof(rr_set) / sizeof(rr_set[0]); i++) { - struct rr_list *rr = rr_set[i]; - for (; rr; rr = rr->next) { - size_t l = mdns_encode_rr(pkt_buf, pkt_len, off, rr->e, comp); - off += l; + // encode answer, authority and additional RRs + for (i = 0; i < sizeof(rr_set) / sizeof(rr_set[0]); i++) { + struct rr_list *rr = rr_set[i]; + for (; rr; rr = rr->next) { + size_t l = mdns_encode_rr(pkt_buf, pkt_len, off, rr->e, comp); + off += l; - if (off >= pkt_len) { - DEBUG_PRINTF("packet buffer too small\n"); - return -1; - } - } + if (off >= pkt_len) { + DEBUG_PRINTF("packet buffer too small\n"); + return -1; + } + } + } - } + // free name compression list + while (comp) { + struct name_comp *c = comp->next; + free(comp); + comp = c; + } - // free name compression list - while (comp) { - struct name_comp *c = comp->next; - free(comp); - comp = c; - } - - return off; + return off; } //******************************************************// @@ -1064,621 +1063,599 @@ size_t mdns_encode_pkt(struct mdns_pkt *answer, uint8_t *pkt_buf, size_t pkt_len #define PACKET_SIZE 65536 -#define SERVICES_DNS_SD_NLABEL \ - ((uint8_t *) "\x09_services\x07_dns-sd\x04_udp\x05local") +#define SERVICES_DNS_SD_NLABEL ((uint8_t *)"\x09_services\x07_dns-sd\x04_udp\x05local") struct mdnsd { - pthread_mutex_t data_lock; - int sockfd; - int notify_pipe[2]; - int stop_flag; + pthread_mutex_t data_lock; + int sockfd; + int notify_pipe[2]; + int stop_flag; - struct rr_group *group; - struct rr_list *announce; - struct rr_list *services; - uint8_t *hostname; + struct rr_group *group; + struct rr_list *announce; + struct rr_list *services; + uint8_t *hostname; }; struct mdns_service { - struct rr_list *entries; + struct rr_list *entries; }; ///////////////////////////////// - - static int create_recv_sock() { - int sd = socket(AF_INET, SOCK_DGRAM, 0); - if (sd < 0) { - log_message(LOG_ERR, "recv socket(): %m"); - return sd; - } + int sd = socket(AF_INET, SOCK_DGRAM, 0); + if (sd < 0) { + log_message(LOG_ERR, "recv socket(): %m"); + return sd; + } - int r = -1; + int r = -1; - int on = 1; - if ((r = setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on))) < 0) { - log_message(LOG_ERR, "recv setsockopt(SO_REUSEADDR): %m"); - return r; - } + int on = 1; + if ((r = setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on))) < 0) { + log_message(LOG_ERR, "recv setsockopt(SO_REUSEADDR): %m"); + return r; + } - /* bind to an address */ - struct sockaddr_in serveraddr; - memset(&serveraddr, 0, sizeof(serveraddr)); - serveraddr.sin_family = AF_INET; - serveraddr.sin_port = htons(MDNS_PORT); - serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); /* receive multicast */ - if ((r = bind(sd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) < 0) { - log_message(LOG_ERR, "recv bind(): %m"); - } + /* bind to an address */ + struct sockaddr_in serveraddr; + memset(&serveraddr, 0, sizeof(serveraddr)); + serveraddr.sin_family = AF_INET; + serveraddr.sin_port = htons(MDNS_PORT); + serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); /* receive multicast */ + if ((r = bind(sd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) < 0) { + log_message(LOG_ERR, "recv bind(): %m"); + } - // add membership to receiving socket - struct ip_mreq mreq; - memset(&mreq, 0, sizeof(struct ip_mreq)); - mreq.imr_interface.s_addr = htonl(INADDR_ANY); - mreq.imr_multiaddr.s_addr = inet_addr(MDNS_ADDR); - if ((r = setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &mreq, sizeof(mreq))) < 0) { - log_message(LOG_ERR, "recv setsockopt(IP_ADD_MEMBERSHIP): %m"); - return r; - } - - // enable loopback in case someone else needs the data - if ((r = setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &on, sizeof(on))) < 0) { - log_message(LOG_ERR, "recv setsockopt(IP_MULTICAST_LOOP): %m"); - return r; - } + // add membership to receiving socket + struct ip_mreq mreq; + memset(&mreq, 0, sizeof(struct ip_mreq)); + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + mreq.imr_multiaddr.s_addr = inet_addr(MDNS_ADDR); + if ((r = setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq))) < 0) { + log_message(LOG_ERR, "recv setsockopt(IP_ADD_MEMBERSHIP): %m"); + return r; + } + // enable loopback in case someone else needs the data + if ((r = setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&on, sizeof(on))) < 0) { + log_message(LOG_ERR, "recv setsockopt(IP_MULTICAST_LOOP): %m"); + return r; + } #ifdef IP_PKTINFO - if ((r = setsockopt(sd, SOL_IP, IP_PKTINFO, (char *) &on, sizeof(on))) < 0) { - log_message(LOG_ERR, "recv setsockopt(IP_PKTINFO): %m"); - return r; - } + if ((r = setsockopt(sd, SOL_IP, IP_PKTINFO, (char *)&on, sizeof(on))) < 0) { + log_message(LOG_ERR, "recv setsockopt(IP_PKTINFO): %m"); + return r; + } #endif - return sd; + return sd; } static ssize_t send_packet(int fd, const void *data, size_t len) { - static struct sockaddr_in toaddr; - if (toaddr.sin_family != AF_INET) { - memset(&toaddr, 0, sizeof(struct sockaddr_in)); - toaddr.sin_family = AF_INET; - toaddr.sin_port = htons(MDNS_PORT); - toaddr.sin_addr.s_addr = inet_addr(MDNS_ADDR); - } + static struct sockaddr_in toaddr; + if (toaddr.sin_family != AF_INET) { + memset(&toaddr, 0, sizeof(struct sockaddr_in)); + toaddr.sin_family = AF_INET; + toaddr.sin_port = htons(MDNS_PORT); + toaddr.sin_addr.s_addr = inet_addr(MDNS_ADDR); + } - return sendto(fd, data, len, 0, (struct sockaddr *) &toaddr, sizeof(struct sockaddr_in)); + return sendto(fd, data, len, 0, (struct sockaddr *)&toaddr, sizeof(struct sockaddr_in)); } - // populate the specified list which matches the RR name and type // type can be RR_ANY, which populates all entries EXCEPT RR_NSEC -static int populate_answers(struct mdnsd *svr, struct rr_list **rr_head, uint8_t *name, enum rr_type type) { - int num_ans = 0; +static int populate_answers(struct mdnsd *svr, struct rr_list **rr_head, uint8_t *name, + enum rr_type type) { + int num_ans = 0; - // check if we have the records - pthread_mutex_lock(&svr->data_lock); - struct rr_group *ans_grp = rr_group_find(svr->group, name); - if (ans_grp == NULL) { - pthread_mutex_unlock(&svr->data_lock); - return num_ans; - } + // check if we have the records + pthread_mutex_lock(&svr->data_lock); + struct rr_group *ans_grp = rr_group_find(svr->group, name); + if (ans_grp == NULL) { + pthread_mutex_unlock(&svr->data_lock); + return num_ans; + } - // decide which records should go into answers - struct rr_list *n = ans_grp->rr; - for (; n; n = n->next) { - // exclude NSEC for RR_ANY - if (type == RR_ANY && n->e->type == RR_NSEC) - continue; + // decide which records should go into answers + struct rr_list *n = ans_grp->rr; + for (; n; n = n->next) { + // exclude NSEC for RR_ANY + if (type == RR_ANY && n->e->type == RR_NSEC) + continue; - if ((type == n->e->type || type == RR_ANY) && cmp_nlabel(name, n->e->name) == 0) { - num_ans += rr_list_append(rr_head, n->e); - } - } + if ((type == n->e->type || type == RR_ANY) && cmp_nlabel(name, n->e->name) == 0) { + num_ans += rr_list_append(rr_head, n->e); + } + } - pthread_mutex_unlock(&svr->data_lock); + pthread_mutex_unlock(&svr->data_lock); - return num_ans; + return num_ans; } // given a list of RRs, look up related records and add them static void add_related_rr(struct mdnsd *svr, struct rr_list *list, struct mdns_pkt *reply) { - for (; list; list = list->next) { - struct rr_entry *ans = list->e; + for (; list; list = list->next) { + struct rr_entry *ans = list->e; - switch (ans->type) { - case RR_PTR: - // target host A, AAAA records - reply->num_add_rr += populate_answers(svr, &reply->rr_add, - MDNS_RR_GET_PTR_NAME(ans), RR_ANY); - break; + switch (ans->type) { + case RR_PTR: + // target host A, AAAA records + reply->num_add_rr += populate_answers(svr, &reply->rr_add, MDNS_RR_GET_PTR_NAME(ans), RR_ANY); + break; - case RR_SRV: - // target host A, AAAA records - reply->num_add_rr += populate_answers(svr, &reply->rr_add, - ans->data.SRV.target, RR_ANY); + case RR_SRV: + // target host A, AAAA records + reply->num_add_rr += populate_answers(svr, &reply->rr_add, ans->data.SRV.target, RR_ANY); - // perhaps TXT records of the same name? - // if we use RR_ANY, we risk pulling in the same RR_SRV - reply->num_add_rr += populate_answers(svr, &reply->rr_add, - ans->name, RR_TXT); - break; + // perhaps TXT records of the same name? + // if we use RR_ANY, we risk pulling in the same RR_SRV + reply->num_add_rr += populate_answers(svr, &reply->rr_add, ans->name, RR_TXT); + break; - case RR_A: - case RR_AAAA: - reply->num_add_rr += populate_answers(svr, &reply->rr_add, - ans->name, RR_NSEC); - break; + case RR_A: + case RR_AAAA: + reply->num_add_rr += populate_answers(svr, &reply->rr_add, ans->name, RR_NSEC); + break; - default: - // nothing to add - break; - } - } + default: + // nothing to add + break; + } + } } // creates an announce packet given the type name PTR static void announce_srv(struct mdnsd *svr, struct mdns_pkt *reply, uint8_t *name) { - mdns_init_reply(reply, 0); + mdns_init_reply(reply, 0); - reply->num_ans_rr += populate_answers(svr, &reply->rr_ans, name, RR_PTR); + reply->num_ans_rr += populate_answers(svr, &reply->rr_ans, name, RR_PTR); - // remember to add the services dns-sd PTR too - reply->num_ans_rr += populate_answers(svr, &reply->rr_ans, - SERVICES_DNS_SD_NLABEL, RR_PTR); + // remember to add the services dns-sd PTR too + reply->num_ans_rr += populate_answers(svr, &reply->rr_ans, SERVICES_DNS_SD_NLABEL, RR_PTR); - // see if we can match additional records for answers - add_related_rr(svr, reply->rr_ans, reply); + // see if we can match additional records for answers + add_related_rr(svr, reply->rr_ans, reply); - // additional records for additional records - add_related_rr(svr, reply->rr_add, reply); + // additional records for additional records + add_related_rr(svr, reply->rr_add, reply); } // processes the incoming MDNS packet // returns >0 if processed, 0 otherwise static int process_mdns_pkt(struct mdnsd *svr, struct mdns_pkt *pkt, struct mdns_pkt *reply) { - int i; + int i; - assert(pkt != NULL); + assert(pkt != NULL); - // is it standard query? - if ((pkt->flags & MDNS_FLAG_RESP) == 0 && - MDNS_FLAG_GET_OPCODE(pkt->flags) == 0) { - mdns_init_reply(reply, pkt->id); + // is it standard query? + if ((pkt->flags & MDNS_FLAG_RESP) == 0 && MDNS_FLAG_GET_OPCODE(pkt->flags) == 0) { + mdns_init_reply(reply, pkt->id); - DEBUG_PRINTF("flags = %04x, qn = %d, ans = %d, add = %d\n", - pkt->flags, - pkt->num_qn, - pkt->num_ans_rr, - pkt->num_add_rr); + DEBUG_PRINTF("flags = %04x, qn = %d, ans = %d, add = %d\n", pkt->flags, pkt->num_qn, + pkt->num_ans_rr, pkt->num_add_rr); - // loop through questions - struct rr_list *qnl = pkt->rr_qn; - for (i = 0; i < pkt->num_qn; i++, qnl = qnl->next) { - struct rr_entry *qn = qnl->e; - int num_ans_added = 0; + // loop through questions + struct rr_list *qnl = pkt->rr_qn; + for (i = 0; i < pkt->num_qn; i++, qnl = qnl->next) { + struct rr_entry *qn = qnl->e; + int num_ans_added = 0; - char *namestr = nlabel_to_str(qn->name); - DEBUG_PRINTF("qn #%d: type %s (%02x) %s - ", i, rr_get_type_name(qn->type), qn->type, namestr); - free(namestr); + char *namestr = nlabel_to_str(qn->name); + DEBUG_PRINTF("qn #%d: type %s (%02x) %s - ", i, rr_get_type_name(qn->type), qn->type, + namestr); + free(namestr); - // check if it's a unicast query - we ignore those - if (qn->unicast_query) { - DEBUG_PRINTF("skipping unicast query\n"); - continue; - } + // check if it's a unicast query - we ignore those + if (qn->unicast_query) { + DEBUG_PRINTF("skipping unicast query\n"); + continue; + } - num_ans_added = populate_answers(svr, &reply->rr_ans, qn->name, qn->type); - reply->num_ans_rr += num_ans_added; + num_ans_added = populate_answers(svr, &reply->rr_ans, qn->name, qn->type); + reply->num_ans_rr += num_ans_added; - DEBUG_PRINTF("added %d answers\n", num_ans_added); - } + DEBUG_PRINTF("added %d answers\n", num_ans_added); + } - // remove our replies if they were already in their answers - struct rr_list *ans = NULL, *prev_ans = NULL; - for (ans = reply->rr_ans; ans; ) { - struct rr_list *next_ans = ans->next; - struct rr_entry *known_ans = rr_entry_match(pkt->rr_ans, ans->e); + // remove our replies if they were already in their answers + struct rr_list *ans = NULL, *prev_ans = NULL; + for (ans = reply->rr_ans; ans;) { + struct rr_list *next_ans = ans->next; + struct rr_entry *known_ans = rr_entry_match(pkt->rr_ans, ans->e); - // discard answers that have at least half of the actual TTL - if (known_ans != NULL && known_ans->ttl >= ans->e->ttl / 2) { - char *namestr = nlabel_to_str(ans->e->name); - DEBUG_PRINTF("removing answer for %s\n", namestr); - free(namestr); + // discard answers that have at least half of the actual TTL + if (known_ans != NULL && known_ans->ttl >= ans->e->ttl / 2) { + char *namestr = nlabel_to_str(ans->e->name); + DEBUG_PRINTF("removing answer for %s\n", namestr); + free(namestr); - // check if list item is head - if (prev_ans == NULL) - reply->rr_ans = ans->next; - else - prev_ans->next = ans->next; - free(ans); + // check if list item is head + if (prev_ans == NULL) + reply->rr_ans = ans->next; + else + prev_ans->next = ans->next; + free(ans); - ans = prev_ans; + ans = prev_ans; - // adjust answer count - reply->num_ans_rr--; - } + // adjust answer count + reply->num_ans_rr--; + } - prev_ans = ans; - ans = next_ans; - } + prev_ans = ans; + ans = next_ans; + } + // see if we can match additional records for answers + add_related_rr(svr, reply->rr_ans, reply); - // see if we can match additional records for answers - add_related_rr(svr, reply->rr_ans, reply); + // additional records for additional records + add_related_rr(svr, reply->rr_add, reply); - // additional records for additional records - add_related_rr(svr, reply->rr_add, reply); + DEBUG_PRINTF("\n"); - DEBUG_PRINTF("\n"); + return reply->num_ans_rr; + } - return reply->num_ans_rr; - } - - return 0; + return 0; } int create_pipe(int handles[2]) { #ifdef _WIN32 - SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock == INVALID_SOCKET) { - return -1; - } - struct sockaddr_in serv_addr; - memset(&serv_addr, 0, sizeof(serv_addr)); - serv_addr.sin_family = AF_INET; - serv_addr.sin_port = htons(0); - serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - if (bind(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == SOCKET_ERROR) { - closesocket(sock); - return -1; - } - if (listen(sock, 1) == SOCKET_ERROR) { - closesocket(sock); - return -1; - } - int len = sizeof(serv_addr); - if (getsockname(sock, (SOCKADDR*)&serv_addr, &len) == SOCKET_ERROR) { - closesocket(sock); - return -1; - } - if ((handles[1] = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { - closesocket(sock); - return -1; - } - if (connect(handles[1], (struct sockaddr*)&serv_addr, len) == SOCKET_ERROR) { - closesocket(sock); - return -1; - } - if ((handles[0] = accept(sock, (struct sockaddr*)&serv_addr, &len)) == INVALID_SOCKET) { - closesocket((SOCKET)handles[1]); - handles[1] = INVALID_SOCKET; - closesocket(sock); - return -1; - } - closesocket(sock); - return 0; + SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == INVALID_SOCKET) { + return -1; + } + struct sockaddr_in serv_addr; + memset(&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(0); + serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + if (bind(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == SOCKET_ERROR) { + closesocket(sock); + return -1; + } + if (listen(sock, 1) == SOCKET_ERROR) { + closesocket(sock); + return -1; + } + int len = sizeof(serv_addr); + if (getsockname(sock, (SOCKADDR *)&serv_addr, &len) == SOCKET_ERROR) { + closesocket(sock); + return -1; + } + if ((handles[1] = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { + closesocket(sock); + return -1; + } + if (connect(handles[1], (struct sockaddr *)&serv_addr, len) == SOCKET_ERROR) { + closesocket(sock); + return -1; + } + if ((handles[0] = accept(sock, (struct sockaddr *)&serv_addr, &len)) == INVALID_SOCKET) { + closesocket((SOCKET)handles[1]); + handles[1] = INVALID_SOCKET; + closesocket(sock); + return -1; + } + closesocket(sock); + return 0; #else - return pipe(handles); + return pipe(handles); #endif } -int read_pipe(int s, char* buf, int len) { +int read_pipe(int s, char *buf, int len) { #ifdef _WIN32 - int ret = recv(s, buf, len, 0); - if (ret < 0 && WSAGetLastError() == WSAECONNRESET) { - ret = 0; - } - return ret; + int ret = recv(s, buf, len, 0); + if (ret < 0 && WSAGetLastError() == WSAECONNRESET) { + ret = 0; + } + return ret; #else - return read(s, buf, len); + return read(s, buf, len); #endif } -int write_pipe(int s, char* buf, int len) { +int write_pipe(int s, char *buf, int len) { #ifdef _WIN32 - return send(s, buf, len, 0); + return send(s, buf, len, 0); #else - return write(s, buf, len); + return write(s, buf, len); #endif } int close_pipe(int s) { #ifdef _WIN32 - return closesocket(s); + return closesocket(s); #else - return close(s); + return close(s); #endif } // main loop to receive, process and send out MDNS replies // also handles MDNS service announces static void main_loop(struct mdnsd *svr) { - fd_set sockfd_set; - int max_fd = svr->sockfd; - char notify_buf[2]; // buffer for reading of notify_pipe + fd_set sockfd_set; + int max_fd = svr->sockfd; + char notify_buf[2]; // buffer for reading of notify_pipe - void *pkt_buffer = malloc(PACKET_SIZE); + void *pkt_buffer = malloc(PACKET_SIZE); - if (svr->notify_pipe[0] > max_fd) - max_fd = svr->notify_pipe[0]; + if (svr->notify_pipe[0] > max_fd) + max_fd = svr->notify_pipe[0]; - struct mdns_pkt *mdns_reply = malloc(sizeof(struct mdns_pkt)); - memset(mdns_reply, 0, sizeof(struct mdns_pkt)); + struct mdns_pkt *mdns_reply = malloc(sizeof(struct mdns_pkt)); + memset(mdns_reply, 0, sizeof(struct mdns_pkt)); - while (! svr->stop_flag) { - FD_ZERO(&sockfd_set); - FD_SET(svr->sockfd, &sockfd_set); - FD_SET(svr->notify_pipe[0], &sockfd_set); - select(max_fd + 1, &sockfd_set, NULL, NULL, NULL); + while (!svr->stop_flag) { + FD_ZERO(&sockfd_set); + FD_SET(svr->sockfd, &sockfd_set); + FD_SET(svr->notify_pipe[0], &sockfd_set); + select(max_fd + 1, &sockfd_set, NULL, NULL, NULL); - if (FD_ISSET(svr->notify_pipe[0], &sockfd_set)) { - // flush the notify_pipe - read_pipe(svr->notify_pipe[0], (char*)¬ify_buf, 1); - } else if (FD_ISSET(svr->sockfd, &sockfd_set)) { - struct sockaddr_in fromaddr; - socklen_t sockaddr_size = sizeof(struct sockaddr_in); + if (FD_ISSET(svr->notify_pipe[0], &sockfd_set)) { + // flush the notify_pipe + read_pipe(svr->notify_pipe[0], (char *)¬ify_buf, 1); + } else if (FD_ISSET(svr->sockfd, &sockfd_set)) { + struct sockaddr_in fromaddr; + socklen_t sockaddr_size = sizeof(struct sockaddr_in); - ssize_t recvsize = recvfrom(svr->sockfd, pkt_buffer, PACKET_SIZE, 0, - (struct sockaddr *) &fromaddr, &sockaddr_size); - if (recvsize < 0) { - log_message(LOG_ERR, "recv(): %m"); - } + ssize_t recvsize = recvfrom(svr->sockfd, pkt_buffer, PACKET_SIZE, 0, + (struct sockaddr *)&fromaddr, &sockaddr_size); + if (recvsize < 0) { + log_message(LOG_ERR, "recv(): %m"); + } - DEBUG_PRINTF("data from=%s size=%ld\n", inet_ntoa(fromaddr.sin_addr), (long) recvsize); - struct mdns_pkt *mdns = mdns_parse_pkt(pkt_buffer, recvsize); - if (mdns != NULL) { - if (process_mdns_pkt(svr, mdns, mdns_reply)) { - size_t replylen = mdns_encode_pkt(mdns_reply, pkt_buffer, PACKET_SIZE); - send_packet(svr->sockfd, pkt_buffer, replylen); - } else if (mdns->num_qn == 0) { - DEBUG_PRINTF("(no questions in packet)\n\n"); - } - - mdns_pkt_destroy(mdns); - } - } - - // send out announces - while (1) { - struct rr_entry *ann_e = NULL; - - // extract from head of list - pthread_mutex_lock(&svr->data_lock); - if (svr->announce) - ann_e = rr_list_remove(&svr->announce, svr->announce->e); - pthread_mutex_unlock(&svr->data_lock); - - if (! ann_e) - break; - - char *namestr = nlabel_to_str(ann_e->name); - DEBUG_PRINTF("sending announce for %s\n", namestr); - free(namestr); - - announce_srv(svr, mdns_reply, ann_e->name); - - if (mdns_reply->num_ans_rr > 0) { - size_t replylen = mdns_encode_pkt(mdns_reply, pkt_buffer, PACKET_SIZE); - send_packet(svr->sockfd, pkt_buffer, replylen); - } - } + DEBUG_PRINTF("data from=%s size=%ld\n", inet_ntoa(fromaddr.sin_addr), (long)recvsize); + struct mdns_pkt *mdns = mdns_parse_pkt(pkt_buffer, recvsize); + if (mdns != NULL) { + if (process_mdns_pkt(svr, mdns, mdns_reply)) { + size_t replylen = mdns_encode_pkt(mdns_reply, pkt_buffer, PACKET_SIZE); + send_packet(svr->sockfd, pkt_buffer, replylen); + } else if (mdns->num_qn == 0) { + DEBUG_PRINTF("(no questions in packet)\n\n"); } - // main thread terminating. send out "goodbye packets" for services - mdns_init_reply(mdns_reply, 0); + mdns_pkt_destroy(mdns); + } + } - pthread_mutex_lock(&svr->data_lock); - struct rr_list *svc_le = svr->services; - for (; svc_le; svc_le = svc_le->next) { - // set TTL to zero - svc_le->e->ttl = 0; - mdns_reply->num_ans_rr += rr_list_append(&mdns_reply->rr_ans, svc_le->e); - } - pthread_mutex_unlock(&svr->data_lock); + // send out announces + while (1) { + struct rr_entry *ann_e = NULL; - // send out packet - if (mdns_reply->num_ans_rr > 0) { - size_t replylen = mdns_encode_pkt(mdns_reply, pkt_buffer, PACKET_SIZE); - send_packet(svr->sockfd, pkt_buffer, replylen); - } + // extract from head of list + pthread_mutex_lock(&svr->data_lock); + if (svr->announce) + ann_e = rr_list_remove(&svr->announce, svr->announce->e); + pthread_mutex_unlock(&svr->data_lock); - // destroy packet - mdns_init_reply(mdns_reply, 0); - free(mdns_reply); + if (!ann_e) + break; - free(pkt_buffer); + char *namestr = nlabel_to_str(ann_e->name); + DEBUG_PRINTF("sending announce for %s\n", namestr); + free(namestr); - close_pipe(svr->sockfd); + announce_srv(svr, mdns_reply, ann_e->name); - svr->stop_flag = 2; + if (mdns_reply->num_ans_rr > 0) { + size_t replylen = mdns_encode_pkt(mdns_reply, pkt_buffer, PACKET_SIZE); + send_packet(svr->sockfd, pkt_buffer, replylen); + } + } + } + + // main thread terminating. send out "goodbye packets" for services + mdns_init_reply(mdns_reply, 0); + + pthread_mutex_lock(&svr->data_lock); + struct rr_list *svc_le = svr->services; + for (; svc_le; svc_le = svc_le->next) { + // set TTL to zero + svc_le->e->ttl = 0; + mdns_reply->num_ans_rr += rr_list_append(&mdns_reply->rr_ans, svc_le->e); + } + pthread_mutex_unlock(&svr->data_lock); + + // send out packet + if (mdns_reply->num_ans_rr > 0) { + size_t replylen = mdns_encode_pkt(mdns_reply, pkt_buffer, PACKET_SIZE); + send_packet(svr->sockfd, pkt_buffer, replylen); + } + + // destroy packet + mdns_init_reply(mdns_reply, 0); + free(mdns_reply); + + free(pkt_buffer); + + close_pipe(svr->sockfd); + + svr->stop_flag = 2; } ///////////////////////////////////////////////////// - void mdnsd_set_hostname(struct mdnsd *svr, const char *hostname, uint32_t ip) { - struct rr_entry *a_e = NULL, - *nsec_e = NULL; + struct rr_entry *a_e = NULL, *nsec_e = NULL; - // currently can't be called twice - // dont ask me what happens if the IP changes - assert(svr->hostname == NULL); + // currently can't be called twice + // dont ask me what happens if the IP changes + assert(svr->hostname == NULL); - a_e = rr_create_a(create_nlabel(hostname), ip); // 120 seconds automatically + a_e = rr_create_a(create_nlabel(hostname), ip); // 120 seconds automatically - nsec_e = rr_create(create_nlabel(hostname), RR_NSEC); - nsec_e->ttl = DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME; // set to 120 seconds (default is 4500) - rr_set_nsec(nsec_e, RR_A); + nsec_e = rr_create(create_nlabel(hostname), RR_NSEC); + nsec_e->ttl = DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME; // set to 120 seconds (default is 4500) + rr_set_nsec(nsec_e, RR_A); - pthread_mutex_lock(&svr->data_lock); - svr->hostname = create_nlabel(hostname); - rr_group_add(&svr->group, a_e); - rr_group_add(&svr->group, nsec_e); - pthread_mutex_unlock(&svr->data_lock); + pthread_mutex_lock(&svr->data_lock); + svr->hostname = create_nlabel(hostname); + rr_group_add(&svr->group, a_e); + rr_group_add(&svr->group, nsec_e); + pthread_mutex_unlock(&svr->data_lock); } -void mdnsd_set_hostname_v6(struct mdnsd *svr, const char *hostname, struct in6_addr *addr) -{ - struct rr_entry *aaaa_e = NULL, - *nsec_e = NULL; +void mdnsd_set_hostname_v6(struct mdnsd *svr, const char *hostname, struct in6_addr *addr) { + struct rr_entry *aaaa_e = NULL, *nsec_e = NULL; - // currently can't be called twice - // dont ask me what happens if the IP changes - assert(svr->hostname == NULL); + // currently can't be called twice + // dont ask me what happens if the IP changes + assert(svr->hostname == NULL); - aaaa_e = rr_create_aaaa(create_nlabel(hostname), addr); // 120 seconds automatically + aaaa_e = rr_create_aaaa(create_nlabel(hostname), addr); // 120 seconds automatically - nsec_e = rr_create(create_nlabel(hostname), RR_NSEC); - nsec_e->ttl = DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME; // set to 120 seconds (default is 4500) - rr_set_nsec(nsec_e, RR_AAAA); + nsec_e = rr_create(create_nlabel(hostname), RR_NSEC); + nsec_e->ttl = DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME; // set to 120 seconds (default is 4500) + rr_set_nsec(nsec_e, RR_AAAA); - pthread_mutex_lock(&svr->data_lock); - svr->hostname = create_nlabel(hostname); - rr_group_add(&svr->group, aaaa_e); - rr_group_add(&svr->group, nsec_e); - pthread_mutex_unlock(&svr->data_lock); + pthread_mutex_lock(&svr->data_lock); + svr->hostname = create_nlabel(hostname); + rr_group_add(&svr->group, aaaa_e); + rr_group_add(&svr->group, nsec_e); + pthread_mutex_unlock(&svr->data_lock); } void mdnsd_add_rr(struct mdnsd *svr, struct rr_entry *rr) { - pthread_mutex_lock(&svr->data_lock); - rr_group_add(&svr->group, rr); - pthread_mutex_unlock(&svr->data_lock); + pthread_mutex_lock(&svr->data_lock); + rr_group_add(&svr->group, rr); + pthread_mutex_unlock(&svr->data_lock); } struct mdns_service *mdnsd_register_svc(struct mdnsd *svr, const char *instance_name, - const char *type, uint16_t port, const char *hostname, const char *txt[]) { - struct rr_entry *txt_e = NULL, - *srv_e = NULL, - *ptr_e = NULL, - *bptr_e = NULL; - uint8_t *target; - uint8_t *inst_nlabel, *type_nlabel, *nlabel; - struct mdns_service *service = malloc(sizeof(struct mdns_service)); - memset(service, 0, sizeof(struct mdns_service)); + const char *type, uint16_t port, const char *hostname, + const char *txt[]) { + struct rr_entry *txt_e = NULL, *srv_e = NULL, *ptr_e = NULL, *bptr_e = NULL; + uint8_t *target; + uint8_t *inst_nlabel, *type_nlabel, *nlabel; + struct mdns_service *service = malloc(sizeof(struct mdns_service)); + memset(service, 0, sizeof(struct mdns_service)); - // combine service name - type_nlabel = create_nlabel(type); - inst_nlabel = create_label(instance_name); - nlabel = join_nlabel(inst_nlabel, type_nlabel); + // combine service name + type_nlabel = create_nlabel(type); + inst_nlabel = create_label(instance_name); + nlabel = join_nlabel(inst_nlabel, type_nlabel); - // create TXT record - if (txt && *txt) { - txt_e = rr_create(dup_nlabel(nlabel), RR_TXT); // automatically 4500 seconds - rr_list_append(&service->entries, txt_e); + // create TXT record + if (txt && *txt) { + txt_e = rr_create(dup_nlabel(nlabel), RR_TXT); // automatically 4500 seconds + rr_list_append(&service->entries, txt_e); - // add TXTs - for (; *txt; txt++) - rr_add_txt(txt_e, *txt); - } + // add TXTs + for (; *txt; txt++) + rr_add_txt(txt_e, *txt); + } - // create SRV record - assert(hostname || svr->hostname); // either one as target - target = hostname ? - create_nlabel(hostname) : - dup_nlabel(svr->hostname); + // create SRV record + assert(hostname || svr->hostname); // either one as target + target = hostname ? create_nlabel(hostname) : dup_nlabel(svr->hostname); - srv_e = rr_create_srv(dup_nlabel(nlabel), port, target); // automatically 4500 seconds - rr_list_append(&service->entries, srv_e); + srv_e = rr_create_srv(dup_nlabel(nlabel), port, target); // automatically 4500 seconds + rr_list_append(&service->entries, srv_e); - // create PTR record for type - ptr_e = rr_create_ptr(type_nlabel, srv_e); // automatically 4500 seconds + // create PTR record for type + ptr_e = rr_create_ptr(type_nlabel, srv_e); // automatically 4500 seconds - // create services PTR record for type - // this enables the type to show up as a "service" - bptr_e = rr_create_ptr(dup_nlabel(SERVICES_DNS_SD_NLABEL), ptr_e); // automatically 4500 seconds + // create services PTR record for type + // this enables the type to show up as a "service" + bptr_e = rr_create_ptr(dup_nlabel(SERVICES_DNS_SD_NLABEL), ptr_e); // automatically 4500 seconds - // modify lists here - pthread_mutex_lock(&svr->data_lock); + // modify lists here + pthread_mutex_lock(&svr->data_lock); - if (txt_e) - rr_group_add(&svr->group, txt_e); - rr_group_add(&svr->group, srv_e); - rr_group_add(&svr->group, ptr_e); - rr_group_add(&svr->group, bptr_e); + if (txt_e) + rr_group_add(&svr->group, txt_e); + rr_group_add(&svr->group, srv_e); + rr_group_add(&svr->group, ptr_e); + rr_group_add(&svr->group, bptr_e); - // append PTR entry to announce list - rr_list_append(&svr->announce, ptr_e); - rr_list_append(&svr->services, ptr_e); + // append PTR entry to announce list + rr_list_append(&svr->announce, ptr_e); + rr_list_append(&svr->services, ptr_e); - pthread_mutex_unlock(&svr->data_lock); + pthread_mutex_unlock(&svr->data_lock); - // don't free type_nlabel - it's with the PTR record - free(nlabel); - free(inst_nlabel); + // don't free type_nlabel - it's with the PTR record + free(nlabel); + free(inst_nlabel); - // notify server - write_pipe(svr->notify_pipe[1], ".", 1); + // notify server + write_pipe(svr->notify_pipe[1], ".", 1); - return service; + return service; } void mdns_service_destroy(struct mdns_service *srv) { - assert(srv != NULL); - rr_list_destroy(srv->entries, 0); - free(srv); + assert(srv != NULL); + rr_list_destroy(srv->entries, 0); + free(srv); } struct mdnsd *mdnsd_start() { - pthread_t tid; - pthread_attr_t attr; + pthread_t tid; + pthread_attr_t attr; - struct mdnsd *server = malloc(sizeof(struct mdnsd)); - memset(server, 0, sizeof(struct mdnsd)); + struct mdnsd *server = malloc(sizeof(struct mdnsd)); + memset(server, 0, sizeof(struct mdnsd)); - if (create_pipe(server->notify_pipe) != 0) { - log_message(LOG_ERR, "pipe(): %m\n"); - free(server); - return NULL; - } + if (create_pipe(server->notify_pipe) != 0) { + log_message(LOG_ERR, "pipe(): %m\n"); + free(server); + return NULL; + } - server->sockfd = create_recv_sock(); - if (server->sockfd < 0) { - log_message(LOG_ERR, "unable to create recv socket"); - free(server); - return NULL; - } + server->sockfd = create_recv_sock(); + if (server->sockfd < 0) { + log_message(LOG_ERR, "unable to create recv socket"); + free(server); + return NULL; + } - pthread_mutex_init(&server->data_lock, NULL); + pthread_mutex_init(&server->data_lock, NULL); - // init thread - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + // init thread + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - if (pthread_create(&tid, &attr, (void *(*)(void *)) main_loop, (void *) server) != 0) { - pthread_mutex_destroy(&server->data_lock); - free(server); - return NULL; - } + if (pthread_create(&tid, &attr, (void *(*)(void *))main_loop, (void *)server) != 0) { + pthread_mutex_destroy(&server->data_lock); + free(server); + return NULL; + } - return server; + return server; } void mdnsd_stop(struct mdnsd *s) { - assert(s != NULL); + assert(s != NULL); - struct timeval tv = { - .tv_sec = 0, - .tv_usec = 500 * 1000, - }; + struct timeval tv = { + .tv_sec = 0, .tv_usec = 500 * 1000, + }; - s->stop_flag = 1; - write_pipe(s->notify_pipe[1], ".", 1); + s->stop_flag = 1; + write_pipe(s->notify_pipe[1], ".", 1); - while (s->stop_flag != 2) - select(0, NULL, NULL, NULL, &tv); + while (s->stop_flag != 2) + select(0, NULL, NULL, NULL, &tv); - close_pipe(s->notify_pipe[0]); - close_pipe(s->notify_pipe[1]); + close_pipe(s->notify_pipe[0]); + close_pipe(s->notify_pipe[1]); - pthread_mutex_destroy(&s->data_lock); - rr_group_destroy(s->group); - rr_list_destroy(s->announce, 0); - rr_list_destroy(s->services, 0); + pthread_mutex_destroy(&s->data_lock); + rr_group_destroy(s->group); + rr_list_destroy(s->announce, 0); + rr_list_destroy(s->services, 0); - if (s->hostname) - free(s->hostname); + if (s->hostname) + free(s->hostname); - free(s); + free(s); } diff --git a/tinysvcmdns.h b/tinysvcmdns.h index 98f26fac..c32c4430 100644 --- a/tinysvcmdns.h +++ b/tinysvcmdns.h @@ -45,120 +45,118 @@ #include #endif -#define MALLOC_ZERO_STRUCT(x, type) \ - x = malloc(sizeof(struct type)); \ - memset(x, 0, sizeof(struct type)); - -#define DECL_MALLOC_ZERO_STRUCT(x, type) \ - struct type * MALLOC_ZERO_STRUCT(x, type) - +#define MALLOC_ZERO_STRUCT(x, type) \ + x = malloc(sizeof(struct type)); \ + memset(x, 0, sizeof(struct type)); +#define DECL_MALLOC_ZERO_STRUCT(x, type) struct type *MALLOC_ZERO_STRUCT(x, type) struct rr_data_srv { - uint16_t priority; - uint16_t weight; - uint16_t port; - uint8_t *target; // host + uint16_t priority; + uint16_t weight; + uint16_t port; + uint8_t *target; // host }; struct rr_data_txt { - struct rr_data_txt *next; - uint8_t *txt; + struct rr_data_txt *next; + uint8_t *txt; }; struct rr_data_nsec { - //uint8_t *name; // same as record + // uint8_t *name; // same as record - // NSEC occupies the 47th bit, 5 bytes - //uint8_t bitmap_len; // = 5 - uint8_t bitmap[5]; // network order: first byte contains LSB + // NSEC occupies the 47th bit, 5 bytes + // uint8_t bitmap_len; // = 5 + uint8_t bitmap[5]; // network order: first byte contains LSB }; struct rr_data_ptr { - uint8_t *name; // NULL if entry is to be used - struct rr_entry *entry; + uint8_t *name; // NULL if entry is to be used + struct rr_entry *entry; }; struct rr_data_a { - uint32_t addr; + uint32_t addr; }; struct rr_data_aaaa { - struct in6_addr *addr; + struct in6_addr *addr; }; struct rr_entry { - uint8_t *name; + uint8_t *name; - enum rr_type { - RR_A = 0x01, - RR_PTR = 0x0C, - RR_TXT = 0x10, - RR_AAAA = 0x1C, - RR_SRV = 0x21, - RR_NSEC = 0x2F, - RR_ANY = 0xFF, - } type; + enum rr_type { + RR_A = 0x01, + RR_PTR = 0x0C, + RR_TXT = 0x10, + RR_AAAA = 0x1C, + RR_SRV = 0x21, + RR_NSEC = 0x2F, + RR_ANY = 0xFF, + } type; - uint32_t ttl; + uint32_t ttl; - // for use in Questions only - char unicast_query; + // for use in Questions only + char unicast_query; - // for use in Answers only - char cache_flush; + // for use in Answers only + char cache_flush; - uint16_t rr_class; + uint16_t rr_class; - // RR data - union { - struct rr_data_nsec NSEC; - struct rr_data_srv SRV; - struct rr_data_txt TXT; - struct rr_data_ptr PTR; - struct rr_data_a A; - struct rr_data_aaaa AAAA; - } data; + // RR data + union { + struct rr_data_nsec NSEC; + struct rr_data_srv SRV; + struct rr_data_txt TXT; + struct rr_data_ptr PTR; + struct rr_data_a A; + struct rr_data_aaaa AAAA; + } data; }; struct rr_list { - struct rr_entry *e; - struct rr_list *next; + struct rr_entry *e; + struct rr_list *next; }; struct rr_group { - uint8_t *name; + uint8_t *name; - struct rr_list *rr; + struct rr_list *rr; - struct rr_group *next; + struct rr_group *next; }; -#define MDNS_FLAG_RESP (1 << 15) // Query=0 / Response=1 -#define MDNS_FLAG_AA (1 << 10) // Authoritative -#define MDNS_FLAG_TC (1 << 9) // TrunCation -#define MDNS_FLAG_RD (1 << 8) // Recursion Desired -#define MDNS_FLAG_RA (1 << 7) // Recursion Available -#define MDNS_FLAG_Z (1 << 6) // Reserved (zero) +#define MDNS_FLAG_RESP (1 << 15) // Query=0 / Response=1 +#define MDNS_FLAG_AA (1 << 10) // Authoritative +#define MDNS_FLAG_TC (1 << 9) // TrunCation +#define MDNS_FLAG_RD (1 << 8) // Recursion Desired +#define MDNS_FLAG_RA (1 << 7) // Recursion Available +#define MDNS_FLAG_Z (1 << 6) // Reserved (zero) -#define MDNS_FLAG_GET_RCODE(x) (x & 0x0F) +#define MDNS_FLAG_GET_RCODE(x) (x & 0x0F) #define MDNS_FLAG_GET_OPCODE(x) ((x >> 11) & 0x0F) // gets the PTR target name, either from "name" member or "entry" member -#define MDNS_RR_GET_PTR_NAME(rr) (rr->data.PTR.name != NULL ? rr->data.PTR.name : rr->data.PTR.entry->name) +#define MDNS_RR_GET_PTR_NAME(rr) \ + (rr->data.PTR.name != NULL ? rr->data.PTR.name : rr->data.PTR.entry->name) struct mdns_pkt { - uint16_t id; // transaction ID - uint16_t flags; - uint16_t num_qn; - uint16_t num_ans_rr; - uint16_t num_auth_rr; - uint16_t num_add_rr; + uint16_t id; // transaction ID + uint16_t flags; + uint16_t num_qn; + uint16_t num_ans_rr; + uint16_t num_auth_rr; + uint16_t num_add_rr; - struct rr_list *rr_qn; // questions - struct rr_list *rr_ans; // answer RRs - struct rr_list *rr_auth; // authority RRs - struct rr_list *rr_add; // additional RRs + struct rr_list *rr_qn; // questions + struct rr_list *rr_ans; // answer RRs + struct rr_list *rr_auth; // authority RRs + struct rr_list *rr_add; // additional RRs }; struct mdns_pkt *mdns_parse_pkt(uint8_t *pkt_buf, size_t pkt_len); @@ -197,7 +195,7 @@ uint8_t *join_nlabel(const uint8_t *n1, const uint8_t *n2); // compares 2 names static inline int cmp_nlabel(const uint8_t *L1, const uint8_t *L2) { - return strcmp((char *) L1, (char *) L2); + return strcmp((char *)L1, (char *)L2); } //******************************************************// @@ -225,10 +223,10 @@ void mdnsd_add_rr(struct mdnsd *svr, struct rr_entry *rr); // registers a service with the MDNS responder instance struct mdns_service *mdnsd_register_svc(struct mdnsd *svr, const char *instance_name, - const char *type, uint16_t port, const char *hostname, const char *txt[]); + const char *type, uint16_t port, const char *hostname, + const char *txt[]); // destroys the mdns_service struct returned by mdnsd_register_svc() void mdns_service_destroy(struct mdns_service *srv); - #endif // _TINYSVCMDNS_H From 38281dd1c597dae6fde2c7b3a1fc24e40b930bc0 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 2 Jun 2015 11:59:56 +0000 Subject: [PATCH 074/220] Add new auidio back end -- to stdout. --- Makefile.am | 2 +- audio.c | 4 +- audio_pipe.c | 27 ++++++++++++- audio_stdout.c | 101 +++++++++++++++++++++++++++++++++++++++++++++++++ player.c | 5 +++ rtsp.c | 3 -- shairport.c | 63 +++++++++++++++--------------- 7 files changed, 165 insertions(+), 40 deletions(-) create mode 100644 audio_stdout.c diff --git a/Makefile.am b/Makefile.am index c016d98a..203fffbb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,7 +1,7 @@ SUBDIRS = man bin_PROGRAMS = shairport-sync -shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c audio_dummy.c audio_pipe.c +shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c audio_dummy.c audio_pipe.c audio_stdout.c AM_CFLAGS = -Wno-multichar diff --git a/audio.c b/audio.c index 9887531f..e8f56718 100644 --- a/audio.c +++ b/audio.c @@ -41,7 +41,7 @@ extern audio_output audio_pulse; #ifdef CONFIG_ALSA extern audio_output audio_alsa; #endif -extern audio_output audio_dummy, audio_pipe; +extern audio_output audio_dummy, audio_pipe, audio_stdout; static audio_output *outputs[] = { #ifdef CONFIG_SNDIO @@ -56,7 +56,7 @@ static audio_output *outputs[] = { #ifdef CONFIG_AO &audio_ao, #endif - &audio_dummy, &audio_pipe, NULL}; + &audio_dummy, &audio_pipe, &audio_stdout, NULL}; audio_output *audio_get_output(char *name) { audio_output **out; diff --git a/audio_pipe.c b/audio_pipe.c index 12b33360..b1e5e09c 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -53,6 +53,9 @@ static void stop(void) { } static int init(int argc, char **argv) { + const char *str; + int value; + config.audio_backend_buffer_desired_length = 44100; // one second. config.audio_backend_latency_offset = 0; @@ -62,6 +65,26 @@ static int init(int argc, char **argv) { if (config_lookup_string(config.cfg, "pipe.name", &str)) { pipename = (char *)str; } + + /* Get the desired buffer size setting. */ + if (config_lookup_int(config.cfg, "pipe.audio_backend_buffer_desired_length", &value)) { + if ((value < 0) || (value > 66150)) + die("Invalid pipe audio backend buffer desired length \"%sd\". It should be between 0 and " + "66150, default is 44100", + value); + else + config.audio_backend_buffer_desired_length = value; + } + + /* Get the latency offset. */ + if (config_lookup_int(config.cfg, "pipe.audio_backend_latency_offset", &value)) { + if ((value < -22050) || (value > 22050)) + die("Invalid pipe audio backend buffer latency offset \"%sd\". It should be between -22050 " + "and +22050, default is 0", + value); + else + config.audio_backend_latency_offset = value; + } } if ((pipename == NULL) && (argc != 1)) @@ -73,7 +96,7 @@ static int init(int argc, char **argv) { // here, create the pipe if (strcasecmp(pipename, "STDOUT") != 0) if (mkfifo(pipename, 0644) && errno != EEXIST) - die("Could not create metadata FIFO %s", pipename); + die("Could not create output pipe \"%s\"", pipename); debug(1, "Pipename is \"%s\"", pipename); @@ -90,7 +113,7 @@ static void deinit(void) { } static void help(void) { - printf(" pipe takes 1 argument: the name of the FIFO to write to, which can be \"stdout\".\n"); + printf(" pipe takes 1 argument: the name of the FIFO to write to.\n"); } audio_output audio_pipe = {.name = "pipe", diff --git a/audio_stdout.c b/audio_stdout.c new file mode 100644 index 00000000..481a8095 --- /dev/null +++ b/audio_stdout.c @@ -0,0 +1,101 @@ +/* + * pipe output driver. This file is part of Shairport. + * Copyright (c) James Laird 2013 + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include "common.h" +#include "audio.h" + +static int fd = -1; + +static void start(int sample_rate) { + fd = STDOUT_FILENO; +} + +static void play(short buf[], int samples) { + int ignore = write(fd, buf, samples * 4); +} + +static void stop(void) { + // don't close stdout +} + +static int init(int argc, char **argv) { + const char *str; + int value; + + config.audio_backend_buffer_desired_length = 44100; // one second. + config.audio_backend_latency_offset = 0; + + if (config.cfg != NULL) { + /* Get the desired buffer size setting. */ + debug(1,"Get buffer length"); + if (config_lookup_int(config.cfg, "stdout.audio_backend_buffer_desired_length", &value)) { + if ((value < 0) || (value > 132300)) + die("Invalid stdout audio backend buffer desired length \"%d\". It should be between 0 and 132300, default is 44100", + value); + else + config.audio_backend_buffer_desired_length = value; + } + + /* Get the latency offset. */ + debug(1,"Get latency"); + if (config_lookup_int(config.cfg, "stdout.audio_backend_latency_offset", &value)) { + debug(1,"Got a latency value of %d.",value); + if ((value < -66150) || (value > 66150)) + die("Invalid stdout audio backend buffer latency offset \"%d\". It should be between -66150 and +66150, default is 0", + value); + else + config.audio_backend_latency_offset = value; + } + } + debug(1,"buffer: %d, latency: %d.",config.audio_backend_buffer_desired_length,config.audio_backend_latency_offset); + return 0; +} + +static void deinit(void) { + // don't close stdout +} + +static void help(void) { + printf(" stdout takes no arguments\n"); +} + +audio_output audio_stdout = {.name = "stdout", + .help = &help, + .init = &init, + .deinit = &deinit, + .start = &start, + .stop = &stop, + .flush = NULL, + .delay = NULL, + .play = &play, + .volume = NULL, + .parameters = NULL}; diff --git a/player.c b/player.c index 6d28d785..65fa6542 100644 --- a/player.c +++ b/player.c @@ -855,6 +855,11 @@ typedef struct stats { // statistics for running averages } stats_t; static void *player_thread_func(void *arg) { + // check that there are enough buffers to accommodate the desired latency and the latency offset + + int maximum_latency = config.latency+config.audio_backend_latency_offset; + if ((maximum_latency+(352-1))/352 + 10 > BUFFER_FRAMES) + die("Not enough buffers available for a total latency of %d frames. A maximum of %d 352-frame packets may be accommodated.",maximum_latency,BUFFER_FRAMES); connection_state_to_output = get_requested_connection_state_to_output(); // this is about half a minute #define trend_interval 3758 diff --git a/rtsp.c b/rtsp.c index 0f9b1226..f25ba57b 100644 --- a/rtsp.c +++ b/rtsp.c @@ -972,9 +972,6 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) return; remaining_data += towrite_count; remaining_count -= towrite_count; - // ret = write(fd,"\r\n",2); - // if (ret<0) - // return; } snprintf(thestring, 1024, "\n"); ret = non_blocking_write(fd, thestring, strlen(thestring)); diff --git a/shairport.c b/shairport.c index 5ab2638c..97cd0bbd 100644 --- a/shairport.c +++ b/shairport.c @@ -497,38 +497,6 @@ int parse_options(int argc, char **argv) { } #endif - - /* Print out options */ - - debug(2, "statistics_requester status is %d.", config.statistics_requested); - debug(2, "daemon status is %d.", config.daemonise); - debug(2, "rtsp listening port is %d.", config.port); - debug(2, "Shairport Sync player name is \"%s\".", config.apname); - debug(2, "Audio Output name is \"%s\".", config.output_name); - debug(2, "on-start action is \"%s\".", config.cmd_start); - debug(2, "on-stop action is \"%s\".", config.cmd_stop); - debug(2, "wait-cmd status is %d.", config.cmd_blocking); - debug(2, "mdns backend \"%s\".", config.mdns_name); - debug(2, "userSuppliedLatency is %d.", config.userSuppliedLatency); - debug(2, "AirPlayLatency is %d.", config.AirPlayLatency); - debug(2, "iTunesLatency is %d.", config.iTunesLatency); - debug(2, "forkedDaapdLatency is %d.", config.ForkedDaapdLatency); - debug(2, "stuffing option is \"%d\".", config.packet_stuffing); - debug(2, "resync time is %d.", config.resyncthreshold); - debug(2, "allow a session to be interrupted: %d.", config.allow_session_interruption); - debug(2, "busy timeout time is %d.", config.timeout); - debug(2, "tolerance is %d frames.", config.tolerance); - debug(2, "password is \"%s\".", config.password); - debug(2, "ignore_volume_control is %d.", config.ignore_volume_control); - debug(2, "audio backend desired buffer length is %d.", - config.audio_backend_buffer_desired_length); - debug(2, "audio backend latency offset is %d.", config.audio_backend_latency_offset); -#ifdef CONFIG_METADATA - debug(2, "metdata enabled is %d.", config.metadata_enabled); - debug(2, "metadata pipename is \"%s\".", config.metadata_pipename); - debug(2, "get-coverart is %d.", config.get_coverart); -#endif - return optind + 1; } @@ -797,6 +765,37 @@ int main(int argc, char **argv) { config.output->init(argc - audio_arg, argv + audio_arg); daemon_log(LOG_NOTICE, "startup"); + + /* Print out options */ + + debug(2, "statistics_requester status is %d.", config.statistics_requested); + debug(2, "daemon status is %d.", config.daemonise); + debug(2, "rtsp listening port is %d.", config.port); + debug(2, "Shairport Sync player name is \"%s\".", config.apname); + debug(2, "Audio Output name is \"%s\".", config.output_name); + debug(2, "on-start action is \"%s\".", config.cmd_start); + debug(2, "on-stop action is \"%s\".", config.cmd_stop); + debug(2, "wait-cmd status is %d.", config.cmd_blocking); + debug(2, "mdns backend \"%s\".", config.mdns_name); + debug(2, "userSuppliedLatency is %d.", config.userSuppliedLatency); + debug(2, "AirPlayLatency is %d.", config.AirPlayLatency); + debug(2, "iTunesLatency is %d.", config.iTunesLatency); + debug(2, "forkedDaapdLatency is %d.", config.ForkedDaapdLatency); + debug(2, "stuffing option is \"%d\".", config.packet_stuffing); + debug(2, "resync time is %d.", config.resyncthreshold); + debug(2, "allow a session to be interrupted: %d.", config.allow_session_interruption); + debug(2, "busy timeout time is %d.", config.timeout); + debug(2, "tolerance is %d frames.", config.tolerance); + debug(2, "password is \"%s\".", config.password); + debug(2, "ignore_volume_control is %d.", config.ignore_volume_control); + debug(2, "audio backend desired buffer length is %d.", + config.audio_backend_buffer_desired_length); + debug(2, "audio backend latency offset is %d.", config.audio_backend_latency_offset); +#ifdef CONFIG_METADATA + debug(2, "metdata enabled is %d.", config.metadata_enabled); + debug(2, "metadata pipename is \"%s\".", config.metadata_pipename); + debug(2, "get-coverart is %d.", config.get_coverart); +#endif uint8_t ap_md5[16]; From c65211a205f88c9b3c7115d60451801049962deb Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 2 Jun 2015 12:51:42 +0000 Subject: [PATCH 075/220] Quieten some debug messages --- audio_stdout.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/audio_stdout.c b/audio_stdout.c index 481a8095..1637c825 100644 --- a/audio_stdout.c +++ b/audio_stdout.c @@ -1,5 +1,7 @@ /* - * pipe output driver. This file is part of Shairport. + * stdout output driver. This file is part of Shairport Sync. + * Copyright (c) Mike Brady 2015 + * Based on pipe output driver * Copyright (c) James Laird 2013 * All rights reserved. * @@ -56,7 +58,6 @@ static int init(int argc, char **argv) { if (config.cfg != NULL) { /* Get the desired buffer size setting. */ - debug(1,"Get buffer length"); if (config_lookup_int(config.cfg, "stdout.audio_backend_buffer_desired_length", &value)) { if ((value < 0) || (value > 132300)) die("Invalid stdout audio backend buffer desired length \"%d\". It should be between 0 and 132300, default is 44100", @@ -66,9 +67,7 @@ static int init(int argc, char **argv) { } /* Get the latency offset. */ - debug(1,"Get latency"); if (config_lookup_int(config.cfg, "stdout.audio_backend_latency_offset", &value)) { - debug(1,"Got a latency value of %d.",value); if ((value < -66150) || (value > 66150)) die("Invalid stdout audio backend buffer latency offset \"%d\". It should be between -66150 and +66150, default is 0", value); @@ -76,7 +75,6 @@ static int init(int argc, char **argv) { config.audio_backend_latency_offset = value; } } - debug(1,"buffer: %d, latency: %d.",config.audio_backend_buffer_desired_length,config.audio_backend_latency_offset); return 0; } From e810eb64ce782ed4aa2ed24608c53600e3860808 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 2 Jun 2015 12:52:40 +0000 Subject: [PATCH 076/220] Modify the statistics output when synchronisation is disabled. --- player.c | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/player.c b/player.c index 65fa6542..1a760ce2 100644 --- a/player.c +++ b/player.c @@ -964,6 +964,14 @@ static void *player_thread_func(void *arg) { } } + uint32_t bo = seq_diff(ab_read, ab_write); + + if (bo < minimum_buffer_occupancy) + minimum_buffer_occupancy = bo; + + if (bo > maximum_buffer_occupancy) + maximum_buffer_occupancy = bo; + if (config.output->delay) { current_delay = config.output->delay(); if (current_delay == -1) { @@ -973,14 +981,6 @@ static void *player_thread_func(void *arg) { if (current_delay < minimum_dac_queue_size) minimum_dac_queue_size = current_delay; - uint32_t bo = seq_diff(ab_read, ab_write); - - if (bo < minimum_buffer_occupancy) - minimum_buffer_occupancy = bo; - - if (bo > maximum_buffer_occupancy) - maximum_buffer_occupancy = bo; - // this is the actual delay, including the latency we actually want, which will // fluctuate a good bit about a potentially rising or falling trend. int64_t delay = td_in_frames + rt - (nt - current_delay); @@ -1135,14 +1135,22 @@ static void *player_thread_func(void *arg) { // if ((play_number/print_interval)%20==0) if (config.statistics_requested) if (at_least_one_frame_seen) - inform("Sync error: %.1f (frames); net correction: %.1f (ppm); corrections: %.1f " - "(ppm); missing packets %llu; late packets %llu; too late packets %llu; " - "resend requests %llu; min DAC queue size %lli, min and max buffer occupancy " - "%u and %u.", - moving_average_sync_error, moving_average_correction * 1000000 / 352, - moving_average_insertions_plus_deletions * 1000000 / 352, missing_packets, - late_packets, too_late_packets, resend_requests, minimum_dac_queue_size, - minimum_buffer_occupancy, maximum_buffer_occupancy); + if (config.output->delay) + inform("Sync error: %.1f (frames); net correction: %.1f (ppm); corrections: %.1f " + "(ppm); missing packets %llu; late packets %llu; too late packets %llu; " + "resend requests %llu; min DAC queue size %lli, min and max buffer occupancy " + "%u and %u.", + moving_average_sync_error, moving_average_correction * 1000000 / 352, + moving_average_insertions_plus_deletions * 1000000 / 352, missing_packets, + late_packets, too_late_packets, resend_requests, minimum_dac_queue_size, + minimum_buffer_occupancy, maximum_buffer_occupancy); + else + inform("Synchronisation disabled. Missing packets %llu; late packets %llu; too late packets %llu; " + "resend requests %llu; min and max buffer occupancy " + "%u and %u.", + missing_packets, + late_packets, too_late_packets, resend_requests, + minimum_buffer_occupancy, maximum_buffer_occupancy); else inform("No frames received in the last sampling interval."); minimum_dac_queue_size = 1000000; // hack reset From 60104d0ec9ddba1e38077008614db5e68a9ff7f7 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 2 Jun 2015 12:53:42 +0000 Subject: [PATCH 077/220] Add sample and default settings for the back ends and update documentation a little. --- scripts/shairport-sync.conf | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index f8f91775..797e8e4f 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -1,22 +1,23 @@ // Sample Configuration File for Shairport Sync -// Commented out settings are generally the defaults. +// Commented out settings are generally the defaults, except where noted. // General Settings general = { // name = "Shairport Sync Player"; // This is the name the service will advertise to iTunes. The default is "Shairport Sync on " // mdns_backend = "avahi"; // not used, not tested -// output_backend = "alsa"; // setting is not used, alsa is default +// output_backend = "alsa"; // alsa is default, other possibilities are "stdout", "pulse", "dummy". Only alsa supports synchronisation. // port = 5000; // password = "secret"; // default is no password // interpolation = "basic"; // aka "stuffing". Default is "basic", alternative is "soxr" // statistics = "no"; // print statistics in the log // drift = 88; // allow this number of frames of drift before correcting it // resync_threshold = 2205; // a synchronisation error greater than this will cause resynchronisation; 0 disables it -// log_verbosity = 0; // "0" means no verbosity, "3" is highest. +// log_verbosity = 0; // "0" means no verbosity, "3" is most verbose. // ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. }; +// Latencies for different sources. latencies = { // default = 88200; @@ -25,6 +26,8 @@ latencies = // forkedDaapd = 99400; }; + +// How to deal with metadata, including artwork metadata = { // enabled = "no"; @@ -32,6 +35,7 @@ metadata = // pipe_name = "/tmp/shairport-sync-metadata"; }; +// Advanced parameters for controlling how a Shairport Sync runs sessioncontrol = { // run_this_before_play_begins = "/path/to/application and args"; @@ -41,8 +45,11 @@ sessioncontrol = // session_timeout = 120; }; -// These are parameters for the alsa back end, the only back end that currently works with shairport-sync -alsa = +// +// Back End Settings +// + +// These are parameters for the alsa back end, the only back end that supports synchronisation { // output_device = "default"; // mixer_type = "software"; // "software" or "hardware" @@ -55,6 +62,14 @@ alsa = // These are parameters for the pipe back end, an experimental back end that directs output to a pipe. pipe = { -// name = "stdout"; +// audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. +// audio_backend_buffer_desired_length = 44100; +// name = "/path/to/pipe"; // there is no default pipe name for the output }; +// These are parameters for the stdout back end, an experimental back end that directs output to stdout. +stdout = +{ +// audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. +// audio_backend_buffer_desired_length = 44100; +}; From 58a13cf800c04e0d2792394063b8a22384814667 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 2 Jun 2015 13:18:07 +0000 Subject: [PATCH 078/220] Modify latency and buffer size limits, fix a few dying message bugs. --- audio_alsa.c | 7 +++---- audio_pipe.c | 10 ++++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 02af8d86..97531b46 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -107,7 +107,7 @@ static int init(int argc, char **argv) { /* Get the desired buffer size setting. */ if (config_lookup_int(config.cfg, "alsa.audio_backend_buffer_desired_length", &value)) { if ((value < 0) || (value > 66150)) - die("Invalid alsa audio backend buffer desired length \"%sd\". It should be between 0 and " + die("Invalid alsa audio backend buffer desired length \"%d\". It should be between 0 and " "66150, default is 6615", value); else @@ -116,9 +116,8 @@ static int init(int argc, char **argv) { /* Get the latency offset. */ if (config_lookup_int(config.cfg, "alsa.audio_backend_latency_offset", &value)) { - if ((value < -22050) || (value > 22050)) - die("Invalid alsa audio backend buffer latency offset \"%sd\". It should be between -22050 " - "and +22050, default is 0", + if ((value < -66150) || (value > 66150)) + die("Invalid alsa audio backend buffer latency offset \"%d\". It should be between -66150 and +66150, default is 0", value); else config.audio_backend_latency_offset = value; diff --git a/audio_pipe.c b/audio_pipe.c index b1e5e09c..fe109d65 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -68,9 +68,8 @@ static int init(int argc, char **argv) { /* Get the desired buffer size setting. */ if (config_lookup_int(config.cfg, "pipe.audio_backend_buffer_desired_length", &value)) { - if ((value < 0) || (value > 66150)) - die("Invalid pipe audio backend buffer desired length \"%sd\". It should be between 0 and " - "66150, default is 44100", + if ((value < 0) || (value > 132300)) + die("Invalid pipe audio backend buffer desired length \"%d\". It should be between 0 and 132300, default is 44100", value); else config.audio_backend_buffer_desired_length = value; @@ -78,9 +77,8 @@ static int init(int argc, char **argv) { /* Get the latency offset. */ if (config_lookup_int(config.cfg, "pipe.audio_backend_latency_offset", &value)) { - if ((value < -22050) || (value > 22050)) - die("Invalid pipe audio backend buffer latency offset \"%sd\". It should be between -22050 " - "and +22050, default is 0", + if ((value < -66150) || (value > 66150)) + die("Invalid pipe audio backend buffer latency offset \"%d\". It should be between -66150 and +66150, default is 0", value); else config.audio_backend_latency_offset = value; From 6de98c5bd97e96c1ceea8006d7bcf2f0335d83b4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 3 Jun 2015 19:43:24 +0100 Subject: [PATCH 079/220] Update RELEASENOTES.md --- RELEASENOTES.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f2dc05b5..a5c9a8a5 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,20 +1,25 @@ -Version 2.3.1 -- forthcoming +Version 2.3.1 ----- +Some big changes "under the hood" leading to limited support for unsynchronised output to `stdout` or a named `pipe` and continuation of defacto support for unsynchronised PulseAudio. Also, support for a configuration file, an option to ignore volume control and other improvements are provided. + In this release, Shairport Sync gains the ability to read settings from `/etc/shairport-sync.conf`. -This gives more flexibility in adding features and to give better compatability across different versions of Linux. +This gives more flexibility in adding features gives better compatability across different versions of Linux. Existing command-line options continue to work, but some will be deprecated and may disappear in a future version of Shairport Sync. New settings will only be available via the configuration file. In moving to the the use of a configuration file, some "housekeeping" is being done -- some logical corrections and other small changes are being made to option names and modes of operations, so the settings in the configuration file do not exactly match command line options. -When `make install` is executed, a sample configuration is installed or updated at `/etc/shairport-sync.conf.sample`. The same file is also installed as `/etc/shairport-sync.conf` if that file doesn't already exist. +When `make install` is executed, a sample configuration is installed or updated at `/etc/shairport-sync.conf.sample`. The same file is also installed as `/etc/shairport-sync.conf` if that file doesn't already exist. To prevent the configuration files being installed, use the configuration option `--without-configfiles`. * Pesky Change You Must Do Something About If you enable metadata, please note that the option has changed somewhat. The option `-M` has a new long name equivalent: `--metadata-pipename` and the argument you provide must now be the full name of the metadata pipe, e.g. `-M /tmp/shairport-sync-metadata`. -Change the name of the setting "dac_buffer_desired_length" to "backend_buffer_desired_length" - -* Enhancements +* Enchancements + * A new, supported audio back end called `stdio`. To activate, set `output_backend = "stdout"` in the general section of the configuration file. Output is provided synchronously with the source feed. No stuffing or stripping is done. If you are feeding it to an output device that runs slower or faster, you'll eventually get buffer overflow or underflow in that device. To include support for this back end, use the configuration option `--with-stdout`. + * Support for the `pipe` back end has been enhanced. To activate, set `output_backend = "pipe"` in the general section of the configuration and give the fully-specified pathname to the pipe in the pipe section of the configuration file -- see `etc/shairport-sync.conf.sample` for an example. No stuffing or stripping is done. If you are feeding it to an output device that runs slower or faster, you'll eventually get buffer overflow or underflow in that device. To include support for this back end, use the configuration option `--with-pipe`. + * Support for the `dummy` audio backend device continues. To activate, set `output_backend = "dummy"` in in the general section of the configuration. To include support for this back end, use the configuration option `--with-dummy`. + * Limited support for the PulseAudio audio backend continues. To activate, set `output_backend = "pulse"` in in the general section of the configuration. You must still enter its settings via the command line, after the `--` as before. Note that stuffing or stripping is done: if the PulseAudio sink runs slower or faster, you'll eventually get buffer overflow or underflow. + * New backend-specific settings are provided for setting the size of the backend's buffer and for adding or removing a fixed offset to the overall latency. * Shairport Sync now reads settings from the configuration file `/etc/shairport-sync.conf`. This has settings for most command-line options and it's where any new settings will go. A default configuration file will be installed if one doesn't exist, and a sample file configuration file is always installed or updated. Details of settings are provided in the sample file. Shairport Sync relies on the `libconfig` library to read configuration files. For the present, you can disable the new feature (and save the space taken up by `libconfig`) by using the configure option `--without-configfile-support`. * New command-line option `-c ` or `--configfile=` allows you to specify a configuration file other than `/etc/shairport-sync.conf`. * Session Timeout and Allow Session Interruption can now be set independently. This is really some "housekeeping" as referred to above -- it's a kind of a bug fix, where the bug in question is an inappropriate connection of the setting of two parameters. To explain: (1) By default, when a source such as iTunes starts playing to the Shairport Sync device, any other source attempting to start a play session receives a "busy" signal. If a source disappears without warning, Shairport Sync will wait for 120 seconds before dropping the session and allowing another source to start a play session. (2) The command-line option `-t` or `--timeout` allows you to set the wait time before dropping the session. If you set this parameter to `0`, Shairport Sync will not send a "busy" signal, thus allowing another source to interrupt an existing one. (3) The problem is that if you set the parameter to `0`, a session will never be dropped if the source disappears without warning. From a090a14ca84c952a13aefd2cb0992d64b8146c09 Mon Sep 17 00:00:00 2001 From: Mke Brady Date: Wed, 3 Jun 2015 20:15:54 +0100 Subject: [PATCH 080/220] Make dummy, pipe and stdout back ends optional compilations --- Makefile.am | 12 ++++++++++++ configure.ac | 13 +++++++++++-- shairport.c | 9 +++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index 203fffbb..84c20d60 100644 --- a/Makefile.am +++ b/Makefile.am @@ -26,6 +26,18 @@ if USE_SNDIO shairport_sync_SOURCES += audio_sndio.c endif +if USE_STDOUT +shairport_sync_SOURCES += audio_stdout.c +endif + +if USE_PIPE +shairport_sync_SOURCES += audio_pipe.c +endif + +if USE_DUMMY +shairport_sync_SOURCES += audio_dummy.c +endif + if USE_AO shairport_sync_SOURCES += audio_ao.c endif diff --git a/configure.ac b/configure.ac index 56dee0e2..8734643d 100644 --- a/configure.ac +++ b/configure.ac @@ -48,6 +48,15 @@ else AC_CHECK_LIB([popt],[poptGetContext], , AC_MSG_ERROR(libpopt needed)) fi +AC_ARG_WITH([dummy],[ --with-dummy = include the dummy audio back end ],[AC_MSG_RESULT(>>Including the dummy audio back end) AC_DEFINE([CONFIG_DUMMY], 1, [Needed by the compiler.]) ], ) +AM_CONDITIONAL([USE_DUMMY], [test "x$with_dummy" = "x1" ]) + +AC_ARG_WITH([stdout],[ --with-stdout = include the stdout audio back end ],[ AC_MSG_RESULT(>>Including the stdout audio back end) AC_DEFINE([CONFIG_STDOUT], 1, [Needed by the compiler.]) ], ) +AM_CONDITIONAL([USE_STDOUT], [test "x$with_stdout" = "x1" ]) + +AC_ARG_WITH([pipe],[ --with-pipe = include the pipe audio back end ],[ AC_MSG_RESULT(>>Including the pipe audio back end) AC_DEFINE([CONFIG_PIPE], 1, [Needed by the compiler.]) ], ) +AM_CONDITIONAL([USE_PIPE], [test "x$with_pipe" = "x1" ]) + AC_ARG_WITH([initscript], [ --with-initscript = include a startup script], ,[with_initscript=1]) AM_CONDITIONAL([INSTALL_INITSCRIPT], [test "x$with_initscript" = "x1"]) @@ -173,8 +182,8 @@ AC_ARG_WITH(ao, [ --with-ao = choose native AO (Audio Output?) API support -- p AM_CONDITIONAL([USE_AO], [test "x$HAS_AO" = "x1"]) # Look for pulseaudio flag -AC_ARG_WITH(pulseaudio, [ --with-pulseaudio = choose native PulseAudio API support -- probably broken], [ - AC_MSG_RESULT(>>Including a PulseAudio back end --- N.B. this is probably broken!) +AC_ARG_WITH(pulseaudio, [ --with-pulseaudio = choose native PulseAudio API support -- N.B. no synchronisation -- so underflow or overflow is inevitable!], [ + AC_MSG_RESULT(>>Including a PulseAudio back end -- N.B. no synchronisation -- so underflow or overflow is inevitable!) HAS_PULSE=1 AC_DEFINE([CONFIG_PULSE], 1, [Needed by the compiler.]) AC_CHECK_LIB([pulse-simple], [pa_simple_new], , AC_MSG_ERROR(PulseAudio support requires the pulse-simple library!)) diff --git a/shairport.c b/shairport.c index 97cd0bbd..16a42080 100644 --- a/shairport.c +++ b/shairport.c @@ -132,6 +132,15 @@ void print_version(void) { #ifdef CONFIG_PULSE strcat(version_string, "-pulse"); #endif +#ifdef CONFIG_DUMMY + strcat(version_string, "-dummy"); +#endif +#ifdef CONFIG_STDOUT + strcat(version_string, "-stdout"); +#endif +#ifdef CONFIG_PIPE + strcat(version_string, "-pipe"); +#endif #ifdef HAVE_LIBSOXR strcat(version_string, "-soxr"); #endif From 84eac6c5c81f63ee80f3860b21497e5e2312176a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 3 Jun 2015 20:20:44 +0100 Subject: [PATCH 081/220] =?UTF-8?q?Big=20update=20=E2=80=93=20extra=20outp?= =?UTF-8?q?uts,=20config=20files.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- RELEASENOTES.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a5c9a8a5..9c9e810e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,10 +1,12 @@ Version 2.3.1 ----- -Some big changes "under the hood" leading to limited support for unsynchronised output to `stdout` or a named `pipe` and continuation of defacto support for unsynchronised PulseAudio. Also, support for a configuration file, an option to ignore volume control and other improvements are provided. +Some big changes "under the hood" leading to limited support for unsynchronised output to `stdout` or a named `pipe` and continuation of defacto support for unsynchronised PulseAudio. Also, support for a configuration file in preference to command line options, an option to ignore volume control and other improvements are provided. In this release, Shairport Sync gains the ability to read settings from `/etc/shairport-sync.conf`. This gives more flexibility in adding features gives better compatability across different versions of Linux. -Existing command-line options continue to work, but some will be deprecated and may disappear in a future version of Shairport Sync. New settings will only be available via the configuration file. +Existing command-line options continue to work, but some will be deprecated and may disappear in a future version of Shairport Sync. New settings will only be available via the configuration file. + +Note that, for the present, settings in the configuration will have priority over command line options for Shairport Sync itself, in contravention of the normal unix convention. Audio back end command line options Ii.e. thopse after yher `--` have priority over configuration file settings for the audio backends. In moving to the the use of a configuration file, some "housekeeping" is being done -- some logical corrections and other small changes are being made to option names and modes of operations, so the settings in the configuration file do not exactly match command line options. @@ -12,7 +14,7 @@ When `make install` is executed, a sample configuration is installed or updated * Pesky Change You Must Do Something About -If you enable metadata, please note that the option has changed somewhat. The option `-M` has a new long name equivalent: `--metadata-pipename` and the argument you provide must now be the full name of the metadata pipe, e.g. `-M /tmp/shairport-sync-metadata`. +If you are using metadata, please note that the option has changed somewhat. The option `-M` has a new long name equivalent: `--metadata-pipename` and the argument you provide must now be the full name of the metadata pipe, e.g. `-M /tmp/shairport-sync-metadata`. * Enchancements * A new, supported audio back end called `stdio`. To activate, set `output_backend = "stdout"` in the general section of the configuration file. Output is provided synchronously with the source feed. No stuffing or stripping is done. If you are feeding it to an output device that runs slower or faster, you'll eventually get buffer overflow or underflow in that device. To include support for this back end, use the configuration option `--with-stdout`. From c8fdbbd5a4452e12f4cb55243c846ee090636e6a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 3 Jun 2015 20:21:45 +0100 Subject: [PATCH 082/220] Rephrase. --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9c9e810e..e63ec8b0 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,6 +1,6 @@ Version 2.3.1 ----- -Some big changes "under the hood" leading to limited support for unsynchronised output to `stdout` or a named `pipe` and continuation of defacto support for unsynchronised PulseAudio. Also, support for a configuration file in preference to command line options, an option to ignore volume control and other improvements are provided. +Some big changes "under the hood" have been made, leading to limited support for unsynchronised output to `stdout` or to a named pipe and continuation of defacto support for unsynchronised PulseAudio. Also, support for a configuration file in preference to command line options, an option to ignore volume control and other improvements are provided. In this release, Shairport Sync gains the ability to read settings from `/etc/shairport-sync.conf`. This gives more flexibility in adding features gives better compatability across different versions of Linux. From 157b3aaf1d8edccf568e9fe5f6cde05f77cc7a70 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 3 Jun 2015 20:25:25 +0100 Subject: [PATCH 083/220] Add detail about PCM --- RELEASENOTES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e63ec8b0..b059b9d0 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -17,8 +17,8 @@ When `make install` is executed, a sample configuration is installed or updated If you are using metadata, please note that the option has changed somewhat. The option `-M` has a new long name equivalent: `--metadata-pipename` and the argument you provide must now be the full name of the metadata pipe, e.g. `-M /tmp/shairport-sync-metadata`. * Enchancements - * A new, supported audio back end called `stdio`. To activate, set `output_backend = "stdout"` in the general section of the configuration file. Output is provided synchronously with the source feed. No stuffing or stripping is done. If you are feeding it to an output device that runs slower or faster, you'll eventually get buffer overflow or underflow in that device. To include support for this back end, use the configuration option `--with-stdout`. - * Support for the `pipe` back end has been enhanced. To activate, set `output_backend = "pipe"` in the general section of the configuration and give the fully-specified pathname to the pipe in the pipe section of the configuration file -- see `etc/shairport-sync.conf.sample` for an example. No stuffing or stripping is done. If you are feeding it to an output device that runs slower or faster, you'll eventually get buffer overflow or underflow in that device. To include support for this back end, use the configuration option `--with-pipe`. + * A new, supported audio back end called `stdio` provides raw 16-bit 44.1kHz stereo PCM output. To activate, set `output_backend = "stdout"` in the general section of the configuration file. Output is provided synchronously with the source feed. No stuffing or stripping is done. If you are feeding it to an output device that runs slower or faster, you'll eventually get buffer overflow or underflow in that device. To include support for this back end, use the configuration option `--with-stdout`. + * Support for the `pipe` back end has been enhanced to provide raw 16-bit 44.1kHz stereo PCM output to a named pipe. To activate, set `output_backend = "pipe"` in the general section of the configuration and give the fully-specified pathname to the pipe in the pipe section of the configuration file -- see `etc/shairport-sync.conf.sample` for an example. No stuffing or stripping is done. If you are feeding it to an output device that runs slower or faster, you'll eventually get buffer overflow or underflow in that device. To include support for this back end, use the configuration option `--with-pipe`. * Support for the `dummy` audio backend device continues. To activate, set `output_backend = "dummy"` in in the general section of the configuration. To include support for this back end, use the configuration option `--with-dummy`. * Limited support for the PulseAudio audio backend continues. To activate, set `output_backend = "pulse"` in in the general section of the configuration. You must still enter its settings via the command line, after the `--` as before. Note that stuffing or stripping is done: if the PulseAudio sink runs slower or faster, you'll eventually get buffer overflow or underflow. * New backend-specific settings are provided for setting the size of the backend's buffer and for adding or removing a fixed offset to the overall latency. From 11cfe7accccfd61328c8d604145c6e004622cad0 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 3 Jun 2015 20:26:38 +0100 Subject: [PATCH 084/220] Include "no", duh! --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b059b9d0..eb71d991 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -20,7 +20,7 @@ If you are using metadata, please note that the option has changed somewhat. The * A new, supported audio back end called `stdio` provides raw 16-bit 44.1kHz stereo PCM output. To activate, set `output_backend = "stdout"` in the general section of the configuration file. Output is provided synchronously with the source feed. No stuffing or stripping is done. If you are feeding it to an output device that runs slower or faster, you'll eventually get buffer overflow or underflow in that device. To include support for this back end, use the configuration option `--with-stdout`. * Support for the `pipe` back end has been enhanced to provide raw 16-bit 44.1kHz stereo PCM output to a named pipe. To activate, set `output_backend = "pipe"` in the general section of the configuration and give the fully-specified pathname to the pipe in the pipe section of the configuration file -- see `etc/shairport-sync.conf.sample` for an example. No stuffing or stripping is done. If you are feeding it to an output device that runs slower or faster, you'll eventually get buffer overflow or underflow in that device. To include support for this back end, use the configuration option `--with-pipe`. * Support for the `dummy` audio backend device continues. To activate, set `output_backend = "dummy"` in in the general section of the configuration. To include support for this back end, use the configuration option `--with-dummy`. - * Limited support for the PulseAudio audio backend continues. To activate, set `output_backend = "pulse"` in in the general section of the configuration. You must still enter its settings via the command line, after the `--` as before. Note that stuffing or stripping is done: if the PulseAudio sink runs slower or faster, you'll eventually get buffer overflow or underflow. + * Limited support for the PulseAudio audio backend continues. To activate, set `output_backend = "pulse"` in in the general section of the configuration. You must still enter its settings via the command line, after the `--` as before. Note that no stuffing or stripping is done: if the PulseAudio sink runs slower or faster, you'll eventually get buffer overflow or underflow. * New backend-specific settings are provided for setting the size of the backend's buffer and for adding or removing a fixed offset to the overall latency. * Shairport Sync now reads settings from the configuration file `/etc/shairport-sync.conf`. This has settings for most command-line options and it's where any new settings will go. A default configuration file will be installed if one doesn't exist, and a sample file configuration file is always installed or updated. Details of settings are provided in the sample file. Shairport Sync relies on the `libconfig` library to read configuration files. For the present, you can disable the new feature (and save the space taken up by `libconfig`) by using the configure option `--without-configfile-support`. * New command-line option `-c ` or `--configfile=` allows you to specify a configuration file other than `/etc/shairport-sync.conf`. From 3d03e6c67010b6abce83ee8bfc906688f5e1bd52 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 3 Jun 2015 20:40:23 +0100 Subject: [PATCH 085/220] Add a little more info, reorder enhancements --- RELEASENOTES.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index eb71d991..2b5a3def 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -16,19 +16,18 @@ When `make install` is executed, a sample configuration is installed or updated If you are using metadata, please note that the option has changed somewhat. The option `-M` has a new long name equivalent: `--metadata-pipename` and the argument you provide must now be the full name of the metadata pipe, e.g. `-M /tmp/shairport-sync-metadata`. -* Enchancements - * A new, supported audio back end called `stdio` provides raw 16-bit 44.1kHz stereo PCM output. To activate, set `output_backend = "stdout"` in the general section of the configuration file. Output is provided synchronously with the source feed. No stuffing or stripping is done. If you are feeding it to an output device that runs slower or faster, you'll eventually get buffer overflow or underflow in that device. To include support for this back end, use the configuration option `--with-stdout`. - * Support for the `pipe` back end has been enhanced to provide raw 16-bit 44.1kHz stereo PCM output to a named pipe. To activate, set `output_backend = "pipe"` in the general section of the configuration and give the fully-specified pathname to the pipe in the pipe section of the configuration file -- see `etc/shairport-sync.conf.sample` for an example. No stuffing or stripping is done. If you are feeding it to an output device that runs slower or faster, you'll eventually get buffer overflow or underflow in that device. To include support for this back end, use the configuration option `--with-pipe`. - * Support for the `dummy` audio backend device continues. To activate, set `output_backend = "dummy"` in in the general section of the configuration. To include support for this back end, use the configuration option `--with-dummy`. - * Limited support for the PulseAudio audio backend continues. To activate, set `output_backend = "pulse"` in in the general section of the configuration. You must still enter its settings via the command line, after the `--` as before. Note that no stuffing or stripping is done: if the PulseAudio sink runs slower or faster, you'll eventually get buffer overflow or underflow. - * New backend-specific settings are provided for setting the size of the backend's buffer and for adding or removing a fixed offset to the overall latency. - * Shairport Sync now reads settings from the configuration file `/etc/shairport-sync.conf`. This has settings for most command-line options and it's where any new settings will go. A default configuration file will be installed if one doesn't exist, and a sample file configuration file is always installed or updated. Details of settings are provided in the sample file. Shairport Sync relies on the `libconfig` library to read configuration files. For the present, you can disable the new feature (and save the space taken up by `libconfig`) by using the configure option `--without-configfile-support`. +* Enhancements +* Shairport Sync now reads settings from the configuration file `/etc/shairport-sync.conf`. This has settings for most command-line options and it's where any new settings will go. A default configuration file will be installed if one doesn't exist, and a sample file configuration file is always installed or updated. Details of settings are provided in the sample file. Shairport Sync relies on the `libconfig` library to read configuration files. For the present, you can disable the new feature (and save the space taken up by `libconfig`) by using the configure option `--without-configfile-support`. * New command-line option `-c ` or `--configfile=` allows you to specify a configuration file other than `/etc/shairport-sync.conf`. * Session Timeout and Allow Session Interruption can now be set independently. This is really some "housekeeping" as referred to above -- it's a kind of a bug fix, where the bug in question is an inappropriate connection of the setting of two parameters. To explain: (1) By default, when a source such as iTunes starts playing to the Shairport Sync device, any other source attempting to start a play session receives a "busy" signal. If a source disappears without warning, Shairport Sync will wait for 120 seconds before dropping the session and allowing another source to start a play session. (2) The command-line option `-t` or `--timeout` allows you to set the wait time before dropping the session. If you set this parameter to `0`, Shairport Sync will not send a "busy" signal, thus allowing another source to interrupt an existing one. (3) The problem is that if you set the parameter to `0`, a session will never be dropped if the source disappears without warning. The (obvious) fix for this is to separate the setting of the two parameters, and this is now done in the configuration file `/etc/shairport-sync.conf` -- please see the settings `allow_session_interruption` and `session_timeout`. The behaviour of the `-t` and `--timeout` command-line options is unchanged but deprecated. * New Option -- "Ignore Volume Control" ('ignore_volume_control'). If you set this to "yes", the output from Shairport Sync is always set at 100%. This is useful when you want to set the volume locally. Available via the settings file only. - * New Option -- "Set DAC Buffer Desired Length" ('dac_buffer_desired_length'). Default is 6615 frames, or 0.15 seconds. On some slower machines, particularly with metadata processing going on, the DAC buffer can underflow on this setting, so it might be worth making the buffer larger. A problem on software mixers only is that changes to volume control settings have to propagate through the buffer to be heard, so the larger the buffer, the longer the response time. This is only a problem if you're using a software mixer; hardware mixers are unaffected. Available via the settings file only. - * Statistics option correctly reports when no frames are received in a sampling interval. + * Statistics option correctly reports when no frames are received in a sampling interval and when output is not being synchronised. + * A new, supported audio back end called `stdio` provides raw 16-bit 44.1kHz stereo PCM output. To activate, set `output_backend = "stdout"` in the general section of the configuration file. Output is provided synchronously with the source feed. No stuffing or stripping is done. If you are feeding it to an output device that runs slower or faster, you'll eventually get buffer overflow or underflow in that device. To include support for this back end, use the configuration option `--with-stdout`. + * Support for the `pipe` back end has been enhanced to provide raw 16-bit 44.1kHz stereo PCM output to a named pipe. To activate, set `output_backend = "pipe"` in the general section of the configuration and give the fully-specified pathname to the pipe in the pipe section of the configuration file -- see `etc/shairport-sync.conf.sample` for an example. No stuffing or stripping is done. If you are feeding it to an output device that runs slower or faster, you'll eventually get buffer overflow or underflow in that device. To include support for this back end, use the configuration option `--with-pipe`. + * Support for the `dummy` audio backend device continues. To activate, set `output_backend = "dummy"` in in the general section of the configuration. To include support for this back end, use the configuration option `--with-dummy`. + * Limited support for the PulseAudio audio backend continues. To activate, set `output_backend = "pulse"` in in the general section of the configuration. You must still enter its settings via the command line, after the `--` as before. Note that no stuffing or stripping is done: if the PulseAudio sink runs slower or faster, you'll eventually get buffer overflow or underflow. + * New backend-specific settings are provided for setting the size of the backend's buffer and for adding or removing a fixed offset to the overall latency. The `audio_backend_buffer_desired_length` default is 6615 frames, or 0.15 seconds. On some slower machines, particularly with metadata processing going on, the DAC buffer can underflow on this setting, so it might be worth making the buffer larger. A problem on software mixers only is that changes to volume control settings have to propagate through the buffer to be heard, so the larger the buffer, the longer the response time. If you're using an alsa back end and are using a hardware mixers, this isn't a problem. The `audio_backend_latency_offset` allows you emit frames to the audio back end some time before or after the synchronised time. This would be useful, for example, if you are outputting to a device that takes 20 ms to process audio; yoou would specify a `audio_backend_latency_offset = -882`, where 882 is the number of frames in 20 ms, to compensate for the device delay. Version 2.3 ----- @@ -36,7 +35,7 @@ Version 2.3 * Adding the System V startup script (the "initscript") is now a configuration option. The default is to include it, so if you want to omit the installation of the initscript, add the configuration option `--without-initscript`. * Metadata support is now a compile-time option: `--with-metadata`. * A metadata feed has been added. Use the option `-M `, e.g. `-M /tmp`. Shairport Sync will provide metadata in a pipe called `/shairport-sync-metadata`. (This is changed in 2.3.1.) There's a sample metadata reader at https://github.com/mikebrady/shairport-sync-metadata-reader. The format of the metadata is a mixture of XML-style tags, 4-character codes and base64 data. Please look at `rtsp.c` and `player.c` for examples. Please note that the format of the metadata may change. -Beware: there appears to be a serious bug in iTunes, such that it may stall for a long period when sending large (more than a few hundred kilobytes) coverart images. +Beware: there appears to be a serious bug in iTunes before 12.1.2, such that it may stall for a long period when sending large (more than a few hundred kilobytes) coverart images. * Bugfix * Fix a bug when compiling for Arch Linux on Raspberry Pi 2 (thanks to https://github.com/joaodriessen). From fd347fcbcee46bd26ea60e17550572102b765d25 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 3 Jun 2015 20:43:39 +0100 Subject: [PATCH 086/220] Fix typos. --- RELEASENOTES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 2b5a3def..bcb21414 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -6,7 +6,7 @@ In this release, Shairport Sync gains the ability to read settings from `/etc/sh This gives more flexibility in adding features gives better compatability across different versions of Linux. Existing command-line options continue to work, but some will be deprecated and may disappear in a future version of Shairport Sync. New settings will only be available via the configuration file. -Note that, for the present, settings in the configuration will have priority over command line options for Shairport Sync itself, in contravention of the normal unix convention. Audio back end command line options Ii.e. thopse after yher `--` have priority over configuration file settings for the audio backends. +Note that, for the present, settings in the configuration will have priority over command line options for Shairport Sync itself, in contravention of the normal unix convention. Audio back end command line options, i.e. those after the `--`, have priority over configuration file settings for the audio backends. In moving to the the use of a configuration file, some "housekeeping" is being done -- some logical corrections and other small changes are being made to option names and modes of operations, so the settings in the configuration file do not exactly match command line options. @@ -17,7 +17,7 @@ When `make install` is executed, a sample configuration is installed or updated If you are using metadata, please note that the option has changed somewhat. The option `-M` has a new long name equivalent: `--metadata-pipename` and the argument you provide must now be the full name of the metadata pipe, e.g. `-M /tmp/shairport-sync-metadata`. * Enhancements -* Shairport Sync now reads settings from the configuration file `/etc/shairport-sync.conf`. This has settings for most command-line options and it's where any new settings will go. A default configuration file will be installed if one doesn't exist, and a sample file configuration file is always installed or updated. Details of settings are provided in the sample file. Shairport Sync relies on the `libconfig` library to read configuration files. For the present, you can disable the new feature (and save the space taken up by `libconfig`) by using the configure option `--without-configfile-support`. + * Shairport Sync now reads settings from the configuration file `/etc/shairport-sync.conf`. This has settings for most command-line options and it's where any new settings will go. A default configuration file will be installed if one doesn't exist, and a sample file configuration file is always installed or updated. Details of settings are provided in the sample file. Shairport Sync relies on the `libconfig` library to read configuration files. For the present, you can disable the new feature (and save the space taken up by `libconfig`) by using the configure option `--without-configfile-support`. * New command-line option `-c ` or `--configfile=` allows you to specify a configuration file other than `/etc/shairport-sync.conf`. * Session Timeout and Allow Session Interruption can now be set independently. This is really some "housekeeping" as referred to above -- it's a kind of a bug fix, where the bug in question is an inappropriate connection of the setting of two parameters. To explain: (1) By default, when a source such as iTunes starts playing to the Shairport Sync device, any other source attempting to start a play session receives a "busy" signal. If a source disappears without warning, Shairport Sync will wait for 120 seconds before dropping the session and allowing another source to start a play session. (2) The command-line option `-t` or `--timeout` allows you to set the wait time before dropping the session. If you set this parameter to `0`, Shairport Sync will not send a "busy" signal, thus allowing another source to interrupt an existing one. (3) The problem is that if you set the parameter to `0`, a session will never be dropped if the source disappears without warning. The (obvious) fix for this is to separate the setting of the two parameters, and this is now done in the configuration file `/etc/shairport-sync.conf` -- please see the settings `allow_session_interruption` and `session_timeout`. The behaviour of the `-t` and `--timeout` command-line options is unchanged but deprecated. From 709518834456d6710ed3a58beb996a681047848f Mon Sep 17 00:00:00 2001 From: Mke Brady Date: Wed, 3 Jun 2015 21:12:59 +0100 Subject: [PATCH 087/220] Bump version --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 8734643d..20d94f14 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.1], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.2], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 4fb74b4289489e79b47b94227fdd1cc6104f01f2 Mon Sep 17 00:00:00 2001 From: Mke Brady Date: Wed, 3 Jun 2015 21:13:40 +0100 Subject: [PATCH 088/220] Add enclosing XML item tags to each item of metadata --- rtsp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rtsp.c b/rtsp.c index f25ba57b..f13da7c1 100644 --- a/rtsp.c +++ b/rtsp.c @@ -942,7 +942,7 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) if (fd < 0) return; char thestring[1024]; - snprintf(thestring, 1024, "%x%x%u\n", type, code, + snprintf(thestring, 1024, "\n%x%x%u\n", type, code, length); ret = non_blocking_write(fd, thestring, strlen(thestring)); if (ret < 1) @@ -973,7 +973,7 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) remaining_data += towrite_count; remaining_count -= towrite_count; } - snprintf(thestring, 1024, "\n"); + snprintf(thestring, 1024, "\n\n"); ret = non_blocking_write(fd, thestring, strlen(thestring)); if (ret < 1) // no reader return; From 42109ef67b39b299d028a757b20e800c1fe6d1e6 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 6 Jun 2015 19:45:00 +0100 Subject: [PATCH 089/220] Update shairport-sync.conf --- scripts/shairport-sync.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 797e8e4f..aad81159 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -67,7 +67,7 @@ pipe = // name = "/path/to/pipe"; // there is no default pipe name for the output }; -// These are parameters for the stdout back end, an experimental back end that directs output to stdout. +// These are parameters for the stdout audio back end, an experimental back end that directs output to stdout. stdout = { // audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. From c2117e302af7207d3a67259f0a04ff420652f1ea Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 6 Jun 2015 21:37:08 +0100 Subject: [PATCH 090/220] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 20d94f14..635ff6d9 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.2], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.3], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From f848b08c7a4cfd6d3844dc3fe99f0989911c1bad Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 6 Jun 2015 21:58:04 +0100 Subject: [PATCH 091/220] Add alsa stanza name! --- scripts/shairport-sync.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index aad81159..ff7ebd4c 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -50,6 +50,7 @@ sessioncontrol = // // These are parameters for the alsa back end, the only back end that supports synchronisation +alsa = { // output_device = "default"; // mixer_type = "software"; // "software" or "hardware" From 8c55f603bc5fd145c0efe8907048a032a71d9044 Mon Sep 17 00:00:00 2001 From: Mke Brady Date: Wed, 10 Jun 2015 22:18:17 +0100 Subject: [PATCH 092/220] UDP port numbers are now chosen from port 7000 up. Two options are introduced to control this. joerg-krause's stack erro fix installed. --- common.h | 2 + configure.ac | 2 +- player.c | 13 +++++-- rtp.c | 39 ++++++++++++------- rtsp.c | 2 +- scripts/shairport-sync.conf | 2 + scripts/shairport-sync.conf~ | 75 ++++++++++++++++++++++++++++++++++++ shairport.c | 22 +++++++++++ 8 files changed, 138 insertions(+), 19 deletions(-) create mode 100644 scripts/shairport-sync.conf~ diff --git a/common.h b/common.h index 6a9436b1..792dd36c 100644 --- a/common.h +++ b/common.h @@ -48,6 +48,8 @@ typedef struct { #endif uint8_t hw_addr[6]; int port; + int udp_port_base; + int udp_port_range; int ignore_volume_control; int resyncthreshold; // if it get's out of whack my more than this, resync. Zero means never // resync. diff --git a/configure.ac b/configure.ac index 20d94f14..635ff6d9 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.2], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.3], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) diff --git a/player.c b/player.c index 1a760ce2..df12eb27 100644 --- a/player.c +++ b/player.c @@ -36,12 +36,13 @@ #include #include #include -#include +#include #include #include #include #include #include +#include #include "config.h" @@ -1271,8 +1272,14 @@ int player_play(stream_cfg *stream) { if (rc) debug(1, "Error initialising condition variable."); config.output->start(sampling_rate); - pthread_create(&player_thread, NULL, player_thread_func, NULL); - + size_t size = (PTHREAD_STACK_MIN + 128 * 1024); + pthread_attr_t tattr; + pthread_attr_init(&tattr); + rc = pthread_attr_setstacksize(&tattr, size); + if (rc) + debug(1, "Error setting stack size for player_thread: %s", strerror(errno)); + pthread_create(&player_thread, &tattr, player_thread_func, NULL); + pthread_attr_destroy(&tattr); return 0; } diff --git a/rtp.c b/rtp.c index 5590e649..37e2eccb 100644 --- a/rtp.c +++ b/rtp.c @@ -396,7 +396,7 @@ static void *rtp_timing_receiver(void *arg) { return NULL; } -static int bind_port(SOCKADDR *remote, int *sock, int desired_port) { +static int bind_port(SOCKADDR *remote, int *sock) { struct addrinfo hints, *info; memset(&hints, 0, sizeof(hints)); @@ -405,21 +405,32 @@ static int bind_port(SOCKADDR *remote, int *sock, int desired_port) { hints.ai_flags = AI_PASSIVE; char buffer[10]; - snprintf(buffer, 10, "%d", desired_port); + + // look for a port in the range, if any was specified. + int desired_port = config.udp_port_base; + int ret; + do { + snprintf(buffer, 10, "%d", desired_port); - int ret = getaddrinfo(NULL, buffer, &hints, &info); + ret = getaddrinfo(NULL, buffer, &hints, &info); - if (ret < 0) - die("failed to get usable addrinfo?! %s.", gai_strerror(ret)); + if (ret < 0) + die("failed to get usable addrinfo?! %s.", gai_strerror(ret)); - *sock = socket(remote->SAFAMILY, SOCK_DGRAM, IPPROTO_UDP); - ret = bind(*sock, info->ai_addr, info->ai_addrlen); + *sock = socket(remote->SAFAMILY, SOCK_DGRAM, IPPROTO_UDP); + ret = bind(*sock, info->ai_addr, info->ai_addrlen); - freeaddrinfo(info); - - if (ret < 0) - die("could not bind a UDP port!"); + freeaddrinfo(info); + } while ((ret<0) && (errno==EADDRINUSE) && (desired_port!=0) && (desired_port++ < config.udp_port_base+config.udp_port_range)); + + debug(1,"UDP port chosen: %d.",desired_port); + + if (ret < 0) { + die("error: could not bind a UDP port!"); + } + + int sport; SOCKADDR local; socklen_t local_len = sizeof(local); @@ -510,9 +521,9 @@ void rtp_setup(SOCKADDR *remote, int cport, int tport, uint32_t active_remote, i // now, we open three sockets -- one for the audio stream, one for the timing and one for the // control - *lsport = bind_port(remote, &audio_socket, 0); - *lcport = bind_port(remote, &control_socket, 0); - *ltport = bind_port(remote, &timing_socket, 0); + *lsport = bind_port(remote, &audio_socket); + *lcport = bind_port(remote, &control_socket); + *ltport = bind_port(remote, &timing_socket); debug(2, "listening for audio, control and timing on ports %d, %d, %d.", *lsport, *lcport, *ltport); diff --git a/rtsp.c b/rtsp.c index f13da7c1..3cf03d85 100644 --- a/rtsp.c +++ b/rtsp.c @@ -39,7 +39,7 @@ #include #include #include -#include +#include #include "config.h" diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 797e8e4f..3b1c16bd 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -8,6 +8,8 @@ general = // mdns_backend = "avahi"; // not used, not tested // output_backend = "alsa"; // alsa is default, other possibilities are "stdout", "pulse", "dummy". Only alsa supports synchronisation. // port = 5000; +// udp_port_base = 7000; // start allocation UDP ports from this port number +// udp_port_range = 100; // look for free ports in this number of places, starting at the UDP port base (only three are needed). // password = "secret"; // default is no password // interpolation = "basic"; // aka "stuffing". Default is "basic", alternative is "soxr" // statistics = "no"; // print statistics in the log diff --git a/scripts/shairport-sync.conf~ b/scripts/shairport-sync.conf~ new file mode 100644 index 00000000..797e8e4f --- /dev/null +++ b/scripts/shairport-sync.conf~ @@ -0,0 +1,75 @@ +// Sample Configuration File for Shairport Sync +// Commented out settings are generally the defaults, except where noted. + +// General Settings +general = +{ +// name = "Shairport Sync Player"; // This is the name the service will advertise to iTunes. The default is "Shairport Sync on " +// mdns_backend = "avahi"; // not used, not tested +// output_backend = "alsa"; // alsa is default, other possibilities are "stdout", "pulse", "dummy". Only alsa supports synchronisation. +// port = 5000; +// password = "secret"; // default is no password +// interpolation = "basic"; // aka "stuffing". Default is "basic", alternative is "soxr" +// statistics = "no"; // print statistics in the log +// drift = 88; // allow this number of frames of drift before correcting it +// resync_threshold = 2205; // a synchronisation error greater than this will cause resynchronisation; 0 disables it +// log_verbosity = 0; // "0" means no verbosity, "3" is most verbose. +// ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. +}; + +// Latencies for different sources. +latencies = +{ +// default = 88200; +// itunes = 99400; +// airplay = 88200; +// forkedDaapd = 99400; +}; + + +// How to deal with metadata, including artwork +metadata = +{ +// enabled = "no"; +// include_cover_art = "no"; +// pipe_name = "/tmp/shairport-sync-metadata"; +}; + +// Advanced parameters for controlling how a Shairport Sync runs +sessioncontrol = +{ +// run_this_before_play_begins = "/path/to/application and args"; +// run_this_after_play_ends = "/path/to/application and args"; +// wait_for_completion = "no"; +// allow_session_interruption = "no"; +// session_timeout = 120; +}; + +// +// Back End Settings +// + +// These are parameters for the alsa back end, the only back end that supports synchronisation +{ +// output_device = "default"; +// mixer_type = "software"; // "software" or "hardware" +// mixer_device = "default"; //actually, the mixer default is whatever the output_device is. Normally you wouldn't have to use this. +// mixer_control_name = "PCM"; // the name of the mixer to use -- there is no default. +// audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. +// audio_backend_buffer_desired_length = 6615; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. +}; + +// These are parameters for the pipe back end, an experimental back end that directs output to a pipe. +pipe = +{ +// audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. +// audio_backend_buffer_desired_length = 44100; +// name = "/path/to/pipe"; // there is no default pipe name for the output +}; + +// These are parameters for the stdout back end, an experimental back end that directs output to stdout. +stdout = +{ +// audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. +// audio_backend_buffer_desired_length = 44100; +}; diff --git a/shairport.c b/shairport.c index 16a42080..2c5fcbdd 100644 --- a/shairport.c +++ b/shairport.c @@ -369,6 +369,24 @@ int parse_options(int argc, char **argv) { 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 7000", + 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 < 0) || (value > 65535)) + die("Invalid port range \"%sd\". It should be between 0 and 65535, default is 100", + value); + else + config.udp_port_range = value; + } + /* Get the password setting. */ if (config_lookup_string(config.cfg, "general.password", &str)) config.password = (char *)str; @@ -606,6 +624,8 @@ int main(int argc, char **argv) { set_requested_connection_state_to_output( 1); // we expect to be able to connect to the output device config.audio_backend_buffer_desired_length = 6615; // 0.15 seconds. + config.udp_port_base = 7000; + config.udp_port_range = 100; // this is a bit weird, but apparently necessary char *basec = strdup(argv[0]); @@ -780,6 +800,8 @@ int main(int argc, char **argv) { debug(2, "statistics_requester status is %d.", config.statistics_requested); debug(2, "daemon status is %d.", config.daemonise); debug(2, "rtsp listening port is %d.", config.port); + debug(2, "udp base port is %d.", config.udp_port_base); + debug(2, "udp port range is %d.", config.udp_port_range); debug(2, "Shairport Sync player name is \"%s\".", config.apname); debug(2, "Audio Output name is \"%s\".", config.output_name); debug(2, "on-start action is \"%s\".", config.cmd_start); From 61315e61819f9495b9f1b8d0de5317f12866ef38 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 11 Jun 2015 10:20:10 +0000 Subject: [PATCH 093/220] Change the UDP port base to 6001 --- shairport.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shairport.c b/shairport.c index 2c5fcbdd..0eabc68f 100644 --- a/shairport.c +++ b/shairport.c @@ -372,7 +372,7 @@ int parse_options(int argc, char **argv) { /* 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 7000", + die("Invalid port number \"%sd\". It should be between 0 and 65535, default is 6001", value); else config.udp_port_base = value; @@ -624,7 +624,7 @@ int main(int argc, char **argv) { set_requested_connection_state_to_output( 1); // we expect to be able to connect to the output device config.audio_backend_buffer_desired_length = 6615; // 0.15 seconds. - config.udp_port_base = 7000; + config.udp_port_base = 6001; config.udp_port_range = 100; // this is a bit weird, but apparently necessary From 10b5b27c5bb0ad7963a7ac7474f7e632e1466e9d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 11 Jun 2015 12:30:24 +0100 Subject: [PATCH 094/220] Update RELEASENOTES.md --- RELEASENOTES.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index bcb21414..d18ee425 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,24 @@ +Version 2.3.4 +---- +* Enhancement + * When a play session starts, Shairport Sync opens three UDP ports to communicate with the source. Until now, those ports could be any high numbered port. Now, they are located within a range of 100 port locations starting at port 6001. The starting port and the port range are settable by two new general settings in `/etc/shairport-sync.conf` -- `udp_port_base` (default 6001) and `udp_port_range` (default 100). To retain the previous behaviour, set the `udp_port_base` to `0`. +* Bugfixes + * Fix an out-of-stack-space error that can occur in certain cases (thanks to https://github.com/joerg-krause). + * Fix a couple of compiler warnings (thanks to https://github.com/joerg-krause). + +Version 2.3.3.2 +---- +* Bugfix -- fixed an error in the sample configuration file. + +Version 2.3.3.1 +---- +* Enhancement + * Metadata format has changed slightly -- the format of each item is now `........`, where the `..` part is present if the length is non-zero. The change is that everything is now enclosed in an `..` pair. + +Version 2.3.2 and 2.3.3 +---- +These releases were faulty and have been deleted. + Version 2.3.1 ----- Some big changes "under the hood" have been made, leading to limited support for unsynchronised output to `stdout` or to a named pipe and continuation of defacto support for unsynchronised PulseAudio. Also, support for a configuration file in preference to command line options, an option to ignore volume control and other improvements are provided. From 1da19c3bf9546aaf4973088887a4badd267a5380 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 11 Jun 2015 12:30:52 +0100 Subject: [PATCH 095/220] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 635ff6d9..d2f4771c 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.3], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.4], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 2514239ee572606eb6678f1d3a459d5ed7df78ad Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 11 Jun 2015 12:32:22 +0100 Subject: [PATCH 096/220] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index d2f4771c..635ff6d9 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.4], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.3], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 9719685b8b1f98d4a5c82d999f0bf421efae9064 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 11 Jun 2015 12:33:31 +0100 Subject: [PATCH 097/220] Bump bug fix number --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 635ff6d9..d2f4771c 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.3], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.4], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From dfb04d2c15f287396326a1cea4efc7358926d4d2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 13 Jun 2015 20:05:44 +0100 Subject: [PATCH 098/220] Delete shairport-sync.conf~ Shouldn't be here. --- scripts/shairport-sync.conf~ | 75 ------------------------------------ 1 file changed, 75 deletions(-) delete mode 100644 scripts/shairport-sync.conf~ diff --git a/scripts/shairport-sync.conf~ b/scripts/shairport-sync.conf~ deleted file mode 100644 index 797e8e4f..00000000 --- a/scripts/shairport-sync.conf~ +++ /dev/null @@ -1,75 +0,0 @@ -// Sample Configuration File for Shairport Sync -// Commented out settings are generally the defaults, except where noted. - -// General Settings -general = -{ -// name = "Shairport Sync Player"; // This is the name the service will advertise to iTunes. The default is "Shairport Sync on " -// mdns_backend = "avahi"; // not used, not tested -// output_backend = "alsa"; // alsa is default, other possibilities are "stdout", "pulse", "dummy". Only alsa supports synchronisation. -// port = 5000; -// password = "secret"; // default is no password -// interpolation = "basic"; // aka "stuffing". Default is "basic", alternative is "soxr" -// statistics = "no"; // print statistics in the log -// drift = 88; // allow this number of frames of drift before correcting it -// resync_threshold = 2205; // a synchronisation error greater than this will cause resynchronisation; 0 disables it -// log_verbosity = 0; // "0" means no verbosity, "3" is most verbose. -// ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. -}; - -// Latencies for different sources. -latencies = -{ -// default = 88200; -// itunes = 99400; -// airplay = 88200; -// forkedDaapd = 99400; -}; - - -// How to deal with metadata, including artwork -metadata = -{ -// enabled = "no"; -// include_cover_art = "no"; -// pipe_name = "/tmp/shairport-sync-metadata"; -}; - -// Advanced parameters for controlling how a Shairport Sync runs -sessioncontrol = -{ -// run_this_before_play_begins = "/path/to/application and args"; -// run_this_after_play_ends = "/path/to/application and args"; -// wait_for_completion = "no"; -// allow_session_interruption = "no"; -// session_timeout = 120; -}; - -// -// Back End Settings -// - -// These are parameters for the alsa back end, the only back end that supports synchronisation -{ -// output_device = "default"; -// mixer_type = "software"; // "software" or "hardware" -// mixer_device = "default"; //actually, the mixer default is whatever the output_device is. Normally you wouldn't have to use this. -// mixer_control_name = "PCM"; // the name of the mixer to use -- there is no default. -// audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. -// audio_backend_buffer_desired_length = 6615; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. -}; - -// These are parameters for the pipe back end, an experimental back end that directs output to a pipe. -pipe = -{ -// audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. -// audio_backend_buffer_desired_length = 44100; -// name = "/path/to/pipe"; // there is no default pipe name for the output -}; - -// These are parameters for the stdout back end, an experimental back end that directs output to stdout. -stdout = -{ -// audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. -// audio_backend_buffer_desired_length = 44100; -}; From bb0726bdae3b3ce348762da230a580dc08e945cb Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 13 Jun 2015 20:06:59 +0100 Subject: [PATCH 099/220] Update shairport-sync.conf Default port base is 6001. --- scripts/shairport-sync.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 7104cb71..391c84d2 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -8,7 +8,7 @@ general = // mdns_backend = "avahi"; // not used, not tested // output_backend = "alsa"; // alsa is default, other possibilities are "stdout", "pulse", "dummy". Only alsa supports synchronisation. // port = 5000; -// udp_port_base = 7000; // start allocation UDP ports from this port number +// udp_port_base = 6001; // start allocation UDP ports from this port number // udp_port_range = 100; // look for free ports in this number of places, starting at the UDP port base (only three are needed). // password = "secret"; // default is no password // interpolation = "basic"; // aka "stuffing". Default is "basic", alternative is "soxr" From 154c6b583954566554aad9f115605f2360e371a7 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 13 Jun 2015 20:07:48 +0100 Subject: [PATCH 100/220] Update shairport-sync.conf Tidy indentation --- scripts/shairport-sync.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 391c84d2..3bfcde5b 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -8,8 +8,8 @@ general = // mdns_backend = "avahi"; // not used, not tested // output_backend = "alsa"; // alsa is default, other possibilities are "stdout", "pulse", "dummy". Only alsa supports synchronisation. // port = 5000; -// udp_port_base = 6001; // start allocation UDP ports from this port number -// udp_port_range = 100; // look for free ports in this number of places, starting at the UDP port base (only three are needed). +// udp_port_base = 6001; // start allocation UDP ports from this port number +// udp_port_range = 100; // look for free ports in this number of places, starting at the UDP port base (only three are needed). // password = "secret"; // default is no password // interpolation = "basic"; // aka "stuffing". Default is "basic", alternative is "soxr" // statistics = "no"; // print statistics in the log From 0da133ca23ea4823e5ea3052821e9f3fbac7f57f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 15 Jun 2015 11:43:41 +0000 Subject: [PATCH 101/220] Add messages to see what's missing in whaale's announce message --- rtsp.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/rtsp.c b/rtsp.c index 3cf03d85..3144eeec 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1112,6 +1112,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_m static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { // interrupt session if permitted if ((config.allow_session_interruption == 1) || (pthread_mutex_trylock(&play_lock) == 0)) { + resp->respcode = 456; // 456 - Header Field Not Valid for Resource char *paesiv = NULL; char *prsaaeskey = NULL; char *pfmtp = NULL; @@ -1133,7 +1134,19 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag cp = next; } + + if (paesiv==NULL) { + warn("AESIV missing from ANNOUNCE"); + } + if (prsaaeskey==NULL) { + warn("RSAAESKEY missing from ANNOUNCE"); + } + + if (pfmtp==NULL) { + warn("FMTP missing from ANNOUNCE"); + } + if (!paesiv || !prsaaeskey || !pfmtp) { warn("required params missing from announce"); goto out; From 5c33488eafb951a3be3f99519e926d422609018c Mon Sep 17 00:00:00 2001 From: Mke Brady Date: Mon, 15 Jun 2015 21:33:35 +0100 Subject: [PATCH 102/220] Print our whole meessage content on error --- rtsp.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/rtsp.c b/rtsp.c index 3144eeec..20c378a5 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1148,7 +1148,19 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag } if (!paesiv || !prsaaeskey || !pfmtp) { - warn("required params missing from announce"); + warn("required params missing from the following ANNOUNCE message:"); + // print each line of the request content + // the problem is that nextline has replace all returns, newlines, etc. by NULLs + char *cp = req->content; + int cp_left = req->contentlength; + while (cp_left>1) { + if (strlen(cp)!=0) + warn(" %s",cp); + cp+=strlen(cp)+1; + cp_left-=strlen(cp)+1; + } + char *next; + goto out; } From a54569f3183c4422c644b05b5b835d04d066fcff Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 16 Jun 2015 14:08:56 +0000 Subject: [PATCH 103/220] Tidy up handling of config file name and debug report --- common.h | 2 + new-rtsp.c | 1616 +++++++++++++++++++++++++++++++++++++++++++++++++++ newcfg.ac | 232 ++++++++ shairport.c | 37 +- 4 files changed, 1871 insertions(+), 16 deletions(-) create mode 100644 new-rtsp.c create mode 100644 newcfg.ac diff --git a/common.h b/common.h index 792dd36c..32f4dc35 100644 --- a/common.h +++ b/common.h @@ -77,7 +77,9 @@ typedef struct { char *pidfile; char *logfile; char *errfile; +#ifdef SUPPORT_CONFIG_FILES char *configfile; +#endif uint audio_backend_buffer_desired_length; // this will be the desired number of frames in the // audio backend buffer -- the DAC buffer for ALSA uint audio_backend_latency_offset; // this will be the offset to compensate for any fixed latency diff --git a/new-rtsp.c b/new-rtsp.c new file mode 100644 index 00000000..bf30f47f --- /dev/null +++ b/new-rtsp.c @@ -0,0 +1,1616 @@ +/* + * RTSP protocol handler. This file is part of Shairport. + * Copyright (c) James Laird 2013 + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#ifdef HAVE_LIBSSL +#include +#endif + +#ifdef HAVE_LIBPOLARSSL +#include +#endif + +#include "common.h" +#include "player.h" +#include "rtp.h" +#include "mdns.h" + +#ifdef AF_INET6 +#define INETx_ADDRSTRLEN INET6_ADDRSTRLEN +#else +#define INETx_ADDRSTRLEN INET_ADDRSTRLEN +#endif + +enum rtsp_read_request_response { + rtsp_read_request_response_ok, + rtsp_read_request_response_shutdown_requested, + rtsp_read_request_response_bad_packet, + rtsp_read_request_response_error +}; + +// Mike Brady's part... +static pthread_mutex_t play_lock = PTHREAD_MUTEX_INITIALIZER; + +// every time we want to retain or release a reference count, lock it with this +// if a reference count is read as zero, it means the it's being deallocated. +static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER; + +// only one thread is allowed to use the player at once. +// it monitors the request variable (at least when interrupted) +static pthread_mutex_t playing_mutex = PTHREAD_MUTEX_INITIALIZER; +static int please_shutdown = 0; +static pthread_t playing_thread = 0; + +typedef struct { + int fd; + stream_cfg stream; + SOCKADDR remote; + int running; + pthread_t thread; +} rtsp_conn_info; + +#ifdef CONFIG_METADATA +typedef struct { + pthread_mutex_t pc_queue_lock; + pthread_cond_t pc_queue_item_added_signal; + pthread_cond_t pc_queue_item_removed_signal; + size_t item_size; // number of bytes in each item + uint32_t count; // number of items in the queue + uint32_t capacity; // maximum number of items + uint32_t toq; // first item to take + uint32_t eoq; // free space at end of queue + void *items; // a pointer to where the items are actually stored +} pc_queue; // producer-consumer queue +#endif + +typedef struct { + uint32_t referenceCount; // we might start using this... + int nheaders; + char *name[16]; + char *value[16]; + + int contentlength; + char *content; + + // for requests + char method[16]; + + // for responses + int respcode; +} rtsp_message; + +#ifdef CONFIG_METADATA +typedef struct { + uint32_t type; + uint32_t code; + char *data; + uint32_t length; + rtsp_message *carrier; +} metadata_package; + +void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items) { + the_queue->item_size = item_size; + the_queue->items = items; + the_queue->count = 0; + the_queue->capacity = number_of_items; + the_queue->toq = 0; + the_queue->eoq = 0; +} + +int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier, + int block); + +int send_ssnc_metadata(uint32_t code, char *data, uint32_t length, int block) { + return send_metadata('ssnc', code, data, length, NULL, block); +} + +int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) { + int rc; + if (the_queue) { + if (block == 0) { + rc = pthread_mutex_trylock(&the_queue->pc_queue_lock); + if (rc == EBUSY) + return EBUSY; + } else + rc = pthread_mutex_lock(&the_queue->pc_queue_lock); + if (rc) + debug(1, "Error locking for pc_queue_add_item"); + while (the_queue->count == the_queue->capacity) { + rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal, &the_queue->pc_queue_lock); + if (rc) + debug(1, "Error waiting for item to be removed"); + } + uint32_t i = the_queue->eoq; + void *p = the_queue->items + the_queue->item_size * i; + // void * p = &the_queue->qbase + the_queue->item_size*the_queue->eoq; + memcpy(p, the_stuff, the_queue->item_size); + + // update the pointer + i++; + if (i == the_queue->capacity) + // fold pointer if necessary + i = 0; + the_queue->eoq = i; + the_queue->count++; + if (the_queue->count == the_queue->capacity) + debug(1, "pc_queue is full!"); + rc = pthread_cond_signal(&the_queue->pc_queue_item_added_signal); + if (rc) + debug(1, "Error signalling after pc_queue_add_item"); + rc = pthread_mutex_unlock(&the_queue->pc_queue_lock); + if (rc) + debug(1, "Error unlocking for pc_queue_add_item"); + } else { + debug(1, "Adding an item to a NULL queue"); + } + return 0; +} + +int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) { + int rc; + if (the_queue) { + rc = pthread_mutex_lock(&the_queue->pc_queue_lock); + if (rc) + debug(1, "Error locking for pc_queue_get_item"); + while (the_queue->count == 0) { + rc = pthread_cond_wait(&the_queue->pc_queue_item_added_signal, &the_queue->pc_queue_lock); + if (rc) + debug(1, "Error waiting for item to be added"); + } + uint32_t i = the_queue->toq; + // void * p = &the_queue->qbase + the_queue->item_size*the_queue->toq; + void *p = the_queue->items + the_queue->item_size * i; + memcpy(the_stuff, p, the_queue->item_size); + + // update the pointer + i++; + if (i == the_queue->capacity) + // fold pointer if necessary + i = 0; + the_queue->toq = i; + the_queue->count--; + rc = pthread_cond_signal(&the_queue->pc_queue_item_removed_signal); + if (rc) + debug(1, "Error signalling after pc_queue_removed_item"); + rc = pthread_mutex_unlock(&the_queue->pc_queue_lock); + if (rc) + debug(1, "Error unlocking for pc_queue_get_item"); + } else { + debug(1, "Removing an item from a NULL queue"); + } + return 0; +} + +#endif + +// determine if we are the currently playing thread +static inline int rtsp_playing(void) { + if (pthread_mutex_trylock(&playing_mutex)) { + return pthread_equal(playing_thread, pthread_self()); + } else { + pthread_mutex_unlock(&playing_mutex); + return 0; + } +} + +void rtsp_request_shutdown_stream(void) { + please_shutdown = 1; + pthread_kill(playing_thread, SIGUSR1); +} + +static void rtsp_take_player(void) { + if (rtsp_playing()) + return; + + if (pthread_mutex_trylock(&playing_mutex)) { + debug(1, "shutting down playing thread."); + // XXX minor race condition between please_shutdown and signal delivery + please_shutdown = 1; + pthread_kill(playing_thread, SIGUSR1); + pthread_mutex_lock(&playing_mutex); + } + playing_thread = pthread_self(); // make us the currently-playing thread (why?) +} + +void rtsp_shutdown_stream(void) { + rtsp_take_player(); + pthread_mutex_unlock(&playing_mutex); +} + +// keep track of the threads we have spawned so we can join() them +static rtsp_conn_info **conns = NULL; +static int nconns = 0; +static void track_thread(rtsp_conn_info *conn) { + conns = realloc(conns, sizeof(rtsp_conn_info *) * (nconns + 1)); + conns[nconns] = conn; + nconns++; +} + +static void cleanup_threads(void) { + void *retval; + int i; + debug(2, "culling threads."); + for (i = 0; i < nconns;) { + if (conns[i]->running == 0) { + pthread_join(conns[i]->thread, &retval); + free(conns[i]); + debug(2, "one joined..."); + nconns--; + if (nconns) + conns[i] = conns[nconns]; + } else { + i++; + } + } +} + +// park a null at the line ending, and return the next line pointer +// accept \r, \n, or \r\n +static char *nextline(char *in, int inbuf) { + char *out = NULL; + while (inbuf) { + if (*in == '\r') { + *in++ = 0; + out = in; + } + if (*in == '\n') { + *in++ = 0; + out = in; + } + + if (out) + break; + + in++; + inbuf--; + } + return out; +} + +static void msg_retain(rtsp_message *msg) { + if (msg) { + int rc = pthread_mutex_lock(&reference_counter_lock); + if (rc) + debug(1, "Error %d locking reference counter lock"); + msg->referenceCount++; + rc = pthread_mutex_unlock(&reference_counter_lock); + if (rc) + debug(1, "Error %d unlocking reference counter lock"); + } else { + debug(1, "null rtsp_message pointer passed to retain"); + } +} + +static rtsp_message *msg_init(void) { + rtsp_message *msg = malloc(sizeof(rtsp_message)); + memset(msg, 0, sizeof(rtsp_message)); + msg->referenceCount = 1; // from now on, any access to this must be protected with the lock + return msg; +} + +static int msg_add_header(rtsp_message *msg, char *name, char *value) { + if (msg->nheaders >= sizeof(msg->name) / sizeof(char *)) { + warn("too many headers?!"); + return 1; + } + + msg->name[msg->nheaders] = strdup(name); + msg->value[msg->nheaders] = strdup(value); + msg->nheaders++; + + return 0; +} + +static char *msg_get_header(rtsp_message *msg, char *name) { + int i; + for (i = 0; i < msg->nheaders; i++) + if (!strcasecmp(msg->name[i], name)) + return msg->value[i]; + return NULL; +} + +static void msg_print_debug_headers(rtsp_message *msg) { + int i; + for (i = 0; i < msg->nheaders; i++) { + debug(1, " Type: \"%s\", content: \"%s\"", msg->name[i], msg->value[i]); + } +} + +static void msg_free(rtsp_message *msg) { + + if (msg) { + int rc = pthread_mutex_lock(&reference_counter_lock); + if (rc) + debug(1, "Error %d locking reference counter lock during msg_free()", rc); + msg->referenceCount--; + rc = pthread_mutex_unlock(&reference_counter_lock); + if (rc) + debug(1, "Error %d unlocking reference counter lock during msg_free()", rc); + if (msg->referenceCount == 0) { + int i; + for (i = 0; i < msg->nheaders; i++) { + free(msg->name[i]); + free(msg->value[i]); + } + if (msg->content) + free(msg->content); + free(msg); + } // else { + // debug(1,"rtsp_message reference count non-zero: %d!",msg->referenceCount); + //} + } else { + debug(1, "null rtsp_message pointer passed to msg_free()"); + } +} + +static int msg_handle_line(rtsp_message **pmsg, char *line) { + rtsp_message *msg = *pmsg; + + if (!msg) { + msg = msg_init(); + *pmsg = msg; + char *sp, *p; + + // debug(1, "received request: %s", line); + + p = strtok_r(line, " ", &sp); + if (!p) + goto fail; + strncpy(msg->method, p, sizeof(msg->method) - 1); + + p = strtok_r(NULL, " ", &sp); + if (!p) + goto fail; + + p = strtok_r(NULL, " ", &sp); + if (!p) + goto fail; + if (strcmp(p, "RTSP/1.0")) + goto fail; + + return -1; + } + + if (strlen(line)) { + char *p; + p = strstr(line, ": "); + if (!p) { + warn("bad header: >>%s<<", line); + goto fail; + } + *p = 0; + p += 2; + msg_add_header(msg, line, p); + debug(2, " %s: %s.", line, p); + return -1; + } else { + char *cl = msg_get_header(msg, "Content-Length"); + if (cl) + return atoi(cl); + else + return 0; + } + +fail: + *pmsg = NULL; + msg_free(msg); + return 0; +} + +static enum rtsp_read_request_response rtsp_read_request(int fd, rtsp_message **the_packet) { + enum rtsp_read_request_response reply = rtsp_read_request_response_ok; + ssize_t buflen = 512; + char *buf = malloc(buflen + 1); + + rtsp_message *msg = NULL; + + ssize_t nread; + ssize_t inbuf = 0; + int msg_size = -1; + + while (msg_size < 0) { + if (please_shutdown) { + debug(1, "RTSP shutdown requested."); + reply = rtsp_read_request_response_shutdown_requested; + goto shutdown; + } + nread = read(fd, buf + inbuf, buflen - inbuf); + if (!nread) { + debug(1, "RTSP connection closed."); + reply = rtsp_read_request_response_shutdown_requested; + goto shutdown; + } + if (nread < 0) { + if (errno == EINTR) + continue; + perror("read failure"); + reply = rtsp_read_request_response_error; + goto shutdown; + } + inbuf += nread; + + char *next; + while (msg_size < 0 && (next = nextline(buf, inbuf))) { + msg_size = msg_handle_line(&msg, buf); + + if (!msg) { + warn("no RTSP header received"); + reply = rtsp_read_request_response_bad_packet; + goto shutdown; + } + + inbuf -= next - buf; + if (inbuf) + memmove(buf, next, inbuf); + } + } + + if (msg_size > buflen) { + buf = realloc(buf, msg_size); + if (!buf) { + warn("too much content"); + reply = rtsp_read_request_response_error; + goto shutdown; + } + buflen = msg_size; + } + + uint64_t threshold_time = + get_absolute_time_in_fp() + ((uint64_t)5 << 32); // i.e. five seconds from now + int warning_message_sent = 0; + + const size_t max_read_chunk = 50000; + while (inbuf < msg_size) { + + // we are going to read the stream in chunks and time how long it takes to do so. + // If it's taking too long, (and we find out about it), we will send an error message as + // metadata + + if (warning_message_sent == 0) { + uint64_t time_now = get_absolute_time_in_fp(); + if (time_now > threshold_time) { // it's taking too long + debug(1, "Error receiving metadata from source -- transmission seems to be stalled."); +#ifdef CONFIG_METADATA + send_ssnc_metadata('stal', NULL, 0, 1); +#endif + warning_message_sent = 1; + } + } + ssize_t read_chunk = msg_size - inbuf; + if (read_chunk > max_read_chunk) + read_chunk = max_read_chunk; + nread = read(fd, buf + inbuf, read_chunk); + if (!nread) { + reply = rtsp_read_request_response_error; + goto shutdown; + } + if (nread == EINTR) + continue; + if (nread < 0) { + perror("read failure"); + reply = rtsp_read_request_response_error; + goto shutdown; + } + inbuf += nread; + } + + msg->contentlength = inbuf; + msg->content = buf; + *the_packet = msg; + return reply; + +shutdown: + if (msg) { + msg_free(msg); // which will free the content and everything else + } + // in case the message wasn't formed or wasn't fully initialised + if ((msg) && (msg->content == NULL) || (!msg)) + free(buf); + *the_packet = NULL; + return reply; +} + +static void msg_write_response(int fd, rtsp_message *resp) { + char pkt[1024]; + int pktfree = sizeof(pkt); + char *p = pkt; + int i, n; + + n = snprintf(p, pktfree, "RTSP/1.0 %d %s\r\n", resp->respcode, + resp->respcode == 200 ? "OK" : "Error"); + // debug(1, "sending response: %s", pkt); + pktfree -= n; + p += n; + + for (i = 0; i < resp->nheaders; i++) { + debug(2, " %s: %s.", resp->name[i], resp->value[i]); + n = snprintf(p, pktfree, "%s: %s\r\n", resp->name[i], resp->value[i]); + pktfree -= n; + p += n; + if (pktfree <= 0) + die("Attempted to write overlong RTSP packet"); + } + + if (pktfree < 3) + die("Attempted to write overlong RTSP packet"); + + strcpy(p, "\r\n"); + int ignore = write(fd, pkt, p - pkt + 2); +} + +static void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { + resp->respcode = 200; + msg_add_header(resp, "Audio-Latency", "88200"); +} + +static void handle_options(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { + resp->respcode = 200; + msg_add_header(resp, "Public", "ANNOUNCE, SETUP, RECORD, " + "PAUSE, FLUSH, TEARDOWN, " + "OPTIONS, GET_PARAMETER, SET_PARAMETER"); +} + +static void handle_teardown(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { + if (!rtsp_playing()) + return; + resp->respcode = 200; + msg_add_header(resp, "Connection", "close"); + please_shutdown = 1; +} + +static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { + if (!rtsp_playing()) + return; + char *p; + uint32_t rtptime = 0; + char *hdr = msg_get_header(req, "RTP-Info"); + + if (hdr) { + // debug(1,"FLUSH message received: \"%s\".",hdr); + // get the rtp timestamp + p = strstr(hdr, "rtptime="); + if (p) { + p = strchr(p, '=') + 1; + if (p) + rtptime = uatoi(p); // unsigned integer -- up to 2^32-1 + } + } + // debug(1,"RTSP Flush Requested: %u.",rtptime); + player_flush(rtptime); + resp->respcode = 200; +} + +static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { + int cport, tport; + int lsport, lcport, ltport; + uint32_t active_remote = 0; + + char *ar = msg_get_header(req, "Active-Remote"); + if (ar) { + // debug(1,"Active-Remote string seen: \"%s\".",ar); + // get the active remote + char *p; + active_remote = strtoul(ar, &p, 10); + // debug(1,"Active Remote is %u.",active_remote); + } + + // select latency + // if iTunes V10 or later is detected, use the iTunes latency setting + // if AirPlay is detected, use the AirPlay latency setting + // for everything else, use the general latency setting, if given, or + // else use the default latency setting + + config.latency = 88200; + + if (config.userSuppliedLatency) + config.latency = config.userSuppliedLatency; + + char *ua = msg_get_header(req, "User-Agent"); + if (ua == 0) { + debug(1, "No User-Agent string found in the SETUP message. Using latency of %d frames.", + config.latency); + } else { + if (strstr(ua, "iTunes") == ua) { + int iTunesVersion = 0; + // now check it's version 10 or later + char *pp = strchr(ua, '/') + 1; + if (pp) + iTunesVersion = atoi(pp); + else + debug(2, "iTunes Version Number not found."); + if (iTunesVersion >= 10) { + debug(2, "User-Agent is iTunes 10 or better, (actual version is %d); selecting the iTunes " + "latency of %d frames.", + iTunesVersion, config.iTunesLatency); + config.latency = config.iTunesLatency; + } + } else if (strstr(ua, "AirPlay") == ua) { + debug(2, "User-Agent is AirPlay; selecting the AirPlay latency of %d frames.", + config.AirPlayLatency); + config.latency = config.AirPlayLatency; + } else if (strstr(ua, "forked-daapd") == ua) { + debug(2, "User-Agent is forked-daapd; selecting the forked-daapd latency of %d frames.", + config.ForkedDaapdLatency); + config.latency = config.ForkedDaapdLatency; + } else { + debug(2, "Unrecognised User-Agent. Using latency of %d frames.", config.latency); + } + } + char *hdr = msg_get_header(req, "Transport"); + if (!hdr) + goto error; + + char *p; + p = strstr(hdr, "control_port="); + if (!p) + goto error; + p = strchr(p, '=') + 1; + cport = atoi(p); + + p = strstr(hdr, "timing_port="); + if (!p) + goto error; + p = strchr(p, '=') + 1; + tport = atoi(p); + + rtsp_take_player(); + rtp_setup(&conn->remote, cport, tport, active_remote, &lsport, &lcport, <port); + if (!lsport) + goto error; + char *q; + p = strstr(hdr, "control_port="); + if (p) { + q = strchr(p, ';'); // get past the control port entry + *p++ = 0; + if (q++) + strcat(hdr, q); // should unsplice the control port entry + } + p = strstr(hdr, "timing_port="); + if (p) { + q = strchr(p, ';'); // get past the timing port entry + *p++ = 0; + if (q++) + strcat(hdr, q); // should unsplice the timing port entry + } + + player_play(&conn->stream); + + char *resphdr = alloca(200); + *resphdr = 0; + sprintf(resphdr, "RTP/AVP/" + "UDP;unicast;interleaved=0-1;mode=record;control_port=%d;timing_port=%d;server_" + "port=%d", + lcport, ltport, lsport); + + msg_add_header(resp, "Transport", resphdr); + + msg_add_header(resp, "Session", "1"); + + resp->respcode = 200; + return; + +error: + warn("Error in setup request."); + pthread_mutex_unlock(&play_lock); + resp->respcode = 451; // invalid arguments +} + +static void handle_ignore(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { + resp->respcode = 200; +} + +static void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req, + rtsp_message *resp) { + char *cp = req->content; + int cp_left = req->contentlength; + char *next; + while (cp_left && cp) { + next = nextline(cp, cp_left); + cp_left -= next - cp; + + if (!strncmp(cp, "volume: ", 8)) { + if (config.ignore_volume_control == 0) { + float volume = atof(cp + 8); + debug(2, "volume: %f\n", volume); + player_volume(volume); + } + } else +#ifdef CONFIG_METADATA + if (!strncmp(cp, "progress: ", 10)) { + char *progress = cp + 10; + debug(2, "progress: \"%s\"\n", + progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per second + send_ssnc_metadata('prgr', strdup(progress), strlen(progress), 1); + } else +#endif + { + debug(1, "unrecognised parameter: \"%s\" (%d)\n", cp, strlen(cp)); + } + cp = next; + } +} + +#ifdef CONFIG_METADATA +// Metadata is not used by shairport-sync. +// Instead we send all metadata to a fifo pipe, so that other apps can listen to the pipe and use +// the metadata. + +// We use two 4-character codes to identify each piece of data and we send the data itself, if any, +// in base64 form. + +// The first 4-character code, called the "type", is either: +// 'core' for all the regular metadadata coming from iTunes, etc., or +// 'ssnc' (for 'shairport-sync') for all metadata coming from Shairport Sync itself, such as +// start/end delimiters, etc. + +// For 'core' metadata, the second 4-character code is the 4-character metadata code coming from +// iTunes etc. +// For 'ssnc' metadata, the second 4-character code is used to distinguish the messages. + +// Cover art is not tagged in the same way as other metadata, it seems, so is sent as an 'ssnc' type +// metadata message with the code 'PICT' +// Here are the 'ssnc' codes defined so far: +// 'PICT' -- the payload is a picture, either a JPEG or a PNG. Check the first few bytes to see +// which. +// 'pbeg' -- play stream begin. No arguments +// 'pend' -- play stream end. No arguments +// 'pfls' -- play stream flush. No arguments +// 'prsm' -- play stream resume. No arguments +// 'pvol' -- play volume. The volume is sent as a string -- +// "airplay_volume,volume,lowest_volume,highest_volume,has_true_mute,is_muted" +// volume, lowest_volume and highest_volume are given in dB +// is_muted is 1 if [true] mute is enabled, 0 otherwise. +// The "airplay_volume" is what's sent to the player, and is from 0.00 down to -30.00, +// with -144.00 meaning mute. +// This is linear on the volume control slider of iTunes or iOS AirPLay +// 'prgr' -- progress -- this is metadata from AirPlay consisting of RTP timestamps for the start +// of the current play sequence, the current play point and the end of the play sequence. +// I guess the timestamps wrap at 2^32. +// 'mdst' -- a sequence of metadata is about to start +// 'mden' -- a sequence of metadata has ended +// 'snam' -- the name of the originator -- e.g. "Joe's iPhone" or "iTunes...". +// +// including a simple base64 encoder to minimise malloc/free activity + +// From Stack Overflow, with thanks: +// http://stackoverflow.com/questions/342409/how-do-i-base64-encode-decode-in-c +// minor mods to make independent of C99. +// more significant changes make it not malloc memory +// needs to initialise the docoding table first + +// add _so to end of name to avoid confusion with polarssl's implementation + +static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; + +static int mod_table[] = {0, 2, 1}; + +// pass in a pointer to the data, its length, a pointer to the output buffer and a pointer to an int +// containing its maximum length +// the actual length will be returned. + +char *base64_encode_so(const unsigned char *data, size_t input_length, char *encoded_data, + size_t *output_length) { + + size_t calculated_output_length = 4 * ((input_length + 2) / 3); + if (calculated_output_length > *output_length) + return (NULL); + *output_length = calculated_output_length; + + int i, j; + for (i = 0, j = 0; i < input_length;) { + + uint32_t octet_a = i < input_length ? (unsigned char)data[i++] : 0; + uint32_t octet_b = i < input_length ? (unsigned char)data[i++] : 0; + uint32_t octet_c = i < input_length ? (unsigned char)data[i++] : 0; + + uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; + + encoded_data[j++] = encoding_table[(triple >> 3 * 6) & 0x3F]; + encoded_data[j++] = encoding_table[(triple >> 2 * 6) & 0x3F]; + encoded_data[j++] = encoding_table[(triple >> 1 * 6) & 0x3F]; + encoded_data[j++] = encoding_table[(triple >> 0 * 6) & 0x3F]; + } + + for (i = 0; i < mod_table[input_length % 3]; i++) + encoded_data[*output_length - 1 - i] = '='; + + return encoded_data; +} + +// with thanks! +// + +static int fd = -1; +static int dirty = 0; +pc_queue metadata_queue; +#define metadata_queue_size 500 +metadata_package metadata_queue_items[metadata_queue_size]; + +static pthread_t metadata_thread; + +void metadata_create(void) { + if (config.metadata_enabled == 0) + return; + + size_t pl = strlen(config.metadata_pipename) + 1; + + char *path = malloc(pl + 1); + snprintf(path, pl + 1, "%s", config.metadata_pipename); + + if (mkfifo(path, 0644) && errno != EEXIST) + die("Could not create metadata FIFO %s", path); + + free(path); +} + +void metadata_open(void) { + if (config.metadata_enabled == 0) + return; + + size_t pl = strlen(config.metadata_pipename) + 1; + + char *path = malloc(pl + 1); + snprintf(path, pl + 1, "%s", config.metadata_pipename); + + fd = open(path, O_WRONLY | O_NONBLOCK); + // if (fd < 0) + // debug(1, "Could not open metadata FIFO %s. Will try again later.", path); + + free(path); +} + +static void metadata_close(void) { + close(fd); + fd = -1; +} + +ssize_t non_blocking_write(int fd, const void *buf, size_t count) { + // debug(1,"writing %u to pipe...",count); + // we are assuming that the count is always smaller than the FIFO's buffer + struct pollfd ufds[1]; + ssize_t reply; + do { + ufds[0].fd = fd; + ufds[0].events = POLLOUT; + int rv = poll(ufds, 1, 5000); + if (rv == -1) + debug(1, "error waiting for pipe to unblock..."); + if (rv == 0) + debug(1, "timeout waiting for pipe to unblock"); + reply = write(fd, buf, count); + if ((reply == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) + debug(1, "writing to pipe will block..."); + // else + // debug(1,"writing %u to pipe done...",reply); + } while ((reply == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))); + return reply; + + // return write(fd,buf,count); +} + +void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) { + debug(2, "Process metadata with type %x, code %x and length %u.", type, code, length); + int ret; + // readers may go away and come back + if (fd < 0) + metadata_open(); + if (fd < 0) + return; + char thestring[1024]; + snprintf(thestring, 1024, "\n%x%x%u\n", type, code, + length); + ret = non_blocking_write(fd, thestring, strlen(thestring)); + if (ret < 1) + return; + if ((data != NULL) && (length > 0)) { + snprintf(thestring, 1024, "\n"); + ret = non_blocking_write(fd, thestring, strlen(thestring)); + if (ret < 1) // no reader + return; + // here, we write the data in base64 form using our nice base64 encoder + // but, we break it into lines of 76 output characters, except for the last one. + // thus, we send groups of (76/4)*3 = 57 bytes to the encoder at a time + size_t remaining_count = length; + char *remaining_data = data; + size_t towrite_count; + char outbuf[76]; + while ((remaining_count) && (ret >= 0)) { + size_t towrite_count = remaining_count; + if (towrite_count > 57) + towrite_count = 57; + size_t outbuf_size = 76; // size of output buffer on entry, length of result on exit + if (base64_encode_so(remaining_data, towrite_count, outbuf, &outbuf_size) == NULL) + debug(1, "Error encoding base64 data."); + // debug(1,"Remaining count: %d ret: %d, outbuf_size: %d.",remaining_count,ret,outbuf_size); + ret = non_blocking_write(fd, outbuf, outbuf_size); + if (ret < 0) + return; + remaining_data += towrite_count; + remaining_count -= towrite_count; + } + snprintf(thestring, 1024, "\n\n"); + ret = non_blocking_write(fd, thestring, strlen(thestring)); + if (ret < 1) // no reader + return; + } +} + +void *metadata_thread_function(void *ignore) { + metadata_create(); + metadata_package pack; + while (1) { + pc_queue_get_item(&metadata_queue, &pack); + if (config.metadata_enabled) + metadata_process(pack.type, pack.code, pack.data, pack.length); + if (pack.carrier) + msg_free(pack.carrier); // release the message + else if (pack.data) + free(pack.data); + } + pthread_exit(NULL); +} + +void metadata_init(void) { + // create a pc_queue for passing information to a threaded metadata handler + pc_queue_init(&metadata_queue, (char *)&metadata_queue_items, sizeof(metadata_package), + metadata_queue_size); + int ret = pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL); + if (ret) + debug(1, "Failed to create metadata thread!"); +} + +int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier, + int block) { + metadata_package pack; + pack.type = type; + pack.code = code; + pack.data = data; + pack.length = length; + if (carrier) + msg_retain(carrier); + pack.carrier = carrier; + int rc = pc_queue_add_item(&metadata_queue, &pack, block); + if ((rc == EBUSY) && (carrier)) + msg_free(carrier); + if (rc == EBUSY) + warn("Metadata queue is busy, dropping message of type 0x%08X, code 0x%08X.", type, code); + return rc; +} + +static void handle_set_parameter_metadata(rtsp_conn_info *conn, rtsp_message *req, + rtsp_message *resp) { + char *cp = req->content; + int cl = req->contentlength; + + unsigned int off = 8; + + // inform the listener that a set of metadata is starting + // this doesn't include the cover art though... + + // parameters: type, code, pointer to data or NULL, length of data or NULL, the rtsp_message or + // NULL + // the rtsp_message is sent for 'core' messages, because it contains the data and must not be + // freed until the data has been read. So, it is passed to send_metadata to be retained, + // sent to the thread where metadata is processed and released (and probably freed). + + // The reading of the parameters is a bit complex + // If the rtsp_message field is non-null, then it represents an rtsp_message which should be freed + // in the thread handler when the parameter pointed to by the pointer and specified by the length + // is finished with + // If the rtsp_message is NULL, then if the pointer is non-null, it points to a malloc'ed block + // and should be freed when the thread is finished with it. The length of the data in the block is + // given in length + // If the rtsp_message is NULL and the pointer is also NULL, nothing further is done. + + send_metadata('ssnc', 'mdst', NULL, 0, NULL, 1); + + while (off < cl) { + // pick up the metadata tag as an unsigned longint + uint32_t itag = ntohl(*(uint32_t *)(cp + off)); + off += sizeof(uint32_t); + + // pick up the length of the data + uint32_t vl = ntohl(*(uint32_t *)(cp + off)); + off += sizeof(uint32_t); + + // pass the data over + if (vl == 0) + send_metadata('core', itag, NULL, 0, NULL, 1); + else + send_metadata('core', itag, (char *)(cp + off), vl, req, 1); + + // move on to the next item + off += vl; + } + + // inform the listener that a set of metadata is ending + send_metadata('ssnc', 'mden', NULL, 0, NULL, 1); + // send the user some shairport-originated metadata + // send the name of the player, e.g. "Joe's iPhone" or "iTunes" + send_metadata('ssnc', 'sndr', strdup(sender_name), strlen(sender_name), NULL, 1); +} + +#endif + +static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { + // if (!req->contentlength) + // debug(1, "received empty SET_PARAMETER request."); + + char *ct = msg_get_header(req, "Content-Type"); + + if (ct) { + debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct); +#ifdef CONFIG_METADATA + if (!strncmp(ct, "application/x-dmap-tagged", 25)) { + debug(2, "received metadata tags in SET_PARAMETER request."); + handle_set_parameter_metadata(conn, req, resp); + } else if (!strncmp(ct, "image", 5)) { + // debug(1, "received image in SET_PARAMETER request."); + // note: the image/type tag isn't reliable, so it's not being sent + // -- best look at the first few bytes of the image + send_metadata('ssnc', 'PICT', req->content, req->contentlength, req, 1); + } else +#endif + if (!strncmp(ct, "text/parameters", 15)) { + debug(2, "received parameters in SET_PARAMETER request."); + handle_set_parameter_parameter(conn, req, resp); + } else { + debug(1, "received unknown Content-Type \"%s\" in SET_PARAMETER request.", ct); + } + } else { + debug(1, "missing Content-Type header in SET_PARAMETER request."); + } + + resp->respcode = 200; +} + +static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { + // interrupt session if permitted + if ((config.allow_session_interruption == 1) || (pthread_mutex_trylock(&play_lock) == 0)) { + resp->respcode = 456; // 456 - Header Field Not Valid for Resource + char *paesiv = NULL; + char *prsaaeskey = NULL; + char *pfmtp = NULL; + char *cp = req->content; + int cp_left = req->contentlength; + char *next; + while (cp_left && cp) { + next = nextline(cp, cp_left); + cp_left -= next - cp; + + if (!strncmp(cp, "a=fmtp:", 7)) + pfmtp = cp + 7; + + if (!strncmp(cp, "a=aesiv:", 8)) + paesiv = cp + 8; + + if (!strncmp(cp, "a=rsaaeskey:", 12)) + prsaaeskey = cp + 12; + + cp = next; + } + + paesiv=NULL; + + if (paesiv==NULL) { + warn("AESIV missing from ANNOUNCE"); + goto out; + } + + if (prsaaeskey==NULL) { + warn("RSAAESKEY missing from ANNOUNCE"); + goto out; + } + + if (pfmtp==NULL) { + warn("FMTP missing from ANNOUNCE"); + goto out; + } + + int len, keylen; + uint8_t *aesiv = base64_dec(paesiv, &len); + if (len != 16) { + warn("client announced aeskey of %d bytes, wanted 16", len); + free(aesiv); + goto out; + } + memcpy(conn->stream.aesiv, aesiv, 16); + free(aesiv); + + uint8_t *rsaaeskey = base64_dec(prsaaeskey, &len); + uint8_t *aeskey = rsa_apply(rsaaeskey, len, &keylen, RSA_MODE_KEY); + free(rsaaeskey); + if (keylen != 16) { + warn("client announced rsaaeskey of %d bytes, wanted 16", keylen); + free(aeskey); + goto out; + } + memcpy(conn->stream.aeskey, aeskey, 16); + free(aeskey); + + int i; + for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++) + conn->stream.fmtp[i] = atoi(strsep(&pfmtp, " \t")); + + char *hdr = msg_get_header(req, "X-Apple-Client-Name"); + if (hdr) { + strncpy(sender_name, hdr, 1024); + debug(1, "Play connection from \"%s\".", hdr); + } else { + hdr = msg_get_header(req, "User-Agent"); + if (hdr) { + debug(1, "Play connection from \"%s\".", hdr); + strncpy(sender_name, hdr, 1024); + } else + sender_name[0] = 0; + } + resp->respcode = 200; + } else { + resp->respcode = 453; + debug(1, "Already playing."); + } + +out: + if (resp->respcode != 200 && resp->respcode != 453) { + pthread_mutex_unlock(&play_lock); + } +} + +static struct method_handler { + char *method; + void (*handler)(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp); +} method_handlers[] = {{"OPTIONS", handle_options}, + {"ANNOUNCE", handle_announce}, + {"FLUSH", handle_flush}, + {"TEARDOWN", handle_teardown}, + {"SETUP", handle_setup}, + {"GET_PARAMETER", handle_ignore}, + {"SET_PARAMETER", handle_set_parameter}, + {"RECORD", handle_record}, + {NULL, NULL}}; + +static void apple_challenge(int fd, rtsp_message *req, rtsp_message *resp) { + char *hdr = msg_get_header(req, "Apple-Challenge"); + if (!hdr) + return; + + SOCKADDR fdsa; + socklen_t sa_len = sizeof(fdsa); + getsockname(fd, (struct sockaddr *)&fdsa, &sa_len); + + int chall_len; + uint8_t *chall = base64_dec(hdr, &chall_len); + uint8_t buf[48], *bp = buf; + int i; + memset(buf, 0, sizeof(buf)); + + if (chall_len > 16) { + warn("oversized Apple-Challenge!"); + free(chall); + return; + } + memcpy(bp, chall, chall_len); + free(chall); + bp += chall_len; + +#ifdef AF_INET6 + if (fdsa.SAFAMILY == AF_INET6) { + struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)(&fdsa); + memcpy(bp, sa6->sin6_addr.s6_addr, 16); + bp += 16; + } else +#endif + { + struct sockaddr_in *sa = (struct sockaddr_in *)(&fdsa); + memcpy(bp, &sa->sin_addr.s_addr, 4); + bp += 4; + } + + for (i = 0; i < 6; i++) + *bp++ = config.hw_addr[i]; + + int buflen, resplen; + buflen = bp - buf; + if (buflen < 0x20) + buflen = 0x20; + + uint8_t *challresp = rsa_apply(buf, buflen, &resplen, RSA_MODE_AUTH); + char *encoded = base64_enc(challresp, resplen); + + // strip the padding. + char *padding = strchr(encoded, '='); + if (padding) + *padding = 0; + + msg_add_header(resp, "Apple-Response", encoded); + free(challresp); + free(encoded); +} + +static char *make_nonce(void) { + uint8_t random[8]; + int fd = open("/dev/random", O_RDONLY); + if (fd < 0) + die("could not open /dev/random!"); + int ignore = read(fd, random, sizeof(random)); + close(fd); + return base64_enc(random, 8); +} + +static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { + + if (!config.password) + return 0; + if (!*nonce) { + *nonce = make_nonce(); + goto authenticate; + } + + char *hdr = msg_get_header(req, "Authorization"); + if (!hdr || strncmp(hdr, "Digest ", 7)) + goto authenticate; + + char *realm = strstr(hdr, "realm=\""); + char *username = strstr(hdr, "username=\""); + char *response = strstr(hdr, "response=\""); + char *uri = strstr(hdr, "uri=\""); + + if (!realm || !username || !response || !uri) + goto authenticate; + + char *quote; + realm = strchr(realm, '"') + 1; + if (!(quote = strchr(realm, '"'))) + goto authenticate; + *quote = 0; + username = strchr(username, '"') + 1; + if (!(quote = strchr(username, '"'))) + goto authenticate; + *quote = 0; + response = strchr(response, '"') + 1; + if (!(quote = strchr(response, '"'))) + goto authenticate; + *quote = 0; + uri = strchr(uri, '"') + 1; + if (!(quote = strchr(uri, '"'))) + goto authenticate; + *quote = 0; + + uint8_t digest_urp[16], digest_mu[16], digest_total[16]; + +#ifdef HAVE_LIBSSL + MD5_CTX ctx; + + MD5_Init(&ctx); + MD5_Update(&ctx, username, strlen(username)); + MD5_Update(&ctx, ":", 1); + MD5_Update(&ctx, realm, strlen(realm)); + MD5_Update(&ctx, ":", 1); + MD5_Update(&ctx, config.password, strlen(config.password)); + MD5_Final(digest_urp, &ctx); + MD5_Init(&ctx); + MD5_Update(&ctx, req->method, strlen(req->method)); + MD5_Update(&ctx, ":", 1); + MD5_Update(&ctx, uri, strlen(uri)); + MD5_Final(digest_mu, &ctx); +#endif + +#ifdef HAVE_LIBPOLARSSL + md5_context tctx; + md5_starts(&tctx); + md5_update(&tctx, (const unsigned char *)username, strlen(username)); + md5_update(&tctx, (unsigned char *)":", 1); + md5_update(&tctx, (const unsigned char *)realm, strlen(realm)); + md5_update(&tctx, (unsigned char *)":", 1); + md5_update(&tctx, (const unsigned char *)config.password, strlen(config.password)); + md5_finish(&tctx, digest_urp); + md5_starts(&tctx); + md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method)); + md5_update(&tctx, (unsigned char *)":", 1); + md5_update(&tctx, (const unsigned char *)uri, strlen(uri)); + md5_finish(&tctx, digest_mu); +#endif + + int i; + unsigned char buf[33]; + for (i = 0; i < 16; i++) + sprintf((char *)buf + 2 * i, "%02X", digest_urp[i]); + +#ifdef HAVE_LIBSSL + MD5_Init(&ctx); + MD5_Update(&ctx, buf, 32); + MD5_Update(&ctx, ":", 1); + MD5_Update(&ctx, *nonce, strlen(*nonce)); + MD5_Update(&ctx, ":", 1); + for (i = 0; i < 16; i++) + sprintf(buf + 2 * i, "%02X", digest_mu[i]); + MD5_Update(&ctx, buf, 32); + MD5_Final(digest_total, &ctx); +#endif + +#ifdef HAVE_LIBPOLARSSL + md5_starts(&tctx); + md5_update(&tctx, buf, 32); + md5_update(&tctx, (unsigned char *)":", 1); + md5_update(&tctx, (const unsigned char *)*nonce, strlen(*nonce)); + md5_update(&tctx, (unsigned char *)":", 1); + for (i = 0; i < 16; i++) + sprintf((char *)buf + 2 * i, "%02X", digest_mu[i]); + md5_update(&tctx, buf, 32); + md5_finish(&tctx, digest_total); +#endif + + for (i = 0; i < 16; i++) + sprintf((char *)buf + 2 * i, "%02X", digest_total[i]); + + if (!strcmp(response, (const char *)buf)) + return 0; + warn("auth failed"); + +authenticate: + resp->respcode = 401; + int hdrlen = strlen(*nonce) + 40; + char *authhdr = malloc(hdrlen); + snprintf(authhdr, hdrlen, "Digest realm=\"taco\", nonce=\"%s\"", *nonce); + msg_add_header(resp, "WWW-Authenticate", authhdr); + free(authhdr); + return 1; +} + +static void *rtsp_conversation_thread_func(void *pconn) { + // SIGUSR1 is used to interrupt this thread if blocked for read + sigset_t set; + sigemptyset(&set); + sigaddset(&set, SIGUSR1); + pthread_sigmask(SIG_UNBLOCK, &set, NULL); + + rtsp_conn_info *conn = pconn; + + rtsp_message *req, *resp; + char *hdr, *auth_nonce = NULL; + + enum rtsp_read_request_response reply; + + do { + reply = rtsp_read_request(conn->fd, &req); + if (reply == rtsp_read_request_response_ok) { + resp = msg_init(); + resp->respcode = 400; + + apple_challenge(conn->fd, req, resp); + hdr = msg_get_header(req, "CSeq"); + if (hdr) + msg_add_header(resp, "CSeq", hdr); + msg_add_header(resp, "Audio-Jack-Status", "connected; type=analog"); + + if (rtsp_auth(&auth_nonce, req, resp)) + goto respond; + + struct method_handler *mh; + for (mh = method_handlers; mh->method; mh++) { + if (!strcmp(mh->method, req->method)) { + // debug(1,"RTSP Packet received of type \"%s\":",mh->method), + // msg_print_debug_headers(req); + mh->handler(conn, req, resp); + // debug(1,"RTSP Response:"); + // msg_print_debug_headers(resp); + break; + } + } + + respond: + msg_write_response(conn->fd, resp); + msg_free(req); + msg_free(resp); + } else { + if (reply != rtsp_read_request_response_shutdown_requested) + debug(1, "rtsp_read_request error %d, packet ignored.", (int)reply); + } + } while (reply != rtsp_read_request_response_shutdown_requested); + + debug(1, "closing RTSP connection."); + if (conn->fd > 0) + close(conn->fd); + if (rtsp_playing()) { + rtp_shutdown(); + player_stop(); + pthread_mutex_unlock(&play_lock); + please_shutdown = 0; + pthread_mutex_unlock(&playing_mutex); + } + if (auth_nonce) + free(auth_nonce); + conn->running = 0; + debug(2, "terminating RTSP thread."); + return NULL; +} + +// this function is not thread safe. +static const char *format_address(struct sockaddr *fsa) { + static char string[INETx_ADDRSTRLEN]; + void *addr; +#ifdef AF_INET6 + if (fsa->sa_family == AF_INET6) { + struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)(fsa); + addr = &(sa6->sin6_addr); + } else +#endif + { + struct sockaddr_in *sa = (struct sockaddr_in *)(fsa); + addr = &(sa->sin_addr); + } + return inet_ntop(fsa->sa_family, addr, string, sizeof(string)); +} + +void rtsp_listen_loop(void) { + struct addrinfo hints, *info, *p; + char portstr[6]; + int *sockfd = NULL; + int nsock = 0; + int i, ret; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + snprintf(portstr, 6, "%d", config.port); + + // debug(1,"listen socket port request is \"%s\".",portstr); + + ret = getaddrinfo(NULL, portstr, &hints, &info); + if (ret) { + die("getaddrinfo failed: %s", gai_strerror(ret)); + } + + for (p = info; p; p = p->ai_next) { + int fd = socket(p->ai_family, p->ai_socktype, IPPROTO_TCP); + int yes = 1; + + // Handle socket open failures if protocol unavailable (or IPV6 not handled) + if (fd == -1) { + // debug(1, "Failed to get socket: fam=%d, %s\n", p->ai_family, strerror(errno)); + continue; + } + + ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + +#ifdef IPV6_V6ONLY + // some systems don't support v4 access on v6 sockets, but some do. + // since we need to account for two sockets we might as well + // always. + if (p->ai_family == AF_INET6) { + ret |= setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)); + } +#endif + + if (!ret) + ret = bind(fd, p->ai_addr, p->ai_addrlen); + + // one of the address families will fail on some systems that + // report its availability. do not complain. + if (ret) { + debug(1, "Failed to bind to address %s.", format_address(p->ai_addr)); + continue; + } + + listen(fd, 5); + nsock++; + sockfd = realloc(sockfd, nsock * sizeof(int)); + sockfd[nsock - 1] = fd; + } + + freeaddrinfo(info); + + if (!nsock) + die("could not bind any listen sockets!"); + + int maxfd = -1; + fd_set fds; + FD_ZERO(&fds); + for (i = 0; i < nsock; i++) { + if (sockfd[i] > maxfd) + maxfd = sockfd[i]; + } + + mdns_register(); + + // printf("Listening for connections."); + // shairport_startup_complete(); + + int acceptfd; + struct timeval tv; + while (1) { + tv.tv_sec = 300; + tv.tv_usec = 0; + + for (i = 0; i < nsock; i++) + FD_SET(sockfd[i], &fds); + + ret = select(maxfd + 1, &fds, 0, 0, &tv); + if (ret < 0) { + if (errno == EINTR) + continue; + break; + } + + cleanup_threads(); + + acceptfd = -1; + for (i = 0; i < nsock; i++) { + if (FD_ISSET(sockfd[i], &fds)) { + acceptfd = sockfd[i]; + break; + } + } + if (acceptfd < 0) // timeout + continue; + + rtsp_conn_info *conn = malloc(sizeof(rtsp_conn_info)); + memset(conn, 0, sizeof(rtsp_conn_info)); + socklen_t slen = sizeof(conn->remote); + + debug(1, "new RTSP connection."); + conn->fd = accept(acceptfd, (struct sockaddr *)&conn->remote, &slen); + if (conn->fd < 0) { + perror("failed to accept connection"); + free(conn); + } else { + pthread_t rtsp_conversation_thread; + ret = pthread_create(&rtsp_conversation_thread, NULL, rtsp_conversation_thread_func, conn); + if (ret) + die("Failed to create RTSP receiver thread!"); + + conn->thread = rtsp_conversation_thread; + conn->running = 1; + track_thread(conn); + } + } + perror("select"); + die("fell out of the RTSP select loop"); +} diff --git a/newcfg.ac b/newcfg.ac new file mode 100644 index 00000000..d80cc9db --- /dev/null +++ b/newcfg.ac @@ -0,0 +1,232 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ([2.50]) +AC_INIT([shairport-sync], [2.3.4], [mikebrady@eircom.net]) +AM_INIT_AUTOMAKE +AC_CONFIG_SRCDIR([shairport.c]) +AC_CONFIG_HEADERS([config.h]) + +# Derived from the Avahi configure.ac file +# +# Specifying the OS type, defaulting to linux. +# +AC_ARG_WITH(os_type, AS_HELP_STRING([--with-os-type=OSType],[Specify the distribution to target: One of linux darwin])) +if test "z$with_os_type" = "z"; then + with_os_type="linux" +fi +with_os_type=`echo ${with_os_type} | tr '[[:upper:]]' '[[:lower:]]' ` + +# Checks for programs. +AC_PROG_CC +AC_PROG_INSTALL + +AC_ARG_WITH([pkg_config], +[ --with-pkg-config = use pkg-config to find libraries], ,[with_pkg_config=1]) + +PKG_PROG_PKG_CONFIG([0.9.0]) + +# Checks for libraries. +if test "x${with_os_type}" = xlinux; then + AC_CHECK_LIB([rt],[clock_gettime], , AC_MSG_ERROR(librt needed)) +fi + +##### Some build systems are not fully using pkg-config, so we can use the flag ${with_pkg_config} on a case-by-case basis +##### to control how to deal with them + + +AC_CHECK_LIB([daemon],[daemon_log], , AC_MSG_ERROR(libdaemon needed)) +AC_CHECK_LIB([pthread],[pthread_create], , AC_MSG_ERROR(pthread library needed)) +AC_CHECK_LIB([m],[exp], , AC_MSG_ERROR(maths library needed)) + +AC_MSG_RESULT(>>Including libpopt) +if test "x${with_pkg_config}" = x1 ; then + PKG_CHECK_MODULES( + [POPT], [popt], + [LIBS="${POPT_LIBS} ${LIBS}"]) +else + AC_CHECK_LIB([popt],[poptGetContext], , AC_MSG_ERROR(libpopt needed)) +fi +AC_DEFINE([HAVE_LIBPOPT],[1],[Define to 1 if you have popt]) + +AC_ARG_WITH([dummy],[ --with-dummy = include the dummy audio back end ],[AC_MSG_RESULT(>>Including the dummy audio back end) AC_DEFINE([CONFIG_DUMMY], 1, [Needed by the compiler.]) ], ) +AM_CONDITIONAL([USE_DUMMY], [test "x$with_dummy" = "x1" ]) + +AC_ARG_WITH([stdout],[ --with-stdout = include the stdout audio back end ],[ AC_MSG_RESULT(>>Including the stdout audio back end) AC_DEFINE([CONFIG_STDOUT], 1, [Needed by the compiler.]) ], ) +AM_CONDITIONAL([USE_STDOUT], [test "x$with_stdout" = "x1" ]) + +AC_ARG_WITH([pipe],[ --with-pipe = include the pipe audio back end ],[ AC_MSG_RESULT(>>Including the pipe audio back end) AC_DEFINE([CONFIG_PIPE], 1, [Needed by the compiler.]) ], ) +AM_CONDITIONAL([USE_PIPE], [test "x$with_pipe" = "x1" ]) + +AC_ARG_WITH([initscript], +[ --with-initscript = include a startup script], ,[with_initscript=1]) +AM_CONDITIONAL([INSTALL_INITSCRIPT], [test "x$with_initscript" = "x1"]) + +# Check if we want to support reading arguments from the command line +AC_ARG_WITH([command_line_argument_support], +[ --with-command-line-argument-support = read configuration from command line arguments], ,[with_command_line_argument_support=1]) + +if test "x${with_command_line_argument_support}" = x1 ; then + AC_MSG_RESULT(>>Including command line argument support) + AC_DEFINE([COMMAND_LINE_ARGUMENT_SUPPORT],[1],[Define to 1 if you want to support command line arguments]) +fi + +# Check if we want to support reading from a configuration file chosen -- i.e. take parameters from /etc/shairport-sync.conf +AC_ARG_WITH([configfile_support], +[ --with-configfile-support = support reading of configuration from a configuration file], ,[with_configfile_support=1]) + +if test "x${with_configfile_support}" = x1 ; then + AC_MSG_RESULT(>>Including configuration file support) + if test "x${with_pkg_config}" = x1 ; then + PKG_CHECK_MODULES( + [LIBCONFIG], [libconfig], + [LIBS="${LIBCONFIG_LIBS} ${LIBS}"]) + else + AC_CHECK_LIB([config],[config_init], , AC_MSG_ERROR(libconfig library needed)) + fi + AC_DEFINE([SUPPORT_CONFIG_FILES],[1],[Define to 1 if you want to support configuration files]) +fi + +AC_ARG_WITH([configfiles], +[ --with-configfiles = include configuration files in installation ], ,[with_configfiles=1]) +AM_CONDITIONAL([INSTALL_CONFIG_FILES], [test "x$with_configfiles" = "x1" -a "x$with_configfile_support" = "x1" ]) + + +# Check to see if we should include the initscript + +# Look for piddir flag +AC_ARG_WITH(piddir, [ --with-piddir= Specify a pathname to a directory in which to write the PID file.], [ + AC_MSG_CHECKING(--with-piddir argument) + if test "x${with_piddir}" = x -o "x${with_piddir}" = xyes ; then + AC_MSG_RESULT(not found) + AC_MSG_ERROR(when you use the --with-piddir directive you must specify the pathname of the directory into which the PID file will be written) + fi + AC_MSG_RESULT(${with_piddir}) + HAS_CUSTOMPIDDIR=1 + AC_SUBST(CUSTOM_PID_DIR,["${with_piddir}"]) + AC_DEFINE([USE_CUSTOM_PID_DIR],1,[Hook up special proc to libdaemon to point to this directory])], ) +AM_CONDITIONAL([USE_CUSTOMPIDDIR], [test "x$HAS_CUSTOMPIDDIR" = "x1"]) + +# Check --with-ssl=argument +AC_ARG_WITH(ssl, [ choose --with-ssl=openssl or --with-ssl=polarssl for encryption services], [ + AC_MSG_CHECKING(encryption libraries chosen) + if test "x${with_ssl}" = x -o "x${with_ssl}" = xyes ; then + AC_MSG_RESULT(not found) + AC_MSG_ERROR(choose either "openssl" or "polarssl" encryption) + fi + if test "x${with_ssl}" = xopenssl ; then + PKG_CHECK_MODULES( + [SSL], [libssl,libcrypto], + [LIBS="${SSL_LIBS} ${LIBS}" + AC_DEFINE([HAVE_LIBCRYPTO],[1],[Define to 1 if you have libcrypto]) + AC_DEFINE([HAVE_LIBSSL],[1],[Define to 1 if you have libssl])]) + elif test "x${with_ssl}" = xpolarssl ; then + AC_CHECK_LIB([polarssl],[ssl_init], , AC_MSG_ERROR(PolarSSL selected but the library cannot be found!)) + else + AC_MSG_ERROR(unknown option "${with_ssl}"." Please choose with "openssl" or "polarssl") + fi +], ) + +# Look for soxr flag +AC_ARG_WITH(soxr, [ --with-soxr = choose libsoxr for high-quality interpolation], [ + AC_MSG_RESULT(>>Including support for soxr-based interpolation) + AC_CHECK_LIB([soxr],[soxr_create], , AC_MSG_ERROR(soxr support requested but libsoxr not found!))], ) + +# Look for metadata flag -- set flag for conditional compilation +AC_ARG_WITH(metadata, [ --with-metadata = include support for a metadata feed], [ + AC_MSG_RESULT(>>Including metadata support) + HAS_METADATA=1 + AC_DEFINE([CONFIG_METADATA], 1, [Needed by the compiler.])], ) +AM_CONDITIONAL([USE_METADATA], [test "x$HAS_METADATA" = "x1"]) + +# What follows is a bit messy, because if the relevant library is requested, a compiler flag is defined, a file is included in the compilation +# and the relevant link files are added. + +# Look for avahi flag +AC_ARG_WITH(avahi, [ --with-avahi = choose Avahi-based mDNS support], [ + AC_MSG_RESULT(>>Including Avahi mDNS support) + HAS_AVAHI=1 + AC_DEFINE([CONFIG_AVAHI], 1, [Needed by the compiler.]) + AC_CHECK_LIB([avahi-client], [avahi_client_new], , AC_MSG_ERROR(Avahi support requires the avahi-client library!)) + AC_CHECK_LIB([avahi-common],[avahi_strerror], , AC_MSG_ERROR(Avahi support requires the avahi-common library!))], ) +AM_CONDITIONAL([USE_AVAHI], [test "x$HAS_AVAHI" = "x1"]) + +# Look for tinysvcmdns flag +AC_ARG_WITH(tinysvcmdns, [ --with-tinysvcmdns = choose tinysvcmdns-based mDNS support], [ + AC_MSG_RESULT(>>Including tinysvcmdns mDNS support) + HAS_TINYSVCMDNS=1 + AC_DEFINE([CONFIG_TINYSVCMDNS], 1, [Needed by the compiler.])], ) +AM_CONDITIONAL([USE_TINYSVCMDNS], [test "x$HAS_TINYSVCMDNS" = "x1"]) + +# Look for ALSA flag +AC_ARG_WITH(alsa, [ --with-alsa = choose native ALSA API support (GNU/Linux only)], + [AC_MSG_RESULT(>>Including an ALSA back end) + HAS_ALSA=1 + AM_CONDITIONAL([USE_ALSA], [test 0]) + AC_DEFINE([CONFIG_ALSA], 1, [Needed by the compiler.]) + PKG_CHECK_MODULES( + [ALSA], [alsa], + [LIBS="${ALSA_LIBS} ${LIBS}" + AC_DEFINE([HAVE_LIBASOUND],[1],[Define to 1 if you have ALSA])])]) +AM_CONDITIONAL([USE_ALSA], [test "x$HAS_ALSA" = "x1"]) + +# Look for SNDIO flag +AC_ARG_WITH(sndio, [ --with-sndio = choose native SNDIO API support (FreeBSD) -- probably broken], [ + AC_MSG_RESULT(>>Including a SNDIO back end --- N.B. this is probably broken!) + HAS_SNDIO=1 + AC_DEFINE([CONFIG_SNDIO], 1, [Needed by the compiler.]) + AC_CHECK_LIB([sndio], [sio_open], , AC_MSG_ERROR(SNDIO support requires the sndio library!))], ) +AM_CONDITIONAL([USE_SNDIO], [test "x$HAS_SNDIO" = "x1"]) + +# Look for AO flag +AC_ARG_WITH(ao, [ --with-ao = choose native AO (Audio Output?) API support -- probably broken], [ + AC_MSG_RESULT(>>Including an AO back end --- N.B. this is probably broken!) + HAS_AO=1 + AC_DEFINE([CONFIG_AO], 1, [Needed by the compiler.]) + AC_CHECK_LIB([ao], [ao_initialize], , AC_MSG_ERROR(AO support requires the ao library!))], ) +AM_CONDITIONAL([USE_AO], [test "x$HAS_AO" = "x1"]) + +# Look for pulseaudio flag +AC_ARG_WITH(pulseaudio, [ --with-pulseaudio = choose native PulseAudio API support -- N.B. no synchronisation -- so underflow or overflow is inevitable!], [ + AC_MSG_RESULT(>>Including a PulseAudio back end -- N.B. no synchronisation -- so underflow or overflow is inevitable!) + HAS_PULSE=1 + AC_DEFINE([CONFIG_PULSE], 1, [Needed by the compiler.]) + AC_CHECK_LIB([pulse-simple], [pa_simple_new], , AC_MSG_ERROR(PulseAudio support requires the pulse-simple library!)) + AC_CHECK_LIB([pulse], [pa_stream_peek], , AC_MSG_ERROR(PulseAudio support requires the libpulse-dev library!))], ) +AM_CONDITIONAL([USE_PULSE], [test "x$HAS_PULSE" = "x1"]) + +# Look for dns_sd flag +AC_ARG_WITH(dns_sd, [ --with-dns_sd = choose dns_sd mDNS support], [ + AC_MSG_RESULT(>>Including dns_sd for mDNS support) + HAS_DNS_SD=1 + AC_DEFINE([CONFIG_HAVE_DNS_SD_H], 1, [Needed by the compiler.]) + AC_CHECK_LIB([dns_sd], [DNSServiceRefDeallocate], , AC_MSG_ERROR(dns_sd support requires the dns_sd library!))], ) +AM_CONDITIONAL([USE_DNS_SD], [test "x$HAS_DNS_SD" = "x1"]) + +# Checks for header files. +AC_HEADER_STDC +AC_CHECK_HEADERS([getopt_long.h]) +AC_CHECK_HEADERS([arpa/inet.h fcntl.h mach/mach.h memory.h netdb.h netinet/in.h stdint.h stdlib.h string.h sys/ioctl.h sys/socket.h sys/time.h syslog.h unistd.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_C_INLINE +AC_TYPE_INT16_T +AC_TYPE_INT32_T +AC_TYPE_INT64_T +AC_TYPE_PID_T +AC_TYPE_SIZE_T +AC_TYPE_SSIZE_T +AC_TYPE_UINT16_T +AC_TYPE_UINT32_T +AC_TYPE_UINT64_T +AC_TYPE_UINT8_T + +# Checks for library functions. +AC_FUNC_ERROR_AT_LINE +AC_FUNC_FORK +AC_FUNC_MALLOC +AC_FUNC_REALLOC +AC_CHECK_FUNCS([clock_gettime gethostname gettimeofday inet_ntoa memchr memmove memset pow select socket stpcpy strcasecmp strchr strdup strerror strstr strtol strtoul]) + +AC_CONFIG_FILES([Makefile man/Makefile]) +AC_OUTPUT diff --git a/shairport.c b/shairport.c index 0eabc68f..e5f32d30 100644 --- a/shairport.c +++ b/shairport.c @@ -62,6 +62,9 @@ static int shutting_down = 0; static char *appName = NULL; +#ifdef SUPPORT_CONFIG_FILES + char configuration_file_path[4096]; +#endif void shairport_shutdown() { if (shutting_down) @@ -320,22 +323,14 @@ int parse_options(int argc, char **argv) { } #ifdef SUPPORT_CONFIG_FILES - char configuration_file_path[4096]; - strcpy(configuration_file_path, "/etc/"); - strcat(configuration_file_path, appName); - strcat(configuration_file_path, ".conf"); - debug(2, "Looking for file \"%s\"", configuration_file_path); config_setting_t *setting; const char *str; int value; config_init(&config_file_stuff); - char *cfp = configuration_file_path; - // use the configuration file path given in the -c option, if any - if (config.configfile) - cfp = config.configfile; + debug(1, "Looking for configuration file \"%s\"", config.configfile); /* Read the file. If there is an error, report it and exit. */ - if (config_read_file(&config_file_stuff, cfp)) { + if (config_read_file(&config_file_stuff, config.configfile)) { // make config.cfg point to it config.cfg = &config_file_stuff; /* Get the Service Name. */ @@ -601,7 +596,20 @@ int main(int argc, char **argv) { memset(&config, 0, sizeof(config)); // also clears all strings, BTW atexit(exit_function); + // this is a bit weird, but apparently necessary + char *basec = strdup(argv[0]); + char *bname = basename(basec); + appName = strdup(bname); + free(basec); + // set defaults +#ifdef SUPPORT_CONFIG_FILES + strcpy(configuration_file_path, "/etc/"); + strcat(configuration_file_path, appName); + strcat(configuration_file_path, ".conf"); + config.configfile = configuration_file_path; +#endif + config.statistics_requested - 0; // don't print stats in the log config.latency = 88200; // AirPlay. Is also reset in rtsp.c when play is about to start config.userSuppliedLatency = 0; // zero means none supplied @@ -627,12 +635,6 @@ int main(int argc, char **argv) { config.udp_port_base = 6001; config.udp_port_range = 100; - // this is a bit weird, but apparently necessary - char *basec = strdup(argv[0]); - char *bname = basename(basec); - appName = strdup(bname); - free(basec); - /* Check if we are called with -V or --version parameter */ if (argc >= 2 && ((strcmp(argv[1], "-V") == 0) || (strcmp(argv[1], "--version") == 0))) { print_version(); @@ -822,6 +824,9 @@ int main(int argc, char **argv) { debug(2, "audio backend desired buffer length is %d.", config.audio_backend_buffer_desired_length); debug(2, "audio backend latency offset is %d.", config.audio_backend_latency_offset); +#ifdef SUPPORT_CONFIG_FILES + debug(2, "Configuration file name \"%s\".", config.configfile); +#endif #ifdef CONFIG_METADATA debug(2, "metdata enabled is %d.", config.metadata_enabled); debug(2, "metadata pipename is \"%s\".", config.metadata_pipename); From 4b2f3a986ae2fa8b6bc2da32a0f0474a41058125 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 16 Jun 2015 14:09:50 +0000 Subject: [PATCH 104/220] Remove files inadvertently included in repository --- new-rtsp.c | 1616 ---------------------------------------------------- newcfg.ac | 232 -------- 2 files changed, 1848 deletions(-) delete mode 100644 new-rtsp.c delete mode 100644 newcfg.ac diff --git a/new-rtsp.c b/new-rtsp.c deleted file mode 100644 index bf30f47f..00000000 --- a/new-rtsp.c +++ /dev/null @@ -1,1616 +0,0 @@ -/* - * RTSP protocol handler. This file is part of Shairport. - * Copyright (c) James Laird 2013 - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" - -#ifdef HAVE_LIBSSL -#include -#endif - -#ifdef HAVE_LIBPOLARSSL -#include -#endif - -#include "common.h" -#include "player.h" -#include "rtp.h" -#include "mdns.h" - -#ifdef AF_INET6 -#define INETx_ADDRSTRLEN INET6_ADDRSTRLEN -#else -#define INETx_ADDRSTRLEN INET_ADDRSTRLEN -#endif - -enum rtsp_read_request_response { - rtsp_read_request_response_ok, - rtsp_read_request_response_shutdown_requested, - rtsp_read_request_response_bad_packet, - rtsp_read_request_response_error -}; - -// Mike Brady's part... -static pthread_mutex_t play_lock = PTHREAD_MUTEX_INITIALIZER; - -// every time we want to retain or release a reference count, lock it with this -// if a reference count is read as zero, it means the it's being deallocated. -static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER; - -// only one thread is allowed to use the player at once. -// it monitors the request variable (at least when interrupted) -static pthread_mutex_t playing_mutex = PTHREAD_MUTEX_INITIALIZER; -static int please_shutdown = 0; -static pthread_t playing_thread = 0; - -typedef struct { - int fd; - stream_cfg stream; - SOCKADDR remote; - int running; - pthread_t thread; -} rtsp_conn_info; - -#ifdef CONFIG_METADATA -typedef struct { - pthread_mutex_t pc_queue_lock; - pthread_cond_t pc_queue_item_added_signal; - pthread_cond_t pc_queue_item_removed_signal; - size_t item_size; // number of bytes in each item - uint32_t count; // number of items in the queue - uint32_t capacity; // maximum number of items - uint32_t toq; // first item to take - uint32_t eoq; // free space at end of queue - void *items; // a pointer to where the items are actually stored -} pc_queue; // producer-consumer queue -#endif - -typedef struct { - uint32_t referenceCount; // we might start using this... - int nheaders; - char *name[16]; - char *value[16]; - - int contentlength; - char *content; - - // for requests - char method[16]; - - // for responses - int respcode; -} rtsp_message; - -#ifdef CONFIG_METADATA -typedef struct { - uint32_t type; - uint32_t code; - char *data; - uint32_t length; - rtsp_message *carrier; -} metadata_package; - -void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items) { - the_queue->item_size = item_size; - the_queue->items = items; - the_queue->count = 0; - the_queue->capacity = number_of_items; - the_queue->toq = 0; - the_queue->eoq = 0; -} - -int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier, - int block); - -int send_ssnc_metadata(uint32_t code, char *data, uint32_t length, int block) { - return send_metadata('ssnc', code, data, length, NULL, block); -} - -int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) { - int rc; - if (the_queue) { - if (block == 0) { - rc = pthread_mutex_trylock(&the_queue->pc_queue_lock); - if (rc == EBUSY) - return EBUSY; - } else - rc = pthread_mutex_lock(&the_queue->pc_queue_lock); - if (rc) - debug(1, "Error locking for pc_queue_add_item"); - while (the_queue->count == the_queue->capacity) { - rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal, &the_queue->pc_queue_lock); - if (rc) - debug(1, "Error waiting for item to be removed"); - } - uint32_t i = the_queue->eoq; - void *p = the_queue->items + the_queue->item_size * i; - // void * p = &the_queue->qbase + the_queue->item_size*the_queue->eoq; - memcpy(p, the_stuff, the_queue->item_size); - - // update the pointer - i++; - if (i == the_queue->capacity) - // fold pointer if necessary - i = 0; - the_queue->eoq = i; - the_queue->count++; - if (the_queue->count == the_queue->capacity) - debug(1, "pc_queue is full!"); - rc = pthread_cond_signal(&the_queue->pc_queue_item_added_signal); - if (rc) - debug(1, "Error signalling after pc_queue_add_item"); - rc = pthread_mutex_unlock(&the_queue->pc_queue_lock); - if (rc) - debug(1, "Error unlocking for pc_queue_add_item"); - } else { - debug(1, "Adding an item to a NULL queue"); - } - return 0; -} - -int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) { - int rc; - if (the_queue) { - rc = pthread_mutex_lock(&the_queue->pc_queue_lock); - if (rc) - debug(1, "Error locking for pc_queue_get_item"); - while (the_queue->count == 0) { - rc = pthread_cond_wait(&the_queue->pc_queue_item_added_signal, &the_queue->pc_queue_lock); - if (rc) - debug(1, "Error waiting for item to be added"); - } - uint32_t i = the_queue->toq; - // void * p = &the_queue->qbase + the_queue->item_size*the_queue->toq; - void *p = the_queue->items + the_queue->item_size * i; - memcpy(the_stuff, p, the_queue->item_size); - - // update the pointer - i++; - if (i == the_queue->capacity) - // fold pointer if necessary - i = 0; - the_queue->toq = i; - the_queue->count--; - rc = pthread_cond_signal(&the_queue->pc_queue_item_removed_signal); - if (rc) - debug(1, "Error signalling after pc_queue_removed_item"); - rc = pthread_mutex_unlock(&the_queue->pc_queue_lock); - if (rc) - debug(1, "Error unlocking for pc_queue_get_item"); - } else { - debug(1, "Removing an item from a NULL queue"); - } - return 0; -} - -#endif - -// determine if we are the currently playing thread -static inline int rtsp_playing(void) { - if (pthread_mutex_trylock(&playing_mutex)) { - return pthread_equal(playing_thread, pthread_self()); - } else { - pthread_mutex_unlock(&playing_mutex); - return 0; - } -} - -void rtsp_request_shutdown_stream(void) { - please_shutdown = 1; - pthread_kill(playing_thread, SIGUSR1); -} - -static void rtsp_take_player(void) { - if (rtsp_playing()) - return; - - if (pthread_mutex_trylock(&playing_mutex)) { - debug(1, "shutting down playing thread."); - // XXX minor race condition between please_shutdown and signal delivery - please_shutdown = 1; - pthread_kill(playing_thread, SIGUSR1); - pthread_mutex_lock(&playing_mutex); - } - playing_thread = pthread_self(); // make us the currently-playing thread (why?) -} - -void rtsp_shutdown_stream(void) { - rtsp_take_player(); - pthread_mutex_unlock(&playing_mutex); -} - -// keep track of the threads we have spawned so we can join() them -static rtsp_conn_info **conns = NULL; -static int nconns = 0; -static void track_thread(rtsp_conn_info *conn) { - conns = realloc(conns, sizeof(rtsp_conn_info *) * (nconns + 1)); - conns[nconns] = conn; - nconns++; -} - -static void cleanup_threads(void) { - void *retval; - int i; - debug(2, "culling threads."); - for (i = 0; i < nconns;) { - if (conns[i]->running == 0) { - pthread_join(conns[i]->thread, &retval); - free(conns[i]); - debug(2, "one joined..."); - nconns--; - if (nconns) - conns[i] = conns[nconns]; - } else { - i++; - } - } -} - -// park a null at the line ending, and return the next line pointer -// accept \r, \n, or \r\n -static char *nextline(char *in, int inbuf) { - char *out = NULL; - while (inbuf) { - if (*in == '\r') { - *in++ = 0; - out = in; - } - if (*in == '\n') { - *in++ = 0; - out = in; - } - - if (out) - break; - - in++; - inbuf--; - } - return out; -} - -static void msg_retain(rtsp_message *msg) { - if (msg) { - int rc = pthread_mutex_lock(&reference_counter_lock); - if (rc) - debug(1, "Error %d locking reference counter lock"); - msg->referenceCount++; - rc = pthread_mutex_unlock(&reference_counter_lock); - if (rc) - debug(1, "Error %d unlocking reference counter lock"); - } else { - debug(1, "null rtsp_message pointer passed to retain"); - } -} - -static rtsp_message *msg_init(void) { - rtsp_message *msg = malloc(sizeof(rtsp_message)); - memset(msg, 0, sizeof(rtsp_message)); - msg->referenceCount = 1; // from now on, any access to this must be protected with the lock - return msg; -} - -static int msg_add_header(rtsp_message *msg, char *name, char *value) { - if (msg->nheaders >= sizeof(msg->name) / sizeof(char *)) { - warn("too many headers?!"); - return 1; - } - - msg->name[msg->nheaders] = strdup(name); - msg->value[msg->nheaders] = strdup(value); - msg->nheaders++; - - return 0; -} - -static char *msg_get_header(rtsp_message *msg, char *name) { - int i; - for (i = 0; i < msg->nheaders; i++) - if (!strcasecmp(msg->name[i], name)) - return msg->value[i]; - return NULL; -} - -static void msg_print_debug_headers(rtsp_message *msg) { - int i; - for (i = 0; i < msg->nheaders; i++) { - debug(1, " Type: \"%s\", content: \"%s\"", msg->name[i], msg->value[i]); - } -} - -static void msg_free(rtsp_message *msg) { - - if (msg) { - int rc = pthread_mutex_lock(&reference_counter_lock); - if (rc) - debug(1, "Error %d locking reference counter lock during msg_free()", rc); - msg->referenceCount--; - rc = pthread_mutex_unlock(&reference_counter_lock); - if (rc) - debug(1, "Error %d unlocking reference counter lock during msg_free()", rc); - if (msg->referenceCount == 0) { - int i; - for (i = 0; i < msg->nheaders; i++) { - free(msg->name[i]); - free(msg->value[i]); - } - if (msg->content) - free(msg->content); - free(msg); - } // else { - // debug(1,"rtsp_message reference count non-zero: %d!",msg->referenceCount); - //} - } else { - debug(1, "null rtsp_message pointer passed to msg_free()"); - } -} - -static int msg_handle_line(rtsp_message **pmsg, char *line) { - rtsp_message *msg = *pmsg; - - if (!msg) { - msg = msg_init(); - *pmsg = msg; - char *sp, *p; - - // debug(1, "received request: %s", line); - - p = strtok_r(line, " ", &sp); - if (!p) - goto fail; - strncpy(msg->method, p, sizeof(msg->method) - 1); - - p = strtok_r(NULL, " ", &sp); - if (!p) - goto fail; - - p = strtok_r(NULL, " ", &sp); - if (!p) - goto fail; - if (strcmp(p, "RTSP/1.0")) - goto fail; - - return -1; - } - - if (strlen(line)) { - char *p; - p = strstr(line, ": "); - if (!p) { - warn("bad header: >>%s<<", line); - goto fail; - } - *p = 0; - p += 2; - msg_add_header(msg, line, p); - debug(2, " %s: %s.", line, p); - return -1; - } else { - char *cl = msg_get_header(msg, "Content-Length"); - if (cl) - return atoi(cl); - else - return 0; - } - -fail: - *pmsg = NULL; - msg_free(msg); - return 0; -} - -static enum rtsp_read_request_response rtsp_read_request(int fd, rtsp_message **the_packet) { - enum rtsp_read_request_response reply = rtsp_read_request_response_ok; - ssize_t buflen = 512; - char *buf = malloc(buflen + 1); - - rtsp_message *msg = NULL; - - ssize_t nread; - ssize_t inbuf = 0; - int msg_size = -1; - - while (msg_size < 0) { - if (please_shutdown) { - debug(1, "RTSP shutdown requested."); - reply = rtsp_read_request_response_shutdown_requested; - goto shutdown; - } - nread = read(fd, buf + inbuf, buflen - inbuf); - if (!nread) { - debug(1, "RTSP connection closed."); - reply = rtsp_read_request_response_shutdown_requested; - goto shutdown; - } - if (nread < 0) { - if (errno == EINTR) - continue; - perror("read failure"); - reply = rtsp_read_request_response_error; - goto shutdown; - } - inbuf += nread; - - char *next; - while (msg_size < 0 && (next = nextline(buf, inbuf))) { - msg_size = msg_handle_line(&msg, buf); - - if (!msg) { - warn("no RTSP header received"); - reply = rtsp_read_request_response_bad_packet; - goto shutdown; - } - - inbuf -= next - buf; - if (inbuf) - memmove(buf, next, inbuf); - } - } - - if (msg_size > buflen) { - buf = realloc(buf, msg_size); - if (!buf) { - warn("too much content"); - reply = rtsp_read_request_response_error; - goto shutdown; - } - buflen = msg_size; - } - - uint64_t threshold_time = - get_absolute_time_in_fp() + ((uint64_t)5 << 32); // i.e. five seconds from now - int warning_message_sent = 0; - - const size_t max_read_chunk = 50000; - while (inbuf < msg_size) { - - // we are going to read the stream in chunks and time how long it takes to do so. - // If it's taking too long, (and we find out about it), we will send an error message as - // metadata - - if (warning_message_sent == 0) { - uint64_t time_now = get_absolute_time_in_fp(); - if (time_now > threshold_time) { // it's taking too long - debug(1, "Error receiving metadata from source -- transmission seems to be stalled."); -#ifdef CONFIG_METADATA - send_ssnc_metadata('stal', NULL, 0, 1); -#endif - warning_message_sent = 1; - } - } - ssize_t read_chunk = msg_size - inbuf; - if (read_chunk > max_read_chunk) - read_chunk = max_read_chunk; - nread = read(fd, buf + inbuf, read_chunk); - if (!nread) { - reply = rtsp_read_request_response_error; - goto shutdown; - } - if (nread == EINTR) - continue; - if (nread < 0) { - perror("read failure"); - reply = rtsp_read_request_response_error; - goto shutdown; - } - inbuf += nread; - } - - msg->contentlength = inbuf; - msg->content = buf; - *the_packet = msg; - return reply; - -shutdown: - if (msg) { - msg_free(msg); // which will free the content and everything else - } - // in case the message wasn't formed or wasn't fully initialised - if ((msg) && (msg->content == NULL) || (!msg)) - free(buf); - *the_packet = NULL; - return reply; -} - -static void msg_write_response(int fd, rtsp_message *resp) { - char pkt[1024]; - int pktfree = sizeof(pkt); - char *p = pkt; - int i, n; - - n = snprintf(p, pktfree, "RTSP/1.0 %d %s\r\n", resp->respcode, - resp->respcode == 200 ? "OK" : "Error"); - // debug(1, "sending response: %s", pkt); - pktfree -= n; - p += n; - - for (i = 0; i < resp->nheaders; i++) { - debug(2, " %s: %s.", resp->name[i], resp->value[i]); - n = snprintf(p, pktfree, "%s: %s\r\n", resp->name[i], resp->value[i]); - pktfree -= n; - p += n; - if (pktfree <= 0) - die("Attempted to write overlong RTSP packet"); - } - - if (pktfree < 3) - die("Attempted to write overlong RTSP packet"); - - strcpy(p, "\r\n"); - int ignore = write(fd, pkt, p - pkt + 2); -} - -static void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - resp->respcode = 200; - msg_add_header(resp, "Audio-Latency", "88200"); -} - -static void handle_options(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - resp->respcode = 200; - msg_add_header(resp, "Public", "ANNOUNCE, SETUP, RECORD, " - "PAUSE, FLUSH, TEARDOWN, " - "OPTIONS, GET_PARAMETER, SET_PARAMETER"); -} - -static void handle_teardown(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - if (!rtsp_playing()) - return; - resp->respcode = 200; - msg_add_header(resp, "Connection", "close"); - please_shutdown = 1; -} - -static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - if (!rtsp_playing()) - return; - char *p; - uint32_t rtptime = 0; - char *hdr = msg_get_header(req, "RTP-Info"); - - if (hdr) { - // debug(1,"FLUSH message received: \"%s\".",hdr); - // get the rtp timestamp - p = strstr(hdr, "rtptime="); - if (p) { - p = strchr(p, '=') + 1; - if (p) - rtptime = uatoi(p); // unsigned integer -- up to 2^32-1 - } - } - // debug(1,"RTSP Flush Requested: %u.",rtptime); - player_flush(rtptime); - resp->respcode = 200; -} - -static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - int cport, tport; - int lsport, lcport, ltport; - uint32_t active_remote = 0; - - char *ar = msg_get_header(req, "Active-Remote"); - if (ar) { - // debug(1,"Active-Remote string seen: \"%s\".",ar); - // get the active remote - char *p; - active_remote = strtoul(ar, &p, 10); - // debug(1,"Active Remote is %u.",active_remote); - } - - // select latency - // if iTunes V10 or later is detected, use the iTunes latency setting - // if AirPlay is detected, use the AirPlay latency setting - // for everything else, use the general latency setting, if given, or - // else use the default latency setting - - config.latency = 88200; - - if (config.userSuppliedLatency) - config.latency = config.userSuppliedLatency; - - char *ua = msg_get_header(req, "User-Agent"); - if (ua == 0) { - debug(1, "No User-Agent string found in the SETUP message. Using latency of %d frames.", - config.latency); - } else { - if (strstr(ua, "iTunes") == ua) { - int iTunesVersion = 0; - // now check it's version 10 or later - char *pp = strchr(ua, '/') + 1; - if (pp) - iTunesVersion = atoi(pp); - else - debug(2, "iTunes Version Number not found."); - if (iTunesVersion >= 10) { - debug(2, "User-Agent is iTunes 10 or better, (actual version is %d); selecting the iTunes " - "latency of %d frames.", - iTunesVersion, config.iTunesLatency); - config.latency = config.iTunesLatency; - } - } else if (strstr(ua, "AirPlay") == ua) { - debug(2, "User-Agent is AirPlay; selecting the AirPlay latency of %d frames.", - config.AirPlayLatency); - config.latency = config.AirPlayLatency; - } else if (strstr(ua, "forked-daapd") == ua) { - debug(2, "User-Agent is forked-daapd; selecting the forked-daapd latency of %d frames.", - config.ForkedDaapdLatency); - config.latency = config.ForkedDaapdLatency; - } else { - debug(2, "Unrecognised User-Agent. Using latency of %d frames.", config.latency); - } - } - char *hdr = msg_get_header(req, "Transport"); - if (!hdr) - goto error; - - char *p; - p = strstr(hdr, "control_port="); - if (!p) - goto error; - p = strchr(p, '=') + 1; - cport = atoi(p); - - p = strstr(hdr, "timing_port="); - if (!p) - goto error; - p = strchr(p, '=') + 1; - tport = atoi(p); - - rtsp_take_player(); - rtp_setup(&conn->remote, cport, tport, active_remote, &lsport, &lcport, <port); - if (!lsport) - goto error; - char *q; - p = strstr(hdr, "control_port="); - if (p) { - q = strchr(p, ';'); // get past the control port entry - *p++ = 0; - if (q++) - strcat(hdr, q); // should unsplice the control port entry - } - p = strstr(hdr, "timing_port="); - if (p) { - q = strchr(p, ';'); // get past the timing port entry - *p++ = 0; - if (q++) - strcat(hdr, q); // should unsplice the timing port entry - } - - player_play(&conn->stream); - - char *resphdr = alloca(200); - *resphdr = 0; - sprintf(resphdr, "RTP/AVP/" - "UDP;unicast;interleaved=0-1;mode=record;control_port=%d;timing_port=%d;server_" - "port=%d", - lcport, ltport, lsport); - - msg_add_header(resp, "Transport", resphdr); - - msg_add_header(resp, "Session", "1"); - - resp->respcode = 200; - return; - -error: - warn("Error in setup request."); - pthread_mutex_unlock(&play_lock); - resp->respcode = 451; // invalid arguments -} - -static void handle_ignore(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - resp->respcode = 200; -} - -static void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp) { - char *cp = req->content; - int cp_left = req->contentlength; - char *next; - while (cp_left && cp) { - next = nextline(cp, cp_left); - cp_left -= next - cp; - - if (!strncmp(cp, "volume: ", 8)) { - if (config.ignore_volume_control == 0) { - float volume = atof(cp + 8); - debug(2, "volume: %f\n", volume); - player_volume(volume); - } - } else -#ifdef CONFIG_METADATA - if (!strncmp(cp, "progress: ", 10)) { - char *progress = cp + 10; - debug(2, "progress: \"%s\"\n", - progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per second - send_ssnc_metadata('prgr', strdup(progress), strlen(progress), 1); - } else -#endif - { - debug(1, "unrecognised parameter: \"%s\" (%d)\n", cp, strlen(cp)); - } - cp = next; - } -} - -#ifdef CONFIG_METADATA -// Metadata is not used by shairport-sync. -// Instead we send all metadata to a fifo pipe, so that other apps can listen to the pipe and use -// the metadata. - -// We use two 4-character codes to identify each piece of data and we send the data itself, if any, -// in base64 form. - -// The first 4-character code, called the "type", is either: -// 'core' for all the regular metadadata coming from iTunes, etc., or -// 'ssnc' (for 'shairport-sync') for all metadata coming from Shairport Sync itself, such as -// start/end delimiters, etc. - -// For 'core' metadata, the second 4-character code is the 4-character metadata code coming from -// iTunes etc. -// For 'ssnc' metadata, the second 4-character code is used to distinguish the messages. - -// Cover art is not tagged in the same way as other metadata, it seems, so is sent as an 'ssnc' type -// metadata message with the code 'PICT' -// Here are the 'ssnc' codes defined so far: -// 'PICT' -- the payload is a picture, either a JPEG or a PNG. Check the first few bytes to see -// which. -// 'pbeg' -- play stream begin. No arguments -// 'pend' -- play stream end. No arguments -// 'pfls' -- play stream flush. No arguments -// 'prsm' -- play stream resume. No arguments -// 'pvol' -- play volume. The volume is sent as a string -- -// "airplay_volume,volume,lowest_volume,highest_volume,has_true_mute,is_muted" -// volume, lowest_volume and highest_volume are given in dB -// is_muted is 1 if [true] mute is enabled, 0 otherwise. -// The "airplay_volume" is what's sent to the player, and is from 0.00 down to -30.00, -// with -144.00 meaning mute. -// This is linear on the volume control slider of iTunes or iOS AirPLay -// 'prgr' -- progress -- this is metadata from AirPlay consisting of RTP timestamps for the start -// of the current play sequence, the current play point and the end of the play sequence. -// I guess the timestamps wrap at 2^32. -// 'mdst' -- a sequence of metadata is about to start -// 'mden' -- a sequence of metadata has ended -// 'snam' -- the name of the originator -- e.g. "Joe's iPhone" or "iTunes...". -// -// including a simple base64 encoder to minimise malloc/free activity - -// From Stack Overflow, with thanks: -// http://stackoverflow.com/questions/342409/how-do-i-base64-encode-decode-in-c -// minor mods to make independent of C99. -// more significant changes make it not malloc memory -// needs to initialise the docoding table first - -// add _so to end of name to avoid confusion with polarssl's implementation - -static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; - -static int mod_table[] = {0, 2, 1}; - -// pass in a pointer to the data, its length, a pointer to the output buffer and a pointer to an int -// containing its maximum length -// the actual length will be returned. - -char *base64_encode_so(const unsigned char *data, size_t input_length, char *encoded_data, - size_t *output_length) { - - size_t calculated_output_length = 4 * ((input_length + 2) / 3); - if (calculated_output_length > *output_length) - return (NULL); - *output_length = calculated_output_length; - - int i, j; - for (i = 0, j = 0; i < input_length;) { - - uint32_t octet_a = i < input_length ? (unsigned char)data[i++] : 0; - uint32_t octet_b = i < input_length ? (unsigned char)data[i++] : 0; - uint32_t octet_c = i < input_length ? (unsigned char)data[i++] : 0; - - uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; - - encoded_data[j++] = encoding_table[(triple >> 3 * 6) & 0x3F]; - encoded_data[j++] = encoding_table[(triple >> 2 * 6) & 0x3F]; - encoded_data[j++] = encoding_table[(triple >> 1 * 6) & 0x3F]; - encoded_data[j++] = encoding_table[(triple >> 0 * 6) & 0x3F]; - } - - for (i = 0; i < mod_table[input_length % 3]; i++) - encoded_data[*output_length - 1 - i] = '='; - - return encoded_data; -} - -// with thanks! -// - -static int fd = -1; -static int dirty = 0; -pc_queue metadata_queue; -#define metadata_queue_size 500 -metadata_package metadata_queue_items[metadata_queue_size]; - -static pthread_t metadata_thread; - -void metadata_create(void) { - if (config.metadata_enabled == 0) - return; - - size_t pl = strlen(config.metadata_pipename) + 1; - - char *path = malloc(pl + 1); - snprintf(path, pl + 1, "%s", config.metadata_pipename); - - if (mkfifo(path, 0644) && errno != EEXIST) - die("Could not create metadata FIFO %s", path); - - free(path); -} - -void metadata_open(void) { - if (config.metadata_enabled == 0) - return; - - size_t pl = strlen(config.metadata_pipename) + 1; - - char *path = malloc(pl + 1); - snprintf(path, pl + 1, "%s", config.metadata_pipename); - - fd = open(path, O_WRONLY | O_NONBLOCK); - // if (fd < 0) - // debug(1, "Could not open metadata FIFO %s. Will try again later.", path); - - free(path); -} - -static void metadata_close(void) { - close(fd); - fd = -1; -} - -ssize_t non_blocking_write(int fd, const void *buf, size_t count) { - // debug(1,"writing %u to pipe...",count); - // we are assuming that the count is always smaller than the FIFO's buffer - struct pollfd ufds[1]; - ssize_t reply; - do { - ufds[0].fd = fd; - ufds[0].events = POLLOUT; - int rv = poll(ufds, 1, 5000); - if (rv == -1) - debug(1, "error waiting for pipe to unblock..."); - if (rv == 0) - debug(1, "timeout waiting for pipe to unblock"); - reply = write(fd, buf, count); - if ((reply == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) - debug(1, "writing to pipe will block..."); - // else - // debug(1,"writing %u to pipe done...",reply); - } while ((reply == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))); - return reply; - - // return write(fd,buf,count); -} - -void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) { - debug(2, "Process metadata with type %x, code %x and length %u.", type, code, length); - int ret; - // readers may go away and come back - if (fd < 0) - metadata_open(); - if (fd < 0) - return; - char thestring[1024]; - snprintf(thestring, 1024, "\n%x%x%u\n", type, code, - length); - ret = non_blocking_write(fd, thestring, strlen(thestring)); - if (ret < 1) - return; - if ((data != NULL) && (length > 0)) { - snprintf(thestring, 1024, "\n"); - ret = non_blocking_write(fd, thestring, strlen(thestring)); - if (ret < 1) // no reader - return; - // here, we write the data in base64 form using our nice base64 encoder - // but, we break it into lines of 76 output characters, except for the last one. - // thus, we send groups of (76/4)*3 = 57 bytes to the encoder at a time - size_t remaining_count = length; - char *remaining_data = data; - size_t towrite_count; - char outbuf[76]; - while ((remaining_count) && (ret >= 0)) { - size_t towrite_count = remaining_count; - if (towrite_count > 57) - towrite_count = 57; - size_t outbuf_size = 76; // size of output buffer on entry, length of result on exit - if (base64_encode_so(remaining_data, towrite_count, outbuf, &outbuf_size) == NULL) - debug(1, "Error encoding base64 data."); - // debug(1,"Remaining count: %d ret: %d, outbuf_size: %d.",remaining_count,ret,outbuf_size); - ret = non_blocking_write(fd, outbuf, outbuf_size); - if (ret < 0) - return; - remaining_data += towrite_count; - remaining_count -= towrite_count; - } - snprintf(thestring, 1024, "\n\n"); - ret = non_blocking_write(fd, thestring, strlen(thestring)); - if (ret < 1) // no reader - return; - } -} - -void *metadata_thread_function(void *ignore) { - metadata_create(); - metadata_package pack; - while (1) { - pc_queue_get_item(&metadata_queue, &pack); - if (config.metadata_enabled) - metadata_process(pack.type, pack.code, pack.data, pack.length); - if (pack.carrier) - msg_free(pack.carrier); // release the message - else if (pack.data) - free(pack.data); - } - pthread_exit(NULL); -} - -void metadata_init(void) { - // create a pc_queue for passing information to a threaded metadata handler - pc_queue_init(&metadata_queue, (char *)&metadata_queue_items, sizeof(metadata_package), - metadata_queue_size); - int ret = pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL); - if (ret) - debug(1, "Failed to create metadata thread!"); -} - -int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier, - int block) { - metadata_package pack; - pack.type = type; - pack.code = code; - pack.data = data; - pack.length = length; - if (carrier) - msg_retain(carrier); - pack.carrier = carrier; - int rc = pc_queue_add_item(&metadata_queue, &pack, block); - if ((rc == EBUSY) && (carrier)) - msg_free(carrier); - if (rc == EBUSY) - warn("Metadata queue is busy, dropping message of type 0x%08X, code 0x%08X.", type, code); - return rc; -} - -static void handle_set_parameter_metadata(rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp) { - char *cp = req->content; - int cl = req->contentlength; - - unsigned int off = 8; - - // inform the listener that a set of metadata is starting - // this doesn't include the cover art though... - - // parameters: type, code, pointer to data or NULL, length of data or NULL, the rtsp_message or - // NULL - // the rtsp_message is sent for 'core' messages, because it contains the data and must not be - // freed until the data has been read. So, it is passed to send_metadata to be retained, - // sent to the thread where metadata is processed and released (and probably freed). - - // The reading of the parameters is a bit complex - // If the rtsp_message field is non-null, then it represents an rtsp_message which should be freed - // in the thread handler when the parameter pointed to by the pointer and specified by the length - // is finished with - // If the rtsp_message is NULL, then if the pointer is non-null, it points to a malloc'ed block - // and should be freed when the thread is finished with it. The length of the data in the block is - // given in length - // If the rtsp_message is NULL and the pointer is also NULL, nothing further is done. - - send_metadata('ssnc', 'mdst', NULL, 0, NULL, 1); - - while (off < cl) { - // pick up the metadata tag as an unsigned longint - uint32_t itag = ntohl(*(uint32_t *)(cp + off)); - off += sizeof(uint32_t); - - // pick up the length of the data - uint32_t vl = ntohl(*(uint32_t *)(cp + off)); - off += sizeof(uint32_t); - - // pass the data over - if (vl == 0) - send_metadata('core', itag, NULL, 0, NULL, 1); - else - send_metadata('core', itag, (char *)(cp + off), vl, req, 1); - - // move on to the next item - off += vl; - } - - // inform the listener that a set of metadata is ending - send_metadata('ssnc', 'mden', NULL, 0, NULL, 1); - // send the user some shairport-originated metadata - // send the name of the player, e.g. "Joe's iPhone" or "iTunes" - send_metadata('ssnc', 'sndr', strdup(sender_name), strlen(sender_name), NULL, 1); -} - -#endif - -static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - // if (!req->contentlength) - // debug(1, "received empty SET_PARAMETER request."); - - char *ct = msg_get_header(req, "Content-Type"); - - if (ct) { - debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct); -#ifdef CONFIG_METADATA - if (!strncmp(ct, "application/x-dmap-tagged", 25)) { - debug(2, "received metadata tags in SET_PARAMETER request."); - handle_set_parameter_metadata(conn, req, resp); - } else if (!strncmp(ct, "image", 5)) { - // debug(1, "received image in SET_PARAMETER request."); - // note: the image/type tag isn't reliable, so it's not being sent - // -- best look at the first few bytes of the image - send_metadata('ssnc', 'PICT', req->content, req->contentlength, req, 1); - } else -#endif - if (!strncmp(ct, "text/parameters", 15)) { - debug(2, "received parameters in SET_PARAMETER request."); - handle_set_parameter_parameter(conn, req, resp); - } else { - debug(1, "received unknown Content-Type \"%s\" in SET_PARAMETER request.", ct); - } - } else { - debug(1, "missing Content-Type header in SET_PARAMETER request."); - } - - resp->respcode = 200; -} - -static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - // interrupt session if permitted - if ((config.allow_session_interruption == 1) || (pthread_mutex_trylock(&play_lock) == 0)) { - resp->respcode = 456; // 456 - Header Field Not Valid for Resource - char *paesiv = NULL; - char *prsaaeskey = NULL; - char *pfmtp = NULL; - char *cp = req->content; - int cp_left = req->contentlength; - char *next; - while (cp_left && cp) { - next = nextline(cp, cp_left); - cp_left -= next - cp; - - if (!strncmp(cp, "a=fmtp:", 7)) - pfmtp = cp + 7; - - if (!strncmp(cp, "a=aesiv:", 8)) - paesiv = cp + 8; - - if (!strncmp(cp, "a=rsaaeskey:", 12)) - prsaaeskey = cp + 12; - - cp = next; - } - - paesiv=NULL; - - if (paesiv==NULL) { - warn("AESIV missing from ANNOUNCE"); - goto out; - } - - if (prsaaeskey==NULL) { - warn("RSAAESKEY missing from ANNOUNCE"); - goto out; - } - - if (pfmtp==NULL) { - warn("FMTP missing from ANNOUNCE"); - goto out; - } - - int len, keylen; - uint8_t *aesiv = base64_dec(paesiv, &len); - if (len != 16) { - warn("client announced aeskey of %d bytes, wanted 16", len); - free(aesiv); - goto out; - } - memcpy(conn->stream.aesiv, aesiv, 16); - free(aesiv); - - uint8_t *rsaaeskey = base64_dec(prsaaeskey, &len); - uint8_t *aeskey = rsa_apply(rsaaeskey, len, &keylen, RSA_MODE_KEY); - free(rsaaeskey); - if (keylen != 16) { - warn("client announced rsaaeskey of %d bytes, wanted 16", keylen); - free(aeskey); - goto out; - } - memcpy(conn->stream.aeskey, aeskey, 16); - free(aeskey); - - int i; - for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++) - conn->stream.fmtp[i] = atoi(strsep(&pfmtp, " \t")); - - char *hdr = msg_get_header(req, "X-Apple-Client-Name"); - if (hdr) { - strncpy(sender_name, hdr, 1024); - debug(1, "Play connection from \"%s\".", hdr); - } else { - hdr = msg_get_header(req, "User-Agent"); - if (hdr) { - debug(1, "Play connection from \"%s\".", hdr); - strncpy(sender_name, hdr, 1024); - } else - sender_name[0] = 0; - } - resp->respcode = 200; - } else { - resp->respcode = 453; - debug(1, "Already playing."); - } - -out: - if (resp->respcode != 200 && resp->respcode != 453) { - pthread_mutex_unlock(&play_lock); - } -} - -static struct method_handler { - char *method; - void (*handler)(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp); -} method_handlers[] = {{"OPTIONS", handle_options}, - {"ANNOUNCE", handle_announce}, - {"FLUSH", handle_flush}, - {"TEARDOWN", handle_teardown}, - {"SETUP", handle_setup}, - {"GET_PARAMETER", handle_ignore}, - {"SET_PARAMETER", handle_set_parameter}, - {"RECORD", handle_record}, - {NULL, NULL}}; - -static void apple_challenge(int fd, rtsp_message *req, rtsp_message *resp) { - char *hdr = msg_get_header(req, "Apple-Challenge"); - if (!hdr) - return; - - SOCKADDR fdsa; - socklen_t sa_len = sizeof(fdsa); - getsockname(fd, (struct sockaddr *)&fdsa, &sa_len); - - int chall_len; - uint8_t *chall = base64_dec(hdr, &chall_len); - uint8_t buf[48], *bp = buf; - int i; - memset(buf, 0, sizeof(buf)); - - if (chall_len > 16) { - warn("oversized Apple-Challenge!"); - free(chall); - return; - } - memcpy(bp, chall, chall_len); - free(chall); - bp += chall_len; - -#ifdef AF_INET6 - if (fdsa.SAFAMILY == AF_INET6) { - struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)(&fdsa); - memcpy(bp, sa6->sin6_addr.s6_addr, 16); - bp += 16; - } else -#endif - { - struct sockaddr_in *sa = (struct sockaddr_in *)(&fdsa); - memcpy(bp, &sa->sin_addr.s_addr, 4); - bp += 4; - } - - for (i = 0; i < 6; i++) - *bp++ = config.hw_addr[i]; - - int buflen, resplen; - buflen = bp - buf; - if (buflen < 0x20) - buflen = 0x20; - - uint8_t *challresp = rsa_apply(buf, buflen, &resplen, RSA_MODE_AUTH); - char *encoded = base64_enc(challresp, resplen); - - // strip the padding. - char *padding = strchr(encoded, '='); - if (padding) - *padding = 0; - - msg_add_header(resp, "Apple-Response", encoded); - free(challresp); - free(encoded); -} - -static char *make_nonce(void) { - uint8_t random[8]; - int fd = open("/dev/random", O_RDONLY); - if (fd < 0) - die("could not open /dev/random!"); - int ignore = read(fd, random, sizeof(random)); - close(fd); - return base64_enc(random, 8); -} - -static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { - - if (!config.password) - return 0; - if (!*nonce) { - *nonce = make_nonce(); - goto authenticate; - } - - char *hdr = msg_get_header(req, "Authorization"); - if (!hdr || strncmp(hdr, "Digest ", 7)) - goto authenticate; - - char *realm = strstr(hdr, "realm=\""); - char *username = strstr(hdr, "username=\""); - char *response = strstr(hdr, "response=\""); - char *uri = strstr(hdr, "uri=\""); - - if (!realm || !username || !response || !uri) - goto authenticate; - - char *quote; - realm = strchr(realm, '"') + 1; - if (!(quote = strchr(realm, '"'))) - goto authenticate; - *quote = 0; - username = strchr(username, '"') + 1; - if (!(quote = strchr(username, '"'))) - goto authenticate; - *quote = 0; - response = strchr(response, '"') + 1; - if (!(quote = strchr(response, '"'))) - goto authenticate; - *quote = 0; - uri = strchr(uri, '"') + 1; - if (!(quote = strchr(uri, '"'))) - goto authenticate; - *quote = 0; - - uint8_t digest_urp[16], digest_mu[16], digest_total[16]; - -#ifdef HAVE_LIBSSL - MD5_CTX ctx; - - MD5_Init(&ctx); - MD5_Update(&ctx, username, strlen(username)); - MD5_Update(&ctx, ":", 1); - MD5_Update(&ctx, realm, strlen(realm)); - MD5_Update(&ctx, ":", 1); - MD5_Update(&ctx, config.password, strlen(config.password)); - MD5_Final(digest_urp, &ctx); - MD5_Init(&ctx); - MD5_Update(&ctx, req->method, strlen(req->method)); - MD5_Update(&ctx, ":", 1); - MD5_Update(&ctx, uri, strlen(uri)); - MD5_Final(digest_mu, &ctx); -#endif - -#ifdef HAVE_LIBPOLARSSL - md5_context tctx; - md5_starts(&tctx); - md5_update(&tctx, (const unsigned char *)username, strlen(username)); - md5_update(&tctx, (unsigned char *)":", 1); - md5_update(&tctx, (const unsigned char *)realm, strlen(realm)); - md5_update(&tctx, (unsigned char *)":", 1); - md5_update(&tctx, (const unsigned char *)config.password, strlen(config.password)); - md5_finish(&tctx, digest_urp); - md5_starts(&tctx); - md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method)); - md5_update(&tctx, (unsigned char *)":", 1); - md5_update(&tctx, (const unsigned char *)uri, strlen(uri)); - md5_finish(&tctx, digest_mu); -#endif - - int i; - unsigned char buf[33]; - for (i = 0; i < 16; i++) - sprintf((char *)buf + 2 * i, "%02X", digest_urp[i]); - -#ifdef HAVE_LIBSSL - MD5_Init(&ctx); - MD5_Update(&ctx, buf, 32); - MD5_Update(&ctx, ":", 1); - MD5_Update(&ctx, *nonce, strlen(*nonce)); - MD5_Update(&ctx, ":", 1); - for (i = 0; i < 16; i++) - sprintf(buf + 2 * i, "%02X", digest_mu[i]); - MD5_Update(&ctx, buf, 32); - MD5_Final(digest_total, &ctx); -#endif - -#ifdef HAVE_LIBPOLARSSL - md5_starts(&tctx); - md5_update(&tctx, buf, 32); - md5_update(&tctx, (unsigned char *)":", 1); - md5_update(&tctx, (const unsigned char *)*nonce, strlen(*nonce)); - md5_update(&tctx, (unsigned char *)":", 1); - for (i = 0; i < 16; i++) - sprintf((char *)buf + 2 * i, "%02X", digest_mu[i]); - md5_update(&tctx, buf, 32); - md5_finish(&tctx, digest_total); -#endif - - for (i = 0; i < 16; i++) - sprintf((char *)buf + 2 * i, "%02X", digest_total[i]); - - if (!strcmp(response, (const char *)buf)) - return 0; - warn("auth failed"); - -authenticate: - resp->respcode = 401; - int hdrlen = strlen(*nonce) + 40; - char *authhdr = malloc(hdrlen); - snprintf(authhdr, hdrlen, "Digest realm=\"taco\", nonce=\"%s\"", *nonce); - msg_add_header(resp, "WWW-Authenticate", authhdr); - free(authhdr); - return 1; -} - -static void *rtsp_conversation_thread_func(void *pconn) { - // SIGUSR1 is used to interrupt this thread if blocked for read - sigset_t set; - sigemptyset(&set); - sigaddset(&set, SIGUSR1); - pthread_sigmask(SIG_UNBLOCK, &set, NULL); - - rtsp_conn_info *conn = pconn; - - rtsp_message *req, *resp; - char *hdr, *auth_nonce = NULL; - - enum rtsp_read_request_response reply; - - do { - reply = rtsp_read_request(conn->fd, &req); - if (reply == rtsp_read_request_response_ok) { - resp = msg_init(); - resp->respcode = 400; - - apple_challenge(conn->fd, req, resp); - hdr = msg_get_header(req, "CSeq"); - if (hdr) - msg_add_header(resp, "CSeq", hdr); - msg_add_header(resp, "Audio-Jack-Status", "connected; type=analog"); - - if (rtsp_auth(&auth_nonce, req, resp)) - goto respond; - - struct method_handler *mh; - for (mh = method_handlers; mh->method; mh++) { - if (!strcmp(mh->method, req->method)) { - // debug(1,"RTSP Packet received of type \"%s\":",mh->method), - // msg_print_debug_headers(req); - mh->handler(conn, req, resp); - // debug(1,"RTSP Response:"); - // msg_print_debug_headers(resp); - break; - } - } - - respond: - msg_write_response(conn->fd, resp); - msg_free(req); - msg_free(resp); - } else { - if (reply != rtsp_read_request_response_shutdown_requested) - debug(1, "rtsp_read_request error %d, packet ignored.", (int)reply); - } - } while (reply != rtsp_read_request_response_shutdown_requested); - - debug(1, "closing RTSP connection."); - if (conn->fd > 0) - close(conn->fd); - if (rtsp_playing()) { - rtp_shutdown(); - player_stop(); - pthread_mutex_unlock(&play_lock); - please_shutdown = 0; - pthread_mutex_unlock(&playing_mutex); - } - if (auth_nonce) - free(auth_nonce); - conn->running = 0; - debug(2, "terminating RTSP thread."); - return NULL; -} - -// this function is not thread safe. -static const char *format_address(struct sockaddr *fsa) { - static char string[INETx_ADDRSTRLEN]; - void *addr; -#ifdef AF_INET6 - if (fsa->sa_family == AF_INET6) { - struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)(fsa); - addr = &(sa6->sin6_addr); - } else -#endif - { - struct sockaddr_in *sa = (struct sockaddr_in *)(fsa); - addr = &(sa->sin_addr); - } - return inet_ntop(fsa->sa_family, addr, string, sizeof(string)); -} - -void rtsp_listen_loop(void) { - struct addrinfo hints, *info, *p; - char portstr[6]; - int *sockfd = NULL; - int nsock = 0; - int i, ret; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_PASSIVE; - - snprintf(portstr, 6, "%d", config.port); - - // debug(1,"listen socket port request is \"%s\".",portstr); - - ret = getaddrinfo(NULL, portstr, &hints, &info); - if (ret) { - die("getaddrinfo failed: %s", gai_strerror(ret)); - } - - for (p = info; p; p = p->ai_next) { - int fd = socket(p->ai_family, p->ai_socktype, IPPROTO_TCP); - int yes = 1; - - // Handle socket open failures if protocol unavailable (or IPV6 not handled) - if (fd == -1) { - // debug(1, "Failed to get socket: fam=%d, %s\n", p->ai_family, strerror(errno)); - continue; - } - - ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - -#ifdef IPV6_V6ONLY - // some systems don't support v4 access on v6 sockets, but some do. - // since we need to account for two sockets we might as well - // always. - if (p->ai_family == AF_INET6) { - ret |= setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)); - } -#endif - - if (!ret) - ret = bind(fd, p->ai_addr, p->ai_addrlen); - - // one of the address families will fail on some systems that - // report its availability. do not complain. - if (ret) { - debug(1, "Failed to bind to address %s.", format_address(p->ai_addr)); - continue; - } - - listen(fd, 5); - nsock++; - sockfd = realloc(sockfd, nsock * sizeof(int)); - sockfd[nsock - 1] = fd; - } - - freeaddrinfo(info); - - if (!nsock) - die("could not bind any listen sockets!"); - - int maxfd = -1; - fd_set fds; - FD_ZERO(&fds); - for (i = 0; i < nsock; i++) { - if (sockfd[i] > maxfd) - maxfd = sockfd[i]; - } - - mdns_register(); - - // printf("Listening for connections."); - // shairport_startup_complete(); - - int acceptfd; - struct timeval tv; - while (1) { - tv.tv_sec = 300; - tv.tv_usec = 0; - - for (i = 0; i < nsock; i++) - FD_SET(sockfd[i], &fds); - - ret = select(maxfd + 1, &fds, 0, 0, &tv); - if (ret < 0) { - if (errno == EINTR) - continue; - break; - } - - cleanup_threads(); - - acceptfd = -1; - for (i = 0; i < nsock; i++) { - if (FD_ISSET(sockfd[i], &fds)) { - acceptfd = sockfd[i]; - break; - } - } - if (acceptfd < 0) // timeout - continue; - - rtsp_conn_info *conn = malloc(sizeof(rtsp_conn_info)); - memset(conn, 0, sizeof(rtsp_conn_info)); - socklen_t slen = sizeof(conn->remote); - - debug(1, "new RTSP connection."); - conn->fd = accept(acceptfd, (struct sockaddr *)&conn->remote, &slen); - if (conn->fd < 0) { - perror("failed to accept connection"); - free(conn); - } else { - pthread_t rtsp_conversation_thread; - ret = pthread_create(&rtsp_conversation_thread, NULL, rtsp_conversation_thread_func, conn); - if (ret) - die("Failed to create RTSP receiver thread!"); - - conn->thread = rtsp_conversation_thread; - conn->running = 1; - track_thread(conn); - } - } - perror("select"); - die("fell out of the RTSP select loop"); -} diff --git a/newcfg.ac b/newcfg.ac deleted file mode 100644 index d80cc9db..00000000 --- a/newcfg.ac +++ /dev/null @@ -1,232 +0,0 @@ -# -*- Autoconf -*- -# Process this file with autoconf to produce a configure script. - -AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.4], [mikebrady@eircom.net]) -AM_INIT_AUTOMAKE -AC_CONFIG_SRCDIR([shairport.c]) -AC_CONFIG_HEADERS([config.h]) - -# Derived from the Avahi configure.ac file -# -# Specifying the OS type, defaulting to linux. -# -AC_ARG_WITH(os_type, AS_HELP_STRING([--with-os-type=OSType],[Specify the distribution to target: One of linux darwin])) -if test "z$with_os_type" = "z"; then - with_os_type="linux" -fi -with_os_type=`echo ${with_os_type} | tr '[[:upper:]]' '[[:lower:]]' ` - -# Checks for programs. -AC_PROG_CC -AC_PROG_INSTALL - -AC_ARG_WITH([pkg_config], -[ --with-pkg-config = use pkg-config to find libraries], ,[with_pkg_config=1]) - -PKG_PROG_PKG_CONFIG([0.9.0]) - -# Checks for libraries. -if test "x${with_os_type}" = xlinux; then - AC_CHECK_LIB([rt],[clock_gettime], , AC_MSG_ERROR(librt needed)) -fi - -##### Some build systems are not fully using pkg-config, so we can use the flag ${with_pkg_config} on a case-by-case basis -##### to control how to deal with them - - -AC_CHECK_LIB([daemon],[daemon_log], , AC_MSG_ERROR(libdaemon needed)) -AC_CHECK_LIB([pthread],[pthread_create], , AC_MSG_ERROR(pthread library needed)) -AC_CHECK_LIB([m],[exp], , AC_MSG_ERROR(maths library needed)) - -AC_MSG_RESULT(>>Including libpopt) -if test "x${with_pkg_config}" = x1 ; then - PKG_CHECK_MODULES( - [POPT], [popt], - [LIBS="${POPT_LIBS} ${LIBS}"]) -else - AC_CHECK_LIB([popt],[poptGetContext], , AC_MSG_ERROR(libpopt needed)) -fi -AC_DEFINE([HAVE_LIBPOPT],[1],[Define to 1 if you have popt]) - -AC_ARG_WITH([dummy],[ --with-dummy = include the dummy audio back end ],[AC_MSG_RESULT(>>Including the dummy audio back end) AC_DEFINE([CONFIG_DUMMY], 1, [Needed by the compiler.]) ], ) -AM_CONDITIONAL([USE_DUMMY], [test "x$with_dummy" = "x1" ]) - -AC_ARG_WITH([stdout],[ --with-stdout = include the stdout audio back end ],[ AC_MSG_RESULT(>>Including the stdout audio back end) AC_DEFINE([CONFIG_STDOUT], 1, [Needed by the compiler.]) ], ) -AM_CONDITIONAL([USE_STDOUT], [test "x$with_stdout" = "x1" ]) - -AC_ARG_WITH([pipe],[ --with-pipe = include the pipe audio back end ],[ AC_MSG_RESULT(>>Including the pipe audio back end) AC_DEFINE([CONFIG_PIPE], 1, [Needed by the compiler.]) ], ) -AM_CONDITIONAL([USE_PIPE], [test "x$with_pipe" = "x1" ]) - -AC_ARG_WITH([initscript], -[ --with-initscript = include a startup script], ,[with_initscript=1]) -AM_CONDITIONAL([INSTALL_INITSCRIPT], [test "x$with_initscript" = "x1"]) - -# Check if we want to support reading arguments from the command line -AC_ARG_WITH([command_line_argument_support], -[ --with-command-line-argument-support = read configuration from command line arguments], ,[with_command_line_argument_support=1]) - -if test "x${with_command_line_argument_support}" = x1 ; then - AC_MSG_RESULT(>>Including command line argument support) - AC_DEFINE([COMMAND_LINE_ARGUMENT_SUPPORT],[1],[Define to 1 if you want to support command line arguments]) -fi - -# Check if we want to support reading from a configuration file chosen -- i.e. take parameters from /etc/shairport-sync.conf -AC_ARG_WITH([configfile_support], -[ --with-configfile-support = support reading of configuration from a configuration file], ,[with_configfile_support=1]) - -if test "x${with_configfile_support}" = x1 ; then - AC_MSG_RESULT(>>Including configuration file support) - if test "x${with_pkg_config}" = x1 ; then - PKG_CHECK_MODULES( - [LIBCONFIG], [libconfig], - [LIBS="${LIBCONFIG_LIBS} ${LIBS}"]) - else - AC_CHECK_LIB([config],[config_init], , AC_MSG_ERROR(libconfig library needed)) - fi - AC_DEFINE([SUPPORT_CONFIG_FILES],[1],[Define to 1 if you want to support configuration files]) -fi - -AC_ARG_WITH([configfiles], -[ --with-configfiles = include configuration files in installation ], ,[with_configfiles=1]) -AM_CONDITIONAL([INSTALL_CONFIG_FILES], [test "x$with_configfiles" = "x1" -a "x$with_configfile_support" = "x1" ]) - - -# Check to see if we should include the initscript - -# Look for piddir flag -AC_ARG_WITH(piddir, [ --with-piddir= Specify a pathname to a directory in which to write the PID file.], [ - AC_MSG_CHECKING(--with-piddir argument) - if test "x${with_piddir}" = x -o "x${with_piddir}" = xyes ; then - AC_MSG_RESULT(not found) - AC_MSG_ERROR(when you use the --with-piddir directive you must specify the pathname of the directory into which the PID file will be written) - fi - AC_MSG_RESULT(${with_piddir}) - HAS_CUSTOMPIDDIR=1 - AC_SUBST(CUSTOM_PID_DIR,["${with_piddir}"]) - AC_DEFINE([USE_CUSTOM_PID_DIR],1,[Hook up special proc to libdaemon to point to this directory])], ) -AM_CONDITIONAL([USE_CUSTOMPIDDIR], [test "x$HAS_CUSTOMPIDDIR" = "x1"]) - -# Check --with-ssl=argument -AC_ARG_WITH(ssl, [ choose --with-ssl=openssl or --with-ssl=polarssl for encryption services], [ - AC_MSG_CHECKING(encryption libraries chosen) - if test "x${with_ssl}" = x -o "x${with_ssl}" = xyes ; then - AC_MSG_RESULT(not found) - AC_MSG_ERROR(choose either "openssl" or "polarssl" encryption) - fi - if test "x${with_ssl}" = xopenssl ; then - PKG_CHECK_MODULES( - [SSL], [libssl,libcrypto], - [LIBS="${SSL_LIBS} ${LIBS}" - AC_DEFINE([HAVE_LIBCRYPTO],[1],[Define to 1 if you have libcrypto]) - AC_DEFINE([HAVE_LIBSSL],[1],[Define to 1 if you have libssl])]) - elif test "x${with_ssl}" = xpolarssl ; then - AC_CHECK_LIB([polarssl],[ssl_init], , AC_MSG_ERROR(PolarSSL selected but the library cannot be found!)) - else - AC_MSG_ERROR(unknown option "${with_ssl}"." Please choose with "openssl" or "polarssl") - fi -], ) - -# Look for soxr flag -AC_ARG_WITH(soxr, [ --with-soxr = choose libsoxr for high-quality interpolation], [ - AC_MSG_RESULT(>>Including support for soxr-based interpolation) - AC_CHECK_LIB([soxr],[soxr_create], , AC_MSG_ERROR(soxr support requested but libsoxr not found!))], ) - -# Look for metadata flag -- set flag for conditional compilation -AC_ARG_WITH(metadata, [ --with-metadata = include support for a metadata feed], [ - AC_MSG_RESULT(>>Including metadata support) - HAS_METADATA=1 - AC_DEFINE([CONFIG_METADATA], 1, [Needed by the compiler.])], ) -AM_CONDITIONAL([USE_METADATA], [test "x$HAS_METADATA" = "x1"]) - -# What follows is a bit messy, because if the relevant library is requested, a compiler flag is defined, a file is included in the compilation -# and the relevant link files are added. - -# Look for avahi flag -AC_ARG_WITH(avahi, [ --with-avahi = choose Avahi-based mDNS support], [ - AC_MSG_RESULT(>>Including Avahi mDNS support) - HAS_AVAHI=1 - AC_DEFINE([CONFIG_AVAHI], 1, [Needed by the compiler.]) - AC_CHECK_LIB([avahi-client], [avahi_client_new], , AC_MSG_ERROR(Avahi support requires the avahi-client library!)) - AC_CHECK_LIB([avahi-common],[avahi_strerror], , AC_MSG_ERROR(Avahi support requires the avahi-common library!))], ) -AM_CONDITIONAL([USE_AVAHI], [test "x$HAS_AVAHI" = "x1"]) - -# Look for tinysvcmdns flag -AC_ARG_WITH(tinysvcmdns, [ --with-tinysvcmdns = choose tinysvcmdns-based mDNS support], [ - AC_MSG_RESULT(>>Including tinysvcmdns mDNS support) - HAS_TINYSVCMDNS=1 - AC_DEFINE([CONFIG_TINYSVCMDNS], 1, [Needed by the compiler.])], ) -AM_CONDITIONAL([USE_TINYSVCMDNS], [test "x$HAS_TINYSVCMDNS" = "x1"]) - -# Look for ALSA flag -AC_ARG_WITH(alsa, [ --with-alsa = choose native ALSA API support (GNU/Linux only)], - [AC_MSG_RESULT(>>Including an ALSA back end) - HAS_ALSA=1 - AM_CONDITIONAL([USE_ALSA], [test 0]) - AC_DEFINE([CONFIG_ALSA], 1, [Needed by the compiler.]) - PKG_CHECK_MODULES( - [ALSA], [alsa], - [LIBS="${ALSA_LIBS} ${LIBS}" - AC_DEFINE([HAVE_LIBASOUND],[1],[Define to 1 if you have ALSA])])]) -AM_CONDITIONAL([USE_ALSA], [test "x$HAS_ALSA" = "x1"]) - -# Look for SNDIO flag -AC_ARG_WITH(sndio, [ --with-sndio = choose native SNDIO API support (FreeBSD) -- probably broken], [ - AC_MSG_RESULT(>>Including a SNDIO back end --- N.B. this is probably broken!) - HAS_SNDIO=1 - AC_DEFINE([CONFIG_SNDIO], 1, [Needed by the compiler.]) - AC_CHECK_LIB([sndio], [sio_open], , AC_MSG_ERROR(SNDIO support requires the sndio library!))], ) -AM_CONDITIONAL([USE_SNDIO], [test "x$HAS_SNDIO" = "x1"]) - -# Look for AO flag -AC_ARG_WITH(ao, [ --with-ao = choose native AO (Audio Output?) API support -- probably broken], [ - AC_MSG_RESULT(>>Including an AO back end --- N.B. this is probably broken!) - HAS_AO=1 - AC_DEFINE([CONFIG_AO], 1, [Needed by the compiler.]) - AC_CHECK_LIB([ao], [ao_initialize], , AC_MSG_ERROR(AO support requires the ao library!))], ) -AM_CONDITIONAL([USE_AO], [test "x$HAS_AO" = "x1"]) - -# Look for pulseaudio flag -AC_ARG_WITH(pulseaudio, [ --with-pulseaudio = choose native PulseAudio API support -- N.B. no synchronisation -- so underflow or overflow is inevitable!], [ - AC_MSG_RESULT(>>Including a PulseAudio back end -- N.B. no synchronisation -- so underflow or overflow is inevitable!) - HAS_PULSE=1 - AC_DEFINE([CONFIG_PULSE], 1, [Needed by the compiler.]) - AC_CHECK_LIB([pulse-simple], [pa_simple_new], , AC_MSG_ERROR(PulseAudio support requires the pulse-simple library!)) - AC_CHECK_LIB([pulse], [pa_stream_peek], , AC_MSG_ERROR(PulseAudio support requires the libpulse-dev library!))], ) -AM_CONDITIONAL([USE_PULSE], [test "x$HAS_PULSE" = "x1"]) - -# Look for dns_sd flag -AC_ARG_WITH(dns_sd, [ --with-dns_sd = choose dns_sd mDNS support], [ - AC_MSG_RESULT(>>Including dns_sd for mDNS support) - HAS_DNS_SD=1 - AC_DEFINE([CONFIG_HAVE_DNS_SD_H], 1, [Needed by the compiler.]) - AC_CHECK_LIB([dns_sd], [DNSServiceRefDeallocate], , AC_MSG_ERROR(dns_sd support requires the dns_sd library!))], ) -AM_CONDITIONAL([USE_DNS_SD], [test "x$HAS_DNS_SD" = "x1"]) - -# Checks for header files. -AC_HEADER_STDC -AC_CHECK_HEADERS([getopt_long.h]) -AC_CHECK_HEADERS([arpa/inet.h fcntl.h mach/mach.h memory.h netdb.h netinet/in.h stdint.h stdlib.h string.h sys/ioctl.h sys/socket.h sys/time.h syslog.h unistd.h]) - -# Checks for typedefs, structures, and compiler characteristics. -AC_C_INLINE -AC_TYPE_INT16_T -AC_TYPE_INT32_T -AC_TYPE_INT64_T -AC_TYPE_PID_T -AC_TYPE_SIZE_T -AC_TYPE_SSIZE_T -AC_TYPE_UINT16_T -AC_TYPE_UINT32_T -AC_TYPE_UINT64_T -AC_TYPE_UINT8_T - -# Checks for library functions. -AC_FUNC_ERROR_AT_LINE -AC_FUNC_FORK -AC_FUNC_MALLOC -AC_FUNC_REALLOC -AC_CHECK_FUNCS([clock_gettime gethostname gettimeofday inet_ntoa memchr memmove memset pow select socket stpcpy strcasecmp strchr strdup strerror strstr strtol strtoul]) - -AC_CONFIG_FILES([Makefile man/Makefile]) -AC_OUTPUT From e9b36458f052b5d4ebbcfc9d8e420e1493411090 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 16 Jun 2015 15:21:30 +0100 Subject: [PATCH 105/220] Update RELEASENOTES.md --- RELEASENOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d18ee425..d169e68b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -5,6 +5,7 @@ Version 2.3.4 * Bugfixes * Fix an out-of-stack-space error that can occur in certain cases (thanks to https://github.com/joerg-krause). * Fix a couple of compiler warnings (thanks to https://github.com/joerg-krause). + * Tidy up a couple of debug messages that were emitting misleading information. Version 2.3.3.2 ---- From 2a1fe7afeda24b3ca4f3a6e6b9a461690bbc2bf4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 17 Jun 2015 12:10:33 +0100 Subject: [PATCH 106/220] Add ability to deal with an unencrypted audio stream --- player.c | 47 ++++++++++++++++++++------------------- player.h | 1 + rtsp.c | 67 ++++++++++++++++++++++++++------------------------------ 3 files changed, 56 insertions(+), 59 deletions(-) diff --git a/player.c b/player.c index df12eb27..94afea42 100644 --- a/player.c +++ b/player.c @@ -83,9 +83,9 @@ static aes_context dctx; static pthread_t player_thread; static int please_stop; +static int encrypted; // Normally the audio is encrypted, but it may not be -static int - connection_state_to_output; // if true, then play incoming stuff; if false drop everything +static int connection_state_to_output; // if true, then play incoming stuff; if false drop everything static alac_file *decoder_info; @@ -226,23 +226,23 @@ static void alac_decode(short *dest, uint8_t *buf, int len) { unsigned char packet[MAX_PACKET]; unsigned char packetp[MAX_PACKET]; assert(len <= MAX_PACKET); - - unsigned char iv[16]; - int aeslen = len & ~0xf; - memcpy(iv, aesiv, sizeof(iv)); - -#ifdef HAVE_LIBPOLARSSL - aes_crypt_cbc(&dctx, AES_DECRYPT, aeslen, iv, buf, packet); -#endif -#ifdef HAVE_LIBSSL - AES_cbc_encrypt(buf, packet, aeslen, &aes, iv, AES_DECRYPT); -#endif - - memcpy(packet + aeslen, buf + aeslen, len - aeslen); - int outsize; - alac_decode_frame(decoder_info, packet, dest, &outsize); + if (encrypted) { + unsigned char iv[16]; + int aeslen = len & ~0xf; + memcpy(iv, aesiv, sizeof(iv)); +#ifdef HAVE_LIBPOLARSSL + aes_crypt_cbc(&dctx, AES_DECRYPT, aeslen, iv, buf, packet); +#endif +#ifdef HAVE_LIBSSL + AES_cbc_encrypt(buf, packet, aeslen, &aes, iv, AES_DECRYPT); +#endif + memcpy(packet + aeslen, buf + aeslen, len - aeslen); + alac_decode_frame(decoder_info, packet, dest, &outsize); + } else { + alac_decode_frame(decoder_info, buf, dest, &outsize); + } assert(outsize == FRAME_BYTES(frame_size)); } @@ -1236,20 +1236,21 @@ void player_flush(uint32_t timestamp) { int player_play(stream_cfg *stream) { packet_count = 0; + encrypted = stream->encrypted; if (config.buffer_start_fill > BUFFER_FRAMES) die("specified buffer starting fill %d > buffer size %d", config.buffer_start_fill, BUFFER_FRAMES); - + if (encrypted) { #ifdef HAVE_LIBPOLARSSL - memset(&dctx, 0, sizeof(aes_context)); - aes_setkey_dec(&dctx, stream->aeskey, 128); + memset(&dctx, 0, sizeof(aes_context)); + aes_setkey_dec(&dctx, stream->aeskey, 128); #endif #ifdef HAVE_LIBSSL - AES_set_decrypt_key(stream->aeskey, 128, &aes); + AES_set_decrypt_key(stream->aeskey, 128, &aes); #endif - - aesiv = stream->aesiv; + aesiv = stream->aesiv; + } init_decoder(stream->fmtp); // must be after decoder init init_buffer(); diff --git a/player.h b/player.h index 9f4b93d6..b9cc900a 100644 --- a/player.h +++ b/player.h @@ -4,6 +4,7 @@ #include "audio.h" typedef struct { + int encrypted; uint8_t aesiv[16], aeskey[16]; int32_t fmtp[12]; } stream_cfg; diff --git a/rtsp.c b/rtsp.c index 20c378a5..0c7fa9eb 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1135,20 +1135,16 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag cp = next; } - if (paesiv==NULL) { - warn("AESIV missing from ANNOUNCE"); - } - - if (prsaaeskey==NULL) { - warn("RSAAESKEY missing from ANNOUNCE"); - } - - if (pfmtp==NULL) { - warn("FMTP missing from ANNOUNCE"); + if ((paesiv==NULL) && (prsaaeskey==NULL)) { + debug(1,"Unencrypted session requested?"); + conn->stream.encrypted = 0; + } else { + conn->stream.encrypted = 1; + debug(1,"Encrypted session requested?"); } - if (!paesiv || !prsaaeskey || !pfmtp) { - warn("required params missing from the following ANNOUNCE message:"); + if (!pfmtp) { + warn("FMTP params missing from the following ANNOUNCE message:"); // print each line of the request content // the problem is that nextline has replace all returns, newlines, etc. by NULLs char *cp = req->content; @@ -1159,32 +1155,31 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag cp+=strlen(cp)+1; cp_left-=strlen(cp)+1; } - char *next; - goto out; } - - int len, keylen; - uint8_t *aesiv = base64_dec(paesiv, &len); - if (len != 16) { - warn("client announced aeskey of %d bytes, wanted 16", len); + + if (conn->stream.encrypted) { + int len, keylen; + uint8_t *aesiv = base64_dec(paesiv, &len); + if (len != 16) { + warn("client announced aeskey of %d bytes, wanted 16", len); + free(aesiv); + goto out; + } + memcpy(conn->stream.aesiv, aesiv, 16); free(aesiv); - goto out; - } - memcpy(conn->stream.aesiv, aesiv, 16); - free(aesiv); - uint8_t *rsaaeskey = base64_dec(prsaaeskey, &len); - uint8_t *aeskey = rsa_apply(rsaaeskey, len, &keylen, RSA_MODE_KEY); - free(rsaaeskey); - if (keylen != 16) { - warn("client announced rsaaeskey of %d bytes, wanted 16", keylen); + uint8_t *rsaaeskey = base64_dec(prsaaeskey, &len); + uint8_t *aeskey = rsa_apply(rsaaeskey, len, &keylen, RSA_MODE_KEY); + free(rsaaeskey); + if (keylen != 16) { + warn("client announced rsaaeskey of %d bytes, wanted 16", keylen); + free(aeskey); + goto out; + } + memcpy(conn->stream.aeskey, aeskey, 16); free(aeskey); - goto out; } - memcpy(conn->stream.aeskey, aeskey, 16); - free(aeskey); - int i; for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++) conn->stream.fmtp[i] = atoi(strsep(&pfmtp, " \t")); @@ -1446,11 +1441,11 @@ static void *rtsp_conversation_thread_func(void *pconn) { struct method_handler *mh; for (mh = method_handlers; mh->method; mh++) { if (!strcmp(mh->method, req->method)) { - // debug(1,"RTSP Packet received of type \"%s\":",mh->method), - // msg_print_debug_headers(req); + //debug(1,"RTSP Packet received of type \"%s\":",mh->method), + //msg_print_debug_headers(req); mh->handler(conn, req, resp); - // debug(1,"RTSP Response:"); - // msg_print_debug_headers(resp); + //debug(1,"RTSP Response:"); + //msg_print_debug_headers(resp); break; } } From 41f47f79f575843f3f0e507ffac5c5e64c674340 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 17 Jun 2015 18:42:05 +0100 Subject: [PATCH 107/220] Tidy up and move packet counter inside mutex. --- player.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/player.c b/player.c index 94afea42..3ae9bac7 100644 --- a/player.c +++ b/player.c @@ -294,9 +294,8 @@ static void free_buffer(void) { void player_put_packet(seq_t seqno, uint32_t timestamp, uint8_t *data, int len) { - packet_count++; - pthread_mutex_lock(&ab_mutex); + packet_count++; time_of_last_audio_packet = get_absolute_time_in_fp(); if (connection_state_to_output) { // if we are supposed to be processing these packets From 7bc6d08872099124a0ce98e6a23056cb80b2e61d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 17 Jun 2015 18:43:00 +0100 Subject: [PATCH 108/220] Enable control thread to handle retransmitted audio packets. Seems like a bug that they should show up here, but whaale does it. --- rtp.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/rtp.c b/rtp.c index 37e2eccb..b372d94f 100644 --- a/rtp.c +++ b/rtp.c @@ -152,7 +152,7 @@ static void *rtp_audio_receiver(void *arg) { static void *rtp_control_receiver(void *arg) { // we inherit the signal mask (SIGUSR1) reference_timestamp = 0; // nothing valid received yet - uint8_t packet[2048]; + uint8_t packet[2048], *pktp; struct timespec tn; uint64_t remote_time_of_sync, local_time_now, remote_time_now; uint32_t sync_rtp_timestamp, rtp_timestamp_less_latency; @@ -213,6 +213,24 @@ static void *rtp_control_receiver(void *arg) { } else { debug(1, "Sync packet received before we got a timing packet back."); } + } else if (packet[1] == 0xd6) { // resent audio data in the control path -- whaale only? + // debug(1, "Control Port -- Retransmitted Audio Data Packet received."); + pktp = packet+4; + plen -= 4; + seq_t seqno = ntohs(*(unsigned short *)(pktp + 2)); + + uint32_t timestamp = ntohl(*(unsigned long *)(pktp + 4)); + + pktp += 12; + plen -= 12; + + // check if packet contains enough content to be reasonable + if (plen >= 16) { + player_put_packet(seqno, timestamp, pktp, plen); + continue; + } else { + debug(1, "Too-short retransmitted audio packet received in control port, ignored."); + } } else debug(1, "Control Port -- Unknown RTP packet of type 0x%02X length %d.", packet[1], nread); } From 9d12f52abb467c64b5a5a9e6ffd7937e9636ae2a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 17 Jun 2015 20:50:52 +0100 Subject: [PATCH 109/220] Quieten a debug message --- rtp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtp.c b/rtp.c index b372d94f..06df8ac2 100644 --- a/rtp.c +++ b/rtp.c @@ -442,7 +442,7 @@ static int bind_port(SOCKADDR *remote, int *sock) { } while ((ret<0) && (errno==EADDRINUSE) && (desired_port!=0) && (desired_port++ < config.udp_port_base+config.udp_port_range)); - debug(1,"UDP port chosen: %d.",desired_port); + // debug(1,"UDP port chosen: %d.",desired_port); if (ret < 0) { die("error: could not bind a UDP port!"); From b352e7cccaef13d4d9890a98ec6e9077657c469e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 17 Jun 2015 20:51:56 +0100 Subject: [PATCH 110/220] Fix .. generation, add DACP-ID and Active-Remote metadata --- rtsp.c | 54 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/rtsp.c b/rtsp.c index 0c7fa9eb..24ff917a 100644 --- a/rtsp.c +++ b/rtsp.c @@ -628,13 +628,18 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message * char *ar = msg_get_header(req, "Active-Remote"); if (ar) { - // debug(1,"Active-Remote string seen: \"%s\".",ar); + debug(1,"Active-Remote string seen: \"%s\".",ar); // get the active remote char *p; active_remote = strtoul(ar, &p, 10); - // debug(1,"Active Remote is %u.",active_remote); + send_metadata('ssnc','acre',ar,strlen(ar),req,1); } + ar = msg_get_header(req, "DACP-ID"); + if (ar) { + debug(1,"DACP-ID string seen: \"%s\".",ar); + send_metadata('ssnc','daid',ar,strlen(ar),req,1); + } // select latency // if iTunes V10 or later is detected, use the iTunes latency setting // if AirPlay is detected, use the AirPlay latency setting @@ -942,13 +947,13 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) if (fd < 0) return; char thestring[1024]; - snprintf(thestring, 1024, "\n%x%x%u\n", type, code, + snprintf(thestring, 1024, "%x%x%u", type, code, length); ret = non_blocking_write(fd, thestring, strlen(thestring)); if (ret < 1) return; if ((data != NULL) && (length > 0)) { - snprintf(thestring, 1024, "\n"); + snprintf(thestring, 1024, "\n\n"); ret = non_blocking_write(fd, thestring, strlen(thestring)); if (ret < 1) // no reader return; @@ -973,11 +978,15 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) remaining_data += towrite_count; remaining_count -= towrite_count; } - snprintf(thestring, 1024, "\n\n"); + snprintf(thestring, 1024, ""); ret = non_blocking_write(fd, thestring, strlen(thestring)); if (ret < 1) // no reader return; } + snprintf(thestring, 1024, "\n"); + ret = non_blocking_write(fd, thestring, strlen(thestring)); + if (ret < 1) // no reader + return; } void *metadata_thread_function(void *ignore) { @@ -1006,6 +1015,24 @@ void metadata_init(void) { int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier, int block) { + + // parameters: type, code, pointer to data or NULL, length of data or NULL, the rtsp_message or + // NULL + // the rtsp_message is sent for 'core' messages, because it contains the data and must not be + // freed until the data has been read. So, it is passed to send_metadata to be retained, + // sent to the thread where metadata is processed and released (and probably freed). + + // The rtsp_message is also sent for certain non-'core' messages. + + // The reading of the parameters is a bit complex + // If the rtsp_message field is non-null, then it represents an rtsp_message which should be freed + // in the thread handler when the parameter pointed to by the pointer and specified by the length + // is finished with + // If the rtsp_message is NULL, then if the pointer is non-null, it points to a malloc'ed block + // and should be freed when the thread is finished with it. The length of the data in the block is + // given in length + // If the rtsp_message is NULL and the pointer is also NULL, nothing further is done. + metadata_package pack; pack.type = type; pack.code = code; @@ -1032,21 +1059,6 @@ static void handle_set_parameter_metadata(rtsp_conn_info *conn, rtsp_message *re // inform the listener that a set of metadata is starting // this doesn't include the cover art though... - // parameters: type, code, pointer to data or NULL, length of data or NULL, the rtsp_message or - // NULL - // the rtsp_message is sent for 'core' messages, because it contains the data and must not be - // freed until the data has been read. So, it is passed to send_metadata to be retained, - // sent to the thread where metadata is processed and released (and probably freed). - - // The reading of the parameters is a bit complex - // If the rtsp_message field is non-null, then it represents an rtsp_message which should be freed - // in the thread handler when the parameter pointed to by the pointer and specified by the length - // is finished with - // If the rtsp_message is NULL, then if the pointer is non-null, it points to a malloc'ed block - // and should be freed when the thread is finished with it. The length of the data in the block is - // given in length - // If the rtsp_message is NULL and the pointer is also NULL, nothing further is done. - send_metadata('ssnc', 'mdst', NULL, 0, NULL, 1); while (off < cl) { @@ -1140,7 +1152,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag conn->stream.encrypted = 0; } else { conn->stream.encrypted = 1; - debug(1,"Encrypted session requested?"); + debug(1,"Encrypted session requested"); } if (!pfmtp) { From c4e885047447e1baa58d19ea262058eba863e4ca Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 17 Jun 2015 21:07:43 +0100 Subject: [PATCH 111/220] Update RELEASENOTES.md --- RELEASENOTES.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d18ee425..f379adfb 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,12 @@ +Version 2.3.5 -- forthcoming +---- +* Enhancements + * Extra metadata: when a play session starts, the "Active-Remote" and "DACP-ID" fields -- information that can be used to identify the source -- are provided as metadata, with the type `ssnc` and the codes `acre` and `daid` respectively. The IDs are provided as strings. + * Unencrypted audio data. The iOS player "Whaale" attempts to send unencrypted audio, presumably to save processing effort; if unsuccessful, it will send encrypted audio as normal. Shairport Sync now recognises and handles unencrypted audio data. + * Handle retransmitted audio in the control channel. When a packet of audio is missed, Shairport Sync will ask for it to be retransmitted. Normally the retransmitted audio comes back the audio channel, but "Whaale" sends it back in the control channel. (I think this is a bug in "Whaale".) Shairport Sync will now correctly handle retransmitted audio packets coming back in the control channel. +* Bugfixes + * Generate properly-formed `..` items of information. + Version 2.3.4 ---- * Enhancement From 2627757e005e79043a93f28518e531ea465e93c8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 18 Jun 2015 16:12:15 +0000 Subject: [PATCH 112/220] Compile DACP-ID and Active-Remote metadata stuff only if metadata is selected. --- rtsp.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rtsp.c b/rtsp.c index 24ff917a..2c4af6f8 100644 --- a/rtsp.c +++ b/rtsp.c @@ -632,14 +632,19 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message * // get the active remote char *p; active_remote = strtoul(ar, &p, 10); +#ifdef CONFIG_METADATA send_metadata('ssnc','acre',ar,strlen(ar),req,1); +#endif } +#ifdef CONFIG_METADATA ar = msg_get_header(req, "DACP-ID"); if (ar) { debug(1,"DACP-ID string seen: \"%s\".",ar); send_metadata('ssnc','daid',ar,strlen(ar),req,1); } +#endif + // select latency // if iTunes V10 or later is detected, use the iTunes latency setting // if AirPlay is detected, use the AirPlay latency setting From 7626cc2f2e65c2aff2e7041604df8dc25b8463ea Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 18 Jun 2015 16:13:02 +0000 Subject: [PATCH 113/220] Use uint32_t in place of uint for our stuff --- common.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common.h b/common.h index 792dd36c..73b82f8e 100644 --- a/common.h +++ b/common.h @@ -78,9 +78,9 @@ typedef struct { char *logfile; char *errfile; char *configfile; - uint audio_backend_buffer_desired_length; // this will be the desired number of frames in the + uint32_t audio_backend_buffer_desired_length; // this will be the desired number of frames in the // audio backend buffer -- the DAC buffer for ALSA - uint audio_backend_latency_offset; // this will be the offset to compensate for any fixed latency + uint32_t audio_backend_latency_offset; // this will be the offset to compensate for any fixed latency // there might be in the audio } shairport_cfg; From 193fd3f0caefdd3ed6960a81bf526209e1620b32 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 18 Jun 2015 16:14:40 +0000 Subject: [PATCH 114/220] Use two different buffer sizes for alsa -- a short one for software mixer, a longer one for hardware mixer, giving more resilience against delays, etc. without affecting responsiveness --- audio_alsa.c | 26 ++++++++++++++++++++++---- scripts/shairport-sync.conf | 3 ++- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 97531b46..7d05fb5e 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -95,23 +95,39 @@ static void help(void) { static int init(int argc, char **argv) { const char *str; int value; + + uint32_t buffer_length_hardware = 22050; int hardware_mixer = 0; - config.audio_backend_buffer_desired_length = 6615; // this is the default for ALSA config.audio_backend_latency_offset = 0; // this is the default for ALSA + config.audio_backend_buffer_desired_length = 6615; // default for alsa with a software mixer + + // get settings from settings file first, allow them to be over-ridden by command line options if (config.cfg != NULL) { /* Get the desired buffer size setting. */ - if (config_lookup_int(config.cfg, "alsa.audio_backend_buffer_desired_length", &value)) { + if (config_lookup_int(config.cfg, "alsa.audio_backend_buffer_desired_length_software", &value)) { if ((value < 0) || (value > 66150)) - die("Invalid alsa audio backend buffer desired length \"%d\". It should be between 0 and " + die("Invalid alsa audio backend buffer desired length (software) \"%d\". It should be between 0 and " "66150, default is 6615", value); - else + else { config.audio_backend_buffer_desired_length = value; + } + } + + /* Get the desired buffer size setting. */ + if (config_lookup_int(config.cfg, "alsa.audio_backend_buffer_desired_length_hardware", &value)) { + if ((value < 0) || (value > 66150)) + die("Invalid alsa audio backend buffer desired length (hardware) \"%d\". It should be between 0 and " + "66150, default is 22050", + value); + else { + buffer_length_hardware = value; + } } /* Get the latency offset. */ @@ -183,6 +199,8 @@ static int init(int argc, char **argv) { if (!hardware_mixer) return 0; + + config.audio_backend_latency_offset = buffer_length_hardware; if (alsa_mix_dev == NULL) alsa_mix_dev = alsa_out_dev; diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 3bfcde5b..d20f91a4 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -59,7 +59,8 @@ alsa = // mixer_device = "default"; //actually, the mixer default is whatever the output_device is. Normally you wouldn't have to use this. // mixer_control_name = "PCM"; // the name of the mixer to use -- there is no default. // audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. -// audio_backend_buffer_desired_length = 6615; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. +// audio_backend_buffer_desired_length_software = 6615; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. +// audio_backend_buffer_desired_length_hardware = 22050; // If set too large, there is less opportunity for late packets to be retrieved. }; // These are parameters for the pipe back end, an experimental back end that directs output to a pipe. From 4654d9201dafe81d1254c4c8118f39ccb94f6a91 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 18 Jun 2015 16:56:04 +0000 Subject: [PATCH 115/220] Fix small bug associating chosen buffer length --- audio_alsa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_alsa.c b/audio_alsa.c index 7d05fb5e..2f3a1acb 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -200,7 +200,7 @@ static int init(int argc, char **argv) { if (!hardware_mixer) return 0; - config.audio_backend_latency_offset = buffer_length_hardware; + config.audio_backend_buffer_desired_length = buffer_length_hardware; if (alsa_mix_dev == NULL) alsa_mix_dev = alsa_out_dev; From 2f0a9c01bf8e4c73d14f4c2eb98154eb52717702 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 18 Jun 2015 18:08:34 +0100 Subject: [PATCH 116/220] Update RELEASENOTES.md --- RELEASENOTES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f379adfb..42eea4ab 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,8 +1,9 @@ Version 2.3.5 -- forthcoming ---- * Enhancements + * Two default buffer lengths for ALSA -- default 6615 frames if a software volume control is used, to minimise the response time to pause and volume control changes; default 22050 frames if a hardware volume control is used, to give more resilience to timing problems, sudden processor loading, etc. This is especially useful if you are processing metadata and artwork on the same machine. * Extra metadata: when a play session starts, the "Active-Remote" and "DACP-ID" fields -- information that can be used to identify the source -- are provided as metadata, with the type `ssnc` and the codes `acre` and `daid` respectively. The IDs are provided as strings. - * Unencrypted audio data. The iOS player "Whaale" attempts to send unencrypted audio, presumably to save processing effort; if unsuccessful, it will send encrypted audio as normal. Shairport Sync now recognises and handles unencrypted audio data. + * Unencrypted audio data. The iOS player "Whaale" attempts to send unencrypted audio, presumably to save processing effort; if unsuccessful, it will send encrypted audio as normal. Shairport Sync now recognises and handles unencrypted audio data. (Apparently it always advertised that it could process unencrypted audio!) * Handle retransmitted audio in the control channel. When a packet of audio is missed, Shairport Sync will ask for it to be retransmitted. Normally the retransmitted audio comes back the audio channel, but "Whaale" sends it back in the control channel. (I think this is a bug in "Whaale".) Shairport Sync will now correctly handle retransmitted audio packets coming back in the control channel. * Bugfixes * Generate properly-formed `..` items of information. From c1198ae243e378fcdb2be849469853225c846fd8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 19 Jun 2015 13:00:14 +0000 Subject: [PATCH 117/220] Remove a few unused declarations --- common.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/common.h b/common.h index 73b82f8e..7880a9d4 100644 --- a/common.h +++ b/common.h @@ -116,9 +116,6 @@ uint64_t get_absolute_time_in_fp(void); uint32_t uatoi(const char *nptr); shairport_cfg config; -char sender_name[1024]; -char sender_ip[1024]; -char album_name[1024]; // we might need this for picture diagnostics config_t config_file_stuff; void command_start(void); From 15be54e270c6d09f6678cbb65b9e83d873e657ae Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 19 Jun 2015 13:01:29 +0000 Subject: [PATCH 118/220] Add code to announce device name and or user agent when a play session is started. --- rtsp.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/rtsp.c b/rtsp.c index 2c4af6f8..940246a8 100644 --- a/rtsp.c +++ b/rtsp.c @@ -819,7 +819,8 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *r // I guess the timestamps wrap at 2^32. // 'mdst' -- a sequence of metadata is about to start // 'mden' -- a sequence of metadata has ended -// 'snam' -- the name of the originator -- e.g. "Joe's iPhone" or "iTunes...". +// 'snam' -- A device -- e.g. "Joe's iPhone" -- has opened a play session. Specifically, it's the "X-Apple-Client-Name" string +// 'snua' -- A "user agent" -- e.g. "iTunes/12..." -- has opened a play session. Specifically, it's the "User-Agent" string // // including a simple base64 encoder to minimise malloc/free activity @@ -1087,9 +1088,6 @@ static void handle_set_parameter_metadata(rtsp_conn_info *conn, rtsp_message *re // inform the listener that a set of metadata is ending send_metadata('ssnc', 'mden', NULL, 0, NULL, 1); - // send the user some shairport-originated metadata - // send the name of the player, e.g. "Joe's iPhone" or "iTunes" - send_metadata('ssnc', 'sndr', strdup(sender_name), strlen(sender_name), NULL, 1); } #endif @@ -1203,15 +1201,17 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag char *hdr = msg_get_header(req, "X-Apple-Client-Name"); if (hdr) { - strncpy(sender_name, hdr, 1024); - debug(1, "Play connection from \"%s\".", hdr); - } else { - hdr = msg_get_header(req, "User-Agent"); - if (hdr) { - debug(1, "Play connection from \"%s\".", hdr); - strncpy(sender_name, hdr, 1024); - } else - sender_name[0] = 0; + debug(1, "Play connection from device named \"%s\".", hdr); + #ifdef CONFIG_METADATA + send_metadata('ssnc','snam',hdr,strlen(hdr),req,1); + #endif + } + hdr = msg_get_header(req, "User-Agent"); + if (hdr) { + debug(1, "Play connection from user agent \"%s\".", hdr); + #ifdef CONFIG_METADATA + send_metadata('ssnc','snua',hdr,strlen(hdr),req,1); + #endif } resp->respcode = 200; } else { From fced2eca655452ce30b72437c8983ac34ffbf29a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 19 Jun 2015 14:12:37 +0100 Subject: [PATCH 119/220] Update RELEASENOTES.md --- RELEASENOTES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 42eea4ab..f842d23a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,6 +1,9 @@ Version 2.3.5 -- forthcoming ---- +* Changes + * The metadata item 'sndr' is no longer sent in metadata. It's been replaced by 'snam' and 'snua' -- see below. * Enhancements + * When a play session is initiated by a source, it attempts to reserve the player by sending an "ANNOUNCE" packet. Typically, a source device name and/or a source "user agent" is sent as part of the packet. The "user agent" is usually the name of the sending application along with some more information. If metadata is enabled, the source name, if provided, is emitted as a metadata item with the type `ssnc` and code `snam` and similarly the user agent, if provided, is sent with the type `ssnc` and code `snua`. * Two default buffer lengths for ALSA -- default 6615 frames if a software volume control is used, to minimise the response time to pause and volume control changes; default 22050 frames if a hardware volume control is used, to give more resilience to timing problems, sudden processor loading, etc. This is especially useful if you are processing metadata and artwork on the same machine. * Extra metadata: when a play session starts, the "Active-Remote" and "DACP-ID" fields -- information that can be used to identify the source -- are provided as metadata, with the type `ssnc` and the codes `acre` and `daid` respectively. The IDs are provided as strings. * Unencrypted audio data. The iOS player "Whaale" attempts to send unencrypted audio, presumably to save processing effort; if unsuccessful, it will send encrypted audio as normal. Shairport Sync now recognises and handles unencrypted audio data. (Apparently it always advertised that it could process unencrypted audio!) From 26383dda8701f810965916727881482f3627615d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 21 Jun 2015 14:27:04 +0100 Subject: [PATCH 120/220] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index d2f4771c..2cbb879f 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.4], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.5], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 47d6aece5e484e53363d8d5ba991eb5ef10cc599 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 21 Jun 2015 14:53:03 +0100 Subject: [PATCH 121/220] Update common.h Make config file name conditional on config file support --- common.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common.h b/common.h index 7880a9d4..b0448e64 100644 --- a/common.h +++ b/common.h @@ -77,7 +77,9 @@ typedef struct { char *pidfile; char *logfile; char *errfile; +#ifdef SUPPORT_CONFIG_FILES char *configfile; +#endif uint32_t audio_backend_buffer_desired_length; // this will be the desired number of frames in the // audio backend buffer -- the DAC buffer for ALSA uint32_t audio_backend_latency_offset; // this will be the offset to compensate for any fixed latency From 9d1c0a5576fc19b5bd857c66ad72dc661a645faf Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 21 Jun 2015 15:37:45 +0100 Subject: [PATCH 122/220] Remove "forthcoming" --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 139b8016..1ffb1c73 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,4 +1,4 @@ -Version 2.3.5 -- forthcoming +Version 2.3.5 ---- * Changes * The metadata item 'sndr' is no longer sent in metadata. It's been replaced by 'snam' and 'snua' -- see below. From 5f919264696f19f6d42c712048729f06707239e9 Mon Sep 17 00:00:00 2001 From: Mke Brady Date: Mon, 22 Jun 2015 20:11:52 +0100 Subject: [PATCH 123/220] Check and extend the --without-pkg-config option --- configure.ac | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/configure.ac b/configure.ac index 2cbb879f..41c402e0 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.5], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.6], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) @@ -46,6 +46,7 @@ if test "x${with_pkg_config}" = x1 ; then AC_DEFINE([HAVE_LIBPOPT],[1],[Define to 1 if you have popt])]) else AC_CHECK_LIB([popt],[poptGetContext], , AC_MSG_ERROR(libpopt needed)) + AC_DEFINE([HAVE_LIBPOPT],[1],[Define to 1 if you have popt]) fi AC_ARG_WITH([dummy],[ --with-dummy = include the dummy audio back end ],[AC_MSG_RESULT(>>Including the dummy audio back end) AC_DEFINE([CONFIG_DUMMY], 1, [Needed by the compiler.]) ], ) @@ -76,9 +77,13 @@ AC_ARG_WITH([configfile_support], if test "x${with_configfile_support}" = x1 ; then AC_MSG_RESULT(>>Including configuration file support) - PKG_CHECK_MODULES( - [LIBCONFIG], [libconfig], - [LIBS="${LIBCONFIG_LIBS} ${LIBS}"]) + if test "x${with_pkg_config}" = x1 ; then + PKG_CHECK_MODULES( + [LIBCONFIG], [libconfig], + [LIBS="${LIBCONFIG_LIBS} ${LIBS}"]) + else + AC_CHECK_LIB([config],[config_init], , AC_MSG_ERROR([libconfig library needed])) + fi AC_DEFINE([SUPPORT_CONFIG_FILES],[1],[Define to 1 if you want to support configuration files]) fi @@ -110,11 +115,18 @@ AC_ARG_WITH(ssl, [ choose --with-ssl=openssl or --with-ssl=polarssl for encrypti AC_MSG_ERROR(choose either "openssl" or "polarssl" encryption) fi if test "x${with_ssl}" = xopenssl ; then - PKG_CHECK_MODULES( - [SSL], [libssl,libcrypto], - [LIBS="${SSL_LIBS} ${LIBS}" - AC_DEFINE([HAVE_LIBCRYPTO],[1],[Define to 1 if you have libcrypto]) - AC_DEFINE([HAVE_LIBSSL],[1],[Define to 1 if you have libssl])]) + if test "x${with_pkg_config}" = x1 ; then + PKG_CHECK_MODULES( + [SSL], [libssl,libcrypto], + [LIBS="${SSL_LIBS} ${LIBS}" + AC_DEFINE([HAVE_LIBCRYPTO],[1],[Define to 1 if you have libcrypto]) + AC_DEFINE([HAVE_LIBSSL],[1],[Define to 1 if you have libssl])]) + else + AC_CHECK_LIB([crypto], [main], , AC_MSG_ERROR(libcrypto selected but the library cannot be found!)) + AC_CHECK_LIB([ssl], [main], , AC_MSG_ERROR(libssl selected but the library cannot be found!)) + AC_DEFINE([HAVE_LIBCRYPTO],[1],[Define to 1 if you have libcrypto]) + AC_DEFINE([HAVE_LIBSSL],[1],[Define to 1 if you have libssl]) + fi elif test "x${with_ssl}" = xpolarssl ; then AC_CHECK_LIB([polarssl],[ssl_init], , AC_MSG_ERROR(PolarSSL selected but the library cannot be found!)) else @@ -159,10 +171,15 @@ AC_ARG_WITH(alsa, [ --with-alsa = choose native ALSA API support (GNU/Linux onl HAS_ALSA=1 AM_CONDITIONAL([USE_ALSA], [test 0]) AC_DEFINE([CONFIG_ALSA], 1, [Needed by the compiler.]) - PKG_CHECK_MODULES( - [ALSA], [alsa], - [LIBS="${ALSA_LIBS} ${LIBS}" - AC_DEFINE([HAVE_LIBASOUND],[1],[Define to 1 if you have ALSA])])]) + if test "x${with_pkg_config}" = x1 ; then + PKG_CHECK_MODULES( + [ALSA], [alsa], + [LIBS="${ALSA_LIBS} ${LIBS}" + AC_DEFINE([HAVE_LIBASOUND],[1],[Define to 1 if you have ALSA])]) + else + AC_CHECK_LIB([asound], [snd_pcm_open], , AC_MSG_ERROR(ALSA support requires the asound library!)) + AC_DEFINE([HAVE_LIBASOUND],[1],[Define to 1 if you have ALSA]) + fi ]) AM_CONDITIONAL([USE_ALSA], [test "x$HAS_ALSA" = "x1"]) # Look for SNDIO flag From e3722d930941c2f2dd66000911e0fea1e9dbf0ae Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 24 Jun 2015 16:48:20 +0000 Subject: [PATCH 124/220] Add a parts-per-billion drift output. Not really verified, but interesting all the same. --- rtp.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rtp.c b/rtp.c index 06df8ac2..57cd104b 100644 --- a/rtp.c +++ b/rtp.c @@ -307,6 +307,8 @@ static void *rtp_timing_receiver(void *arg) { processing_time; local_to_remote_time_jitters = 0; local_to_remote_time_jitters_count = 0; + uint64_t first_local_to_remote_time_difference = 0; + uint64_t first_local_to_remote_time_difference_time; uint64_t l2rtd = 0; while (1) { if (please_shutdown) @@ -398,6 +400,14 @@ static void *rtp_timing_receiver(void *arg) { // with dispersion of %lld us with delta of %lld us",rtus,ji); local_to_remote_time_difference = l2rtd; + if (first_local_to_remote_time_difference==0) { + first_local_to_remote_time_difference = local_to_remote_time_difference; + first_local_to_remote_time_difference_time = get_absolute_time_in_fp(); + } + if (first_local_to_remote_time_difference>=local_to_remote_time_difference) + debug(1,"Clock drift is -%lld ppb.",(((first_local_to_remote_time_difference-local_to_remote_time_difference)*1000000000)/(get_absolute_time_in_fp()-first_local_to_remote_time_difference_time))); + else + debug(1,"Clock drift is %lld ppb.",(((local_to_remote_time_difference-first_local_to_remote_time_difference)*1000000000)/(get_absolute_time_in_fp()-first_local_to_remote_time_difference_time))); } else { debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %d.", packet[1], nread); } From 830c1902316d80c80827da93d4f4c4c1145356bc Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 25 Jun 2015 10:59:26 +0000 Subject: [PATCH 125/220] Modify the output of drift to parts per million, thanks to joerg-krause. --- rtp.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/rtp.c b/rtp.c index 57cd104b..8de54534 100644 --- a/rtp.c +++ b/rtp.c @@ -404,10 +404,19 @@ static void *rtp_timing_receiver(void *arg) { first_local_to_remote_time_difference = local_to_remote_time_difference; first_local_to_remote_time_difference_time = get_absolute_time_in_fp(); } - if (first_local_to_remote_time_difference>=local_to_remote_time_difference) - debug(1,"Clock drift is -%lld ppb.",(((first_local_to_remote_time_difference-local_to_remote_time_difference)*1000000000)/(get_absolute_time_in_fp()-first_local_to_remote_time_difference_time))); - else - debug(1,"Clock drift is %lld ppb.",(((local_to_remote_time_difference-first_local_to_remote_time_difference)*1000000000)/(get_absolute_time_in_fp()-first_local_to_remote_time_difference_time))); + + uint64_t clock_drift; + if (first_local_to_remote_time_difference>=local_to_remote_time_difference) { + clock_drift = ((first_local_to_remote_time_difference - local_to_remote_time_difference) * 1000000)>>32; + debug(1, "-%llu", clock_drift); + //debug(1,"Clock drift is -%lld usec.",clock_drift); + } + else { + clock_drift = ((local_to_remote_time_difference - first_local_to_remote_time_difference) * 1000000) >>32; + //debug(1,"Clock drift is %lld usec.",clock_drift); + debug(1, "%llu", clock_drift); + } + } else { debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %d.", packet[1], nread); } From 300b4abe6f479a7fd1ac3ebe66eddcb2ea22904c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 25 Jun 2015 14:23:21 +0000 Subject: [PATCH 126/220] Add ability to output clock drift and corrections applied, both in microseconds. --- common.h | 2 ++ player.c | 8 ++++++-- rtp.c | 10 ++++++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/common.h b/common.h index b0448e64..6b38442d 100644 --- a/common.h +++ b/common.h @@ -120,6 +120,8 @@ uint32_t uatoi(const char *nptr); shairport_cfg config; config_t config_file_stuff; +int64_t session_corrections; + void command_start(void); void command_stop(void); diff --git a/player.c b/player.c index 3ae9bac7..47f9b27c 100644 --- a/player.c +++ b/player.c @@ -855,6 +855,7 @@ typedef struct stats { // statistics for running averages } stats_t; static void *player_thread_func(void *arg) { + session_corrections = 0; // check that there are enough buffers to accommodate the desired latency and the latency offset int maximum_latency = config.latency+config.audio_backend_latency_offset; @@ -1115,11 +1116,14 @@ static void *player_thread_func(void *arg) { tsum_of_sync_errors += sync_error; tsum_of_drifts += statistics[newest_statistic].drift; - if (amount_to_stuff > 0) + if (amount_to_stuff > 0) { tsum_of_insertions_and_deletions += amount_to_stuff; - else + } else { tsum_of_insertions_and_deletions -= amount_to_stuff; + } tsum_of_corrections += amount_to_stuff; + session_corrections += amount_to_stuff; + newest_statistic = (newest_statistic + 1) % trend_interval; number_of_statistics++; diff --git a/rtp.c b/rtp.c index 8de54534..d6a0eab7 100644 --- a/rtp.c +++ b/rtp.c @@ -406,15 +406,21 @@ static void *rtp_timing_receiver(void *arg) { } uint64_t clock_drift; + int64_t current_delay; +// if (config.output->delay) { +// current_delay = config.output->delay(); +// } if (first_local_to_remote_time_difference>=local_to_remote_time_difference) { clock_drift = ((first_local_to_remote_time_difference - local_to_remote_time_difference) * 1000000)>>32; - debug(1, "-%llu", clock_drift); +// debug(1, "-%llu\t%lld\t%lld", clock_drift,(session_corrections*1000000)/44100,current_delay); + debug(1, "-%llu\t%lld", clock_drift,(session_corrections*1000000)/44100); //debug(1,"Clock drift is -%lld usec.",clock_drift); } else { clock_drift = ((local_to_remote_time_difference - first_local_to_remote_time_difference) * 1000000) >>32; //debug(1,"Clock drift is %lld usec.",clock_drift); - debug(1, "%llu", clock_drift); +// debug(1, "%llu\t%lld\t%lld", clock_drift,(session_corrections*1000000)/44100,current_delay); + debug(1, "%llu\t%lld\t", clock_drift,(session_corrections*1000000)/44100); } } else { From 841014277e0ea44919f21913b823df2dda1eb576 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 26 Jun 2015 15:11:21 +0100 Subject: [PATCH 127/220] Add check of drift in the source material -- i,e, divergence between calculated and actual remote clocks for frames. --- common.h | 2 ++ player.c | 23 +++++++++++++---------- rtp.c | 42 +++++++++++++++++++++++++++++++----------- rtp.h | 2 +- 4 files changed, 47 insertions(+), 22 deletions(-) diff --git a/common.h b/common.h index 6b38442d..22de06d2 100644 --- a/common.h +++ b/common.h @@ -121,6 +121,8 @@ shairport_cfg config; config_t config_file_stuff; int64_t session_corrections; +uint32_t play_segment_reference_frame; +uint64_t play_segment_reference_frame_remote_time; void command_start(void); void command_stop(void); diff --git a/player.c b/player.c index 47f9b27c..f423ff04 100644 --- a/player.c +++ b/player.c @@ -492,8 +492,8 @@ static abuf_t *buffer_get_frame(void) { // debug(1,"First frame seen, time %u, with %d // frames...",curframe->timestamp,seq_diff(ab_read, ab_write)); uint32_t reference_timestamp; - uint64_t reference_timestamp_time; - get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time); + uint64_t reference_timestamp_time,remote_reference_timestamp_time; + get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time); if (reference_timestamp) { // if we have a reference time // debug(1,"First frame seen with timestamp..."); first_packet_timestamp = curframe->timestamp; // we will keep buffering until we are @@ -601,12 +601,13 @@ static abuf_t *buffer_get_frame(void) { // with %d packets.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read, ab_write)); config.output->play(silence, fs); free(silence); -#ifdef CONFIG_METADATA if (ab_buffering == 0) { - send_ssnc_metadata('prsm', NULL, 0, - 0); // "resume", but don't wait if the queue is locked - } + uint64_t reference_timestamp_time; // don't need this... + get_reference_timestamp_stuff(&play_segment_reference_frame, &reference_timestamp_time, &play_segment_reference_frame_remote_time); +#ifdef CONFIG_METADATA + send_ssnc_metadata('prsm', NULL, 0, 0); // "resume", but don't wait if the queue is locked #endif + } } } } @@ -629,8 +630,8 @@ static abuf_t *buffer_get_frame(void) { int do_wait = 1; if ((ab_synced) && (curframe) && (curframe->ready) && (curframe->timestamp)) { uint32_t reference_timestamp; - uint64_t reference_timestamp_time; - get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time); + uint64_t reference_timestamp_time,remote_reference_timestamp_time; + get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time); if (reference_timestamp) { // if we have a reference time uint32_t packet_timestamp = curframe->timestamp; int64_t delta = ((int64_t)packet_timestamp - (int64_t)reference_timestamp); @@ -856,6 +857,7 @@ typedef struct stats { // statistics for running averages static void *player_thread_func(void *arg) { session_corrections = 0; + play_segment_reference_frame = 0; // zero signals that we are not in a play segment // check that there are enough buffers to accommodate the desired latency and the latency offset int maximum_latency = config.latency+config.audio_backend_latency_offset; @@ -924,8 +926,8 @@ static void *player_thread_func(void *arg) { at_least_one_frame_seen = 1; uint32_t reference_timestamp; - uint64_t reference_timestamp_time; - get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time); + uint64_t reference_timestamp_time,remote_reference_timestamp_time; + get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time); int64_t rt, nt; rt = reference_timestamp; @@ -1232,6 +1234,7 @@ void player_flush(uint32_t timestamp) { // if (timestamp!=0) flush_rtp_timestamp = timestamp; // flush all packets up to (and including?) this pthread_mutex_unlock(&flush_mutex); + play_segment_reference_frame = 0; #ifdef CONFIG_METADATA send_ssnc_metadata('pfls', NULL, 0, 1); #endif diff --git a/rtp.c b/rtp.c index d6a0eab7..b4026a47 100644 --- a/rtp.c +++ b/rtp.c @@ -68,6 +68,7 @@ static pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread; static uint32_t reference_timestamp; static uint64_t reference_timestamp_time; +static uint64_t remote_reference_timestamp_time; // debug variables static int request_sent; @@ -199,12 +200,13 @@ static void *rtp_control_receiver(void *arg) { // after the one whose RTP is given. Weird. } pthread_mutex_lock(&reference_time_mutex); + remote_reference_timestamp_time = remote_time_of_sync; reference_timestamp_time = remote_time_of_sync - local_to_remote_time_difference; reference_timestamp = sync_rtp_timestamp; pthread_mutex_unlock(&reference_time_mutex); // debug(1,"New Reference timestamp and timestamp time..."); // get estimated remote time now - remote_time_now = local_time_now + local_to_remote_time_difference; + // remote_time_now = local_time_now + local_to_remote_time_difference; // debug(1,"Sync Time is %lld us late (remote // times).",((remote_time_now-remote_time_of_sync)*1000000)>>32); @@ -404,23 +406,40 @@ static void *rtp_timing_receiver(void *arg) { first_local_to_remote_time_difference = local_to_remote_time_difference; first_local_to_remote_time_difference_time = get_absolute_time_in_fp(); } + + int64_t source_drift_usec; + if (play_segment_reference_frame!=0) { + uint32_t reference_timestamp; + uint64_t reference_timestamp_time,remote_reference_timestamp_time; + get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time); + uint64_t frame_difference = 0; + if (reference_timestamp>=play_segment_reference_frame) + frame_difference = (uint64_t)reference_timestamp-(uint64_t)play_segment_reference_frame; + else // rollover + frame_difference = (uint64_t)reference_timestamp+0x100000000-(uint64_t)play_segment_reference_frame; + uint64_t frame_time_difference_calculated = (((uint64_t)frame_difference<<32)/44100); + uint64_t frame_time_difference_specified = remote_reference_timestamp_time-play_segment_reference_frame_remote_time; + // debug(1,"%llu frames since play started, %llu usec calculated, %llu usec actual",frame_difference, (frame_time_difference_calculated*1000000)>>32, (frame_time_difference_specified*1000000)>>32); + if (frame_time_difference_specified>=frame_time_difference_calculated) + source_drift_usec = frame_time_difference_specified-frame_time_difference_calculated; + else + source_drift_usec = -(frame_time_difference_calculated-frame_time_difference_specified); + } else + source_drift_usec = 0; + source_drift_usec = (source_drift_usec*1000000)>>32; // turn it to microseconds uint64_t clock_drift; int64_t current_delay; -// if (config.output->delay) { -// current_delay = config.output->delay(); -// } + if (config.output->delay) { + current_delay = config.output->delay(); + } if (first_local_to_remote_time_difference>=local_to_remote_time_difference) { clock_drift = ((first_local_to_remote_time_difference - local_to_remote_time_difference) * 1000000)>>32; -// debug(1, "-%llu\t%lld\t%lld", clock_drift,(session_corrections*1000000)/44100,current_delay); - debug(1, "-%llu\t%lld", clock_drift,(session_corrections*1000000)/44100); - //debug(1,"Clock drift is -%lld usec.",clock_drift); + debug(1, "-%llu\t%lld\t%lld\t%lld", clock_drift,(session_corrections*1000000)/44100,current_delay,source_drift_usec); } else { clock_drift = ((local_to_remote_time_difference - first_local_to_remote_time_difference) * 1000000) >>32; - //debug(1,"Clock drift is %lld usec.",clock_drift); -// debug(1, "%llu\t%lld\t%lld", clock_drift,(session_corrections*1000000)/44100,current_delay); - debug(1, "%llu\t%lld\t", clock_drift,(session_corrections*1000000)/44100); + debug(1, "%llu\t%lld\t%lld\t%lld", clock_drift,(session_corrections*1000000)/44100,current_delay,source_drift_usec); } } else { @@ -581,10 +600,11 @@ void rtp_setup(SOCKADDR *remote, int cport, int tport, uint32_t active_remote, i request_sent = 0; } -void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time) { +void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time, uint64_t *remote_timestamp_time) { pthread_mutex_lock(&reference_time_mutex); *timestamp = reference_timestamp; *timestamp_time = reference_timestamp_time; + *remote_timestamp_time = remote_reference_timestamp_time; pthread_mutex_unlock(&reference_time_mutex); } diff --git a/rtp.h b/rtp.h index 5fd67983..8045d2bb 100644 --- a/rtp.h +++ b/rtp.h @@ -11,7 +11,7 @@ void rtp_shutdown(void); void rtp_request_resend(seq_t first, uint32_t count); void rtp_request_client_pause(void); // ask the client to pause -void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time); +void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time, uint64_t *remote_timestamp_time); void clear_reference_timestamp(void); uint64_t static local_to_remote_time_jitters; From 1ba10db4f227783fdecc4cfb7dd5c0e1dfdb153e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 28 Jun 2015 15:29:47 +0100 Subject: [PATCH 128/220] Add declarion of debug, etc. by Mike Brady --- audio_dummy.c | 1 + 1 file changed, 1 insertion(+) diff --git a/audio_dummy.c b/audio_dummy.c index 57fbec17..9742c58c 100644 --- a/audio_dummy.c +++ b/audio_dummy.c @@ -27,6 +27,7 @@ #include #include #include +#include "common.h" #include "audio.h" int Fs; From 0cc29d63cc1de472019345ed36a86f0712acf07a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 28 Jun 2015 15:33:48 +0100 Subject: [PATCH 129/220] Add declartion of mkfifo --- audio_pipe.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/audio_pipe.c b/audio_pipe.c index fe109d65..210f99f9 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include "common.h" #include "audio.h" From 129a4dc568147837bc926638484c9a187807f683 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 28 Jun 2015 15:37:22 +0100 Subject: [PATCH 130/220] Add declaration file of waitpid --- common.c | 1 + 1 file changed, 1 insertion(+) diff --git a/common.c b/common.c index fb56c508..d3bdeacb 100644 --- a/common.c +++ b/common.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include "common.h" From 2ac88cf634120467e86c804cf00de9ca4143e444 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 28 Jun 2015 16:37:45 +0100 Subject: [PATCH 131/220] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 2cbb879f..d1e60cf4 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.5], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.6.1], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 4986f06522059492781c5c749d78d2281745f1c1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 28 Jun 2015 18:50:42 +0100 Subject: [PATCH 132/220] Remove a few implicit definition warnings --- rtsp.c | 2 ++ rtsp.h | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/rtsp.c b/rtsp.c index 940246a8..262d9b92 100644 --- a/rtsp.c +++ b/rtsp.c @@ -40,6 +40,8 @@ #include #include #include +#include + #include "config.h" diff --git a/rtsp.h b/rtsp.h index 672eefee..49650055 100644 --- a/rtsp.h +++ b/rtsp.h @@ -5,6 +5,11 @@ void rtsp_listen_loop(void); void rtsp_shutdown_stream(void); void rtsp_request_shutdown_stream(void); + +// initialise the metadata stuff + +void metadata_init(void); + // sends metadata out to the metadata pipe, if enabled. // It is sent with the type 'ssnc' the given code, data and length // The handler at the other end must kknow what to do with the data From fb5c4f3012d62e12cb026aafff1f8f024702270f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 28 Jun 2015 19:44:24 +0100 Subject: [PATCH 133/220] Better calculation of f drift between the two clocks --- rtp.c | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/rtp.c b/rtp.c index b4026a47..668d43e1 100644 --- a/rtp.c +++ b/rtp.c @@ -309,6 +309,9 @@ static void *rtp_timing_receiver(void *arg) { processing_time; local_to_remote_time_jitters = 0; local_to_remote_time_jitters_count = 0; + uint64_t first_remote_time = 0; + uint64_t first_local_time = 0; + uint64_t first_local_to_remote_time_difference = 0; uint64_t first_local_to_remote_time_difference_time; uint64_t l2rtd = 0; @@ -406,6 +409,27 @@ static void *rtp_timing_receiver(void *arg) { first_local_to_remote_time_difference = local_to_remote_time_difference; first_local_to_remote_time_difference_time = get_absolute_time_in_fp(); } + + int64_t clock_drift, clock_drift_in_usec; + if (first_local_time==0) { + first_local_time = arrival_time; + first_remote_time = local_time_by_remote_clock; + uint64_t clock_drift = 0; + } else { + uint64_t local_time_change = arrival_time - first_local_time; + uint64_t remote_time_change = local_time_by_remote_clock - first_remote_time; + + if (local_time_change >= remote_time_change) + clock_drift = local_time_change - remote_time_change; + else + clock_drift = -(remote_time_change - local_time_change); + } + if (clock_drift>=0) + clock_drift_in_usec = (clock_drift * 1000000)>>32; + else + clock_drift_in_usec = -(((-clock_drift) * 1000000)>>32); + + int64_t source_drift_usec; if (play_segment_reference_frame!=0) { @@ -428,19 +452,11 @@ static void *rtp_timing_receiver(void *arg) { source_drift_usec = 0; source_drift_usec = (source_drift_usec*1000000)>>32; // turn it to microseconds - uint64_t clock_drift; - int64_t current_delay; + int64_t current_delay = 0; if (config.output->delay) { current_delay = config.output->delay(); } - if (first_local_to_remote_time_difference>=local_to_remote_time_difference) { - clock_drift = ((first_local_to_remote_time_difference - local_to_remote_time_difference) * 1000000)>>32; - debug(1, "-%llu\t%lld\t%lld\t%lld", clock_drift,(session_corrections*1000000)/44100,current_delay,source_drift_usec); - } - else { - clock_drift = ((local_to_remote_time_difference - first_local_to_remote_time_difference) * 1000000) >>32; - debug(1, "%llu\t%lld\t%lld\t%lld", clock_drift,(session_corrections*1000000)/44100,current_delay,source_drift_usec); - } + debug(1, "%lld\t%lld\t%lld\t%lld", clock_drift_in_usec,(session_corrections*1000000)/44100,current_delay,source_drift_usec); } else { debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %d.", packet[1], nread); From a61e6d319e4a9e2e3539e0fe5f0f7e07115fee75 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 28 Jun 2015 19:54:42 +0100 Subject: [PATCH 134/220] Use smoothed time differences --- rtp.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/rtp.c b/rtp.c index 668d43e1..3578f223 100644 --- a/rtp.c +++ b/rtp.c @@ -49,6 +49,8 @@ typedef struct { typedef struct time_ping_record { uint64_t local_to_remote_difference; uint64_t dispersion; + uint64_t local_time; + uint64_t remote_time; } time_ping_record; // only one RTP session can be active at a time. @@ -373,11 +375,17 @@ static void *rtp_timing_receiver(void *arg) { time_pings[cc].dispersion = (time_pings[cc].dispersion * 133) / 100; // make the dispersions 'age' by this rational factor } + // these are for diagnostics only -- not used + time_pings[0].local_time = arrival_time; + time_pings[0].remote_time = distant_transmit_time; + time_pings[0].local_to_remote_difference = local_time_by_remote_clock - arrival_time; time_pings[0].dispersion = return_time; if (time_ping_count < time_ping_history) time_ping_count++; - + + uint64_t local_time_chosen = arrival_time;; + uint64_t remote_time_chosen = distant_transmit_time; // now pick the timestamp with the lowest dispersion uint64_t l2rtd = time_pings[0].local_to_remote_difference; uint64_t tld = time_pings[0].dispersion; @@ -385,6 +393,8 @@ static void *rtp_timing_receiver(void *arg) { if (time_pings[cc].dispersion < tld) { l2rtd = time_pings[cc].local_to_remote_difference; tld = time_pings[cc].dispersion; + local_time_chosen = time_pings[cc].local_time; + remote_time_chosen = time_pings[cc].remote_time; } int64_t ji; @@ -412,12 +422,12 @@ static void *rtp_timing_receiver(void *arg) { int64_t clock_drift, clock_drift_in_usec; if (first_local_time==0) { - first_local_time = arrival_time; - first_remote_time = local_time_by_remote_clock; + first_local_time = local_time_chosen; + first_remote_time = remote_time_chosen; uint64_t clock_drift = 0; } else { - uint64_t local_time_change = arrival_time - first_local_time; - uint64_t remote_time_change = local_time_by_remote_clock - first_remote_time; + uint64_t local_time_change = local_time_chosen - first_local_time; + uint64_t remote_time_change = remote_time_chosen - first_remote_time; if (local_time_change >= remote_time_change) clock_drift = local_time_change - remote_time_change; From 2f379aba6129bf68f985d1c4045d92dbed014210 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 28 Jun 2015 19:55:56 +0100 Subject: [PATCH 135/220] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index d1e60cf4..ecaab727 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.6.1], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.6.2], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 8892561917ad84fd25901a641a2b3d444e9ee95a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 29 Jun 2015 09:08:58 +0000 Subject: [PATCH 136/220] Use CLOCK_MONOTONIC_RAW for better timing estimation --- common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.c b/common.c index d3bdeacb..d4c534f3 100644 --- a/common.c +++ b/common.c @@ -474,7 +474,7 @@ uint64_t get_absolute_time_in_fp() { uint64_t time_now_fp; #ifdef COMPILE_FOR_LINUX struct timespec tn; - clock_gettime(CLOCK_MONOTONIC, &tn); + clock_gettime(CLOCK_MONOTONIC_RAW, &tn); time_now_fp = ((uint64_t)tn.tv_sec << 32) + ((uint64_t)tn.tv_nsec << 32) / 1000000000; #endif #ifdef COMPILE_FOR_OSX From 513cc31c764e9888b45e004cd10576f33813981b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 29 Jun 2015 09:11:59 +0000 Subject: [PATCH 137/220] Invert sense of clock drift figure. A positive number now means the source clock (i.e. the remote clock) is running faster than the local clock. --- rtp.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rtp.c b/rtp.c index 3578f223..3de42b8e 100644 --- a/rtp.c +++ b/rtp.c @@ -429,10 +429,11 @@ static void *rtp_timing_receiver(void *arg) { uint64_t local_time_change = local_time_chosen - first_local_time; uint64_t remote_time_change = remote_time_chosen - first_remote_time; - if (local_time_change >= remote_time_change) - clock_drift = local_time_change - remote_time_change; + + if (remote_time_change >= local_time_change) + clock_drift = remote_time_change - local_time_change; else - clock_drift = -(remote_time_change - local_time_change); + clock_drift = -(local_time_change - remote_time_change); } if (clock_drift>=0) clock_drift_in_usec = (clock_drift * 1000000)>>32; From 72df950d72bca1337393910ef986542f89f38ba8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 29 Jun 2015 09:39:46 +0000 Subject: [PATCH 138/220] Bump buglet --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index ecaab727..4cf053d1 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.6.2], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.6.3], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From fcec6373407724c687263c27c1e15a96679b4bd4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 29 Jun 2015 18:25:39 +0000 Subject: [PATCH 139/220] Add buffer size to diagnostics --- common.h | 1 + configure.ac | 2 +- player.c | 11 ++++++----- rtp.c | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/common.h b/common.h index 22de06d2..39855c59 100644 --- a/common.h +++ b/common.h @@ -120,6 +120,7 @@ uint32_t uatoi(const char *nptr); shairport_cfg config; config_t config_file_stuff; +uint32_t buffer_occupancy; int64_t session_corrections; uint32_t play_segment_reference_frame; uint64_t play_segment_reference_frame_remote_time; diff --git a/configure.ac b/configure.ac index 4cf053d1..c5f18b02 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.6.3], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.6.4], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) diff --git a/player.c b/player.c index f423ff04..522b6fa4 100644 --- a/player.c +++ b/player.c @@ -877,6 +877,7 @@ static void *player_thread_func(void *arg) { int32_t maximum_buffer_occupancy = 0; audio_information.valid = 0; + buffer_occupancy = 0; int play_samples; int64_t current_delay; @@ -967,13 +968,13 @@ static void *player_thread_func(void *arg) { } } - uint32_t bo = seq_diff(ab_read, ab_write); + buffer_occupancy = seq_diff(ab_read, ab_write); - if (bo < minimum_buffer_occupancy) - minimum_buffer_occupancy = bo; + if (buffer_occupancy < minimum_buffer_occupancy) + minimum_buffer_occupancy = buffer_occupancy; - if (bo > maximum_buffer_occupancy) - maximum_buffer_occupancy = bo; + if (buffer_occupancy > maximum_buffer_occupancy) + maximum_buffer_occupancy = buffer_occupancy; if (config.output->delay) { current_delay = config.output->delay(); diff --git a/rtp.c b/rtp.c index 3de42b8e..e4752059 100644 --- a/rtp.c +++ b/rtp.c @@ -467,7 +467,7 @@ static void *rtp_timing_receiver(void *arg) { if (config.output->delay) { current_delay = config.output->delay(); } - debug(1, "%lld\t%lld\t%lld\t%lld", clock_drift_in_usec,(session_corrections*1000000)/44100,current_delay,source_drift_usec); + debug(1, "%lld\t%lld\t%lld\t%lld\t%u", clock_drift_in_usec,(session_corrections*1000000)/44100,current_delay,source_drift_usec,buffer_occupancy); } else { debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %d.", packet[1], nread); From 8c68c722eac7c8de3ee38f37238207b57cdd854f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 1 Jul 2015 10:21:30 +0000 Subject: [PATCH 140/220] Reverty the hardware buffer size to 6615 -- might get rid of it. --- audio_alsa.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 2f3a1acb..05f27a10 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -96,7 +96,7 @@ static int init(int argc, char **argv) { const char *str; int value; - uint32_t buffer_length_hardware = 22050; + uint32_t buffer_length_hardware = 6615; // default for alsa with a hardware mixer int hardware_mixer = 0; @@ -123,7 +123,7 @@ static int init(int argc, char **argv) { if (config_lookup_int(config.cfg, "alsa.audio_backend_buffer_desired_length_hardware", &value)) { if ((value < 0) || (value > 66150)) die("Invalid alsa audio backend buffer desired length (hardware) \"%d\". It should be between 0 and " - "66150, default is 22050", + "66150, default is 6615", value); else { buffer_length_hardware = value; From 221c6e677794afb981ea34e873a532ffaa9ee21e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 1 Jul 2015 10:21:59 +0000 Subject: [PATCH 141/220] Revert the hardware buffer size to 6615 -- might get rid of it. --- scripts/shairport-sync.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index d20f91a4..19697e07 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -60,7 +60,7 @@ alsa = // mixer_control_name = "PCM"; // the name of the mixer to use -- there is no default. // audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. // audio_backend_buffer_desired_length_software = 6615; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. -// audio_backend_buffer_desired_length_hardware = 22050; // If set too large, there is less opportunity for late packets to be retrieved. +// audio_backend_buffer_desired_length_hardware = 6615; // If set too large, there is less opportunity for late packets to be retrieved, and it may be bigger than the actual buffer. }; // These are parameters for the pipe back end, an experimental back end that directs output to a pipe. From 0c5584cbefdf5c16b44d286061ad1ea96a4047ba Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 1 Jul 2015 10:22:26 +0000 Subject: [PATCH 142/220] Correct the calculation of source DAV drift --- rtp.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/rtp.c b/rtp.c index e4752059..bb72393d 100644 --- a/rtp.c +++ b/rtp.c @@ -453,12 +453,14 @@ static void *rtp_timing_receiver(void *arg) { else // rollover frame_difference = (uint64_t)reference_timestamp+0x100000000-(uint64_t)play_segment_reference_frame; uint64_t frame_time_difference_calculated = (((uint64_t)frame_difference<<32)/44100); - uint64_t frame_time_difference_specified = remote_reference_timestamp_time-play_segment_reference_frame_remote_time; - // debug(1,"%llu frames since play started, %llu usec calculated, %llu usec actual",frame_difference, (frame_time_difference_calculated*1000000)>>32, (frame_time_difference_specified*1000000)>>32); - if (frame_time_difference_specified>=frame_time_difference_calculated) - source_drift_usec = frame_time_difference_specified-frame_time_difference_calculated; + uint64_t frame_time_difference_actual = remote_reference_timestamp_time-play_segment_reference_frame_remote_time; // this is all done by reference to the sources' system clock + // debug(1,"%llu frames since play started, %llu usec calculated, %llu usec actual",frame_difference, (frame_time_difference_calculated*1000000)>>32, (frame_time_difference_actual*1000000)>>32); + if (frame_time_difference_calculated>=frame_time_difference_actual) // i.e. if the time it should have taken to send the packets is greater than the actual time difference measured on the source clock + // then the source DAC's clock is running fast relative to the source system clock + source_drift_usec = frame_time_difference_calculated-frame_time_difference_actual; else - source_drift_usec = -(frame_time_difference_calculated-frame_time_difference_specified); + // otherwise the source DAC's clock is running slow relative to the source system clock + source_drift_usec = -(frame_time_difference_actual-frame_time_difference_calculated); } else source_drift_usec = 0; source_drift_usec = (source_drift_usec*1000000)>>32; // turn it to microseconds From ab9986408076669b9486b351ca1a4df0a086381e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 1 Jul 2015 10:23:17 +0000 Subject: [PATCH 143/220] Bump revision --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index c5f18b02..be0f2a8a 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.6.4], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.6.5], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From cc4d9ece420d18cf86bec44a3773f911ddfca93f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 2 Jul 2015 15:17:36 +0100 Subject: [PATCH 144/220] Revert back to CLOCK_MONOTONIC from CLOCK_MONOTONIC_RAW because OpenWrt doesn't recognise it --- common.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common.c b/common.c index d4c534f3..83837437 100644 --- a/common.c +++ b/common.c @@ -474,7 +474,8 @@ uint64_t get_absolute_time_in_fp() { uint64_t time_now_fp; #ifdef COMPILE_FOR_LINUX struct timespec tn; - clock_gettime(CLOCK_MONOTONIC_RAW, &tn); + // can't use CLOCK_MONOTONIC_RAW as it's not implemented in OpenWrt + clock_gettime(CLOCK_MONOTONIC, &tn); time_now_fp = ((uint64_t)tn.tv_sec << 32) + ((uint64_t)tn.tv_nsec << 32) / 1000000000; #endif #ifdef COMPILE_FOR_OSX From 04aa3261bb58cc78c3d3b297c251a3b3b9e02f5a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 2 Jul 2015 15:18:31 +0100 Subject: [PATCH 145/220] Add a few checks suggested by autoscan, bump revision --- configure.ac | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/configure.ac b/configure.ac index be0f2a8a..5513e977 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.6.5], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.6.6], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) @@ -201,9 +201,10 @@ AM_CONDITIONAL([USE_DNS_SD], [test "x$HAS_DNS_SD" = "x1"]) # Checks for header files. AC_HEADER_STDC AC_CHECK_HEADERS([getopt_long.h]) -AC_CHECK_HEADERS([arpa/inet.h fcntl.h mach/mach.h memory.h netdb.h netinet/in.h stdint.h stdlib.h string.h sys/ioctl.h sys/socket.h sys/time.h syslog.h unistd.h]) +AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h mach/mach.h memory.h netdb.h netinet/in.h stdint.h stdlib.h string.h sys/ioctl.h sys/socket.h sys/time.h syslog.h unistd.h]) # Checks for typedefs, structures, and compiler characteristics. +AC_CHECK_HEADER_STDBOOL AC_C_INLINE AC_TYPE_INT16_T AC_TYPE_INT32_T @@ -217,11 +218,12 @@ AC_TYPE_UINT64_T AC_TYPE_UINT8_T # Checks for library functions. +AC_FUNC_ALLOCA AC_FUNC_ERROR_AT_LINE AC_FUNC_FORK AC_FUNC_MALLOC AC_FUNC_REALLOC -AC_CHECK_FUNCS([clock_gettime gethostname gettimeofday inet_ntoa memchr memmove memset pow select socket stpcpy strcasecmp strchr strdup strerror strstr strtol strtoul]) +AC_CHECK_FUNCS([atexit clock_gettime gethostname inet_ntoa memchr memmove memset mkfifo pow select socket stpcpy strcasecmp strchr strdup strerror strstr strtol strtoul]) AC_CONFIG_FILES([Makefile man/Makefile]) AC_OUTPUT From 1780e246237399c3f4d6e3e6482b085b36af2d04 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 2 Jul 2015 16:54:45 +0100 Subject: [PATCH 146/220] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index fb040c1b..ecb6d09e 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.6.6], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.6.7], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 5c3dcc8ba9642a18b9b22fb3bfc25e35809d1e09 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 2 Jul 2015 17:02:37 +0100 Subject: [PATCH 147/220] Update configure.ac MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove AC_CHECK_HEADER_STDBOOL check – OpenWrt doesn't like it --- configure.ac | 1 - 1 file changed, 1 deletion(-) diff --git a/configure.ac b/configure.ac index ecb6d09e..7b19363d 100644 --- a/configure.ac +++ b/configure.ac @@ -221,7 +221,6 @@ AC_CHECK_HEADERS([getopt_long.h]) AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h mach/mach.h memory.h netdb.h netinet/in.h stdint.h stdlib.h string.h sys/ioctl.h sys/socket.h sys/time.h syslog.h unistd.h]) # Checks for typedefs, structures, and compiler characteristics. -AC_CHECK_HEADER_STDBOOL AC_C_INLINE AC_TYPE_INT16_T AC_TYPE_INT32_T From 52d813735d45a18e45d067304d0e8052ee2be03d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 2 Jul 2015 17:02:53 +0100 Subject: [PATCH 148/220] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 7b19363d..725136eb 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.6.7], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.6.8], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 8a7647bdc3c58e75343aa30519b5a547d517c2ad Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 2 Jul 2015 19:37:27 +0100 Subject: [PATCH 149/220] Add timing request turnaround time to diagnostics --- configure.ac | 2 +- rtp.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 725136eb..bbf345bc 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.6.8], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.6.9], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) diff --git a/rtp.c b/rtp.c index bb72393d..2b03ef0e 100644 --- a/rtp.c +++ b/rtp.c @@ -469,7 +469,7 @@ static void *rtp_timing_receiver(void *arg) { if (config.output->delay) { current_delay = config.output->delay(); } - debug(1, "%lld\t%lld\t%lld\t%lld\t%u", clock_drift_in_usec,(session_corrections*1000000)/44100,current_delay,source_drift_usec,buffer_occupancy); + debug(1, "%lld\t%lld\t%lld\t%lld\t%u\t%llu", clock_drift_in_usec,(session_corrections*1000000)/44100,current_delay,source_drift_usec,buffer_occupancy,(return_time*1000000)>>32); } else { debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %d.", packet[1], nread); From 3d10ddb5b55a6a2604071c54c09d647c8a73b375 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 10 Jul 2015 14:56:30 +0000 Subject: [PATCH 150/220] Remove separate latencies for software and hardware mixers in alsa back end. --- audio_alsa.c | 15 --------------- scripts/shairport-sync.conf | 3 +-- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 05f27a10..b3335d7e 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -95,8 +95,6 @@ static void help(void) { static int init(int argc, char **argv) { const char *str; int value; - - uint32_t buffer_length_hardware = 6615; // default for alsa with a hardware mixer int hardware_mixer = 0; @@ -119,17 +117,6 @@ static int init(int argc, char **argv) { } } - /* Get the desired buffer size setting. */ - if (config_lookup_int(config.cfg, "alsa.audio_backend_buffer_desired_length_hardware", &value)) { - if ((value < 0) || (value > 66150)) - die("Invalid alsa audio backend buffer desired length (hardware) \"%d\". It should be between 0 and " - "66150, default is 6615", - value); - else { - buffer_length_hardware = value; - } - } - /* Get the latency offset. */ if (config_lookup_int(config.cfg, "alsa.audio_backend_latency_offset", &value)) { if ((value < -66150) || (value > 66150)) @@ -199,8 +186,6 @@ static int init(int argc, char **argv) { if (!hardware_mixer) return 0; - - config.audio_backend_buffer_desired_length = buffer_length_hardware; if (alsa_mix_dev == NULL) alsa_mix_dev = alsa_out_dev; diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 19697e07..3bfcde5b 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -59,8 +59,7 @@ alsa = // mixer_device = "default"; //actually, the mixer default is whatever the output_device is. Normally you wouldn't have to use this. // mixer_control_name = "PCM"; // the name of the mixer to use -- there is no default. // audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. -// audio_backend_buffer_desired_length_software = 6615; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. -// audio_backend_buffer_desired_length_hardware = 6615; // If set too large, there is less opportunity for late packets to be retrieved, and it may be bigger than the actual buffer. +// audio_backend_buffer_desired_length = 6615; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. }; // These are parameters for the pipe back end, an experimental back end that directs output to a pipe. From 38a10e1490756e0c84526dfad93c0f97dd2adb0c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 10 Jul 2015 15:15:00 +0000 Subject: [PATCH 151/220] Quieten some diagnostics and add a brief comment about the diagrnosticsi nthe timing code in rtp.c --- rtp.c | 13 ++++++++++--- rtsp.c | 4 ++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/rtp.c b/rtp.c index 2b03ef0e..e00a7536 100644 --- a/rtp.c +++ b/rtp.c @@ -462,14 +462,21 @@ static void *rtp_timing_receiver(void *arg) { // otherwise the source DAC's clock is running slow relative to the source system clock source_drift_usec = -(frame_time_difference_actual-frame_time_difference_calculated); } else - source_drift_usec = 0; + source_drift_usec = 0; source_drift_usec = (source_drift_usec*1000000)>>32; // turn it to microseconds int64_t current_delay = 0; if (config.output->delay) { current_delay = config.output->delay(); - } - debug(1, "%lld\t%lld\t%lld\t%lld\t%u\t%llu", clock_drift_in_usec,(session_corrections*1000000)/44100,current_delay,source_drift_usec,buffer_occupancy,(return_time*1000000)>>32); + } + // Useful for troubleshooting: + // clock_drift between source and local clock -- +ve means source is faster + // session_corrections -- the amount of correction done, in microseconds. +ve means frames added + // current_delay = delay in DAC buffer in frames + // source_drift_usec = how much faster (+ve) or slower the source DAC is running relative to the source clock + // buffer_occupancy = the number of buffers occupied. Crude, but should show no long term trend if source and device are in sync. + // return_time = the time from soliciting a timing packet to getting it back. It should be short ( < 5 ms) and pretty consistent. + // debug(1, "%lld\t%lld\t%lld\t%lld\t%u\t%llu", clock_drift_in_usec,(session_corrections*1000000)/44100,current_delay,source_drift_usec,buffer_occupancy,(return_time*1000000)>>32); } else { debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %d.", packet[1], nread); diff --git a/rtsp.c b/rtsp.c index 262d9b92..1a81b98f 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1153,11 +1153,11 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag } if ((paesiv==NULL) && (prsaaeskey==NULL)) { - debug(1,"Unencrypted session requested?"); + //debug(1,"Unencrypted session requested?"); conn->stream.encrypted = 0; } else { conn->stream.encrypted = 1; - debug(1,"Encrypted session requested"); + //debug(1,"Encrypted session requested"); } if (!pfmtp) { From 726c820143709981db10b5c38a4bb4f25feaa29d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 10 Jul 2015 15:15:45 +0000 Subject: [PATCH 152/220] Give command line arguments precedence over config file arguments by processing them afterwards --- shairport.c | 108 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 62 insertions(+), 46 deletions(-) diff --git a/shairport.c b/shairport.c index e5f32d30..0c81e745 100644 --- a/shairport.c +++ b/shairport.c @@ -229,7 +229,8 @@ void usage(char *progname) { int parse_options(int argc, char **argv) { char *stuffing = NULL; /* used for picking up the stuffing option */ - + + signed char c; /* used for argument parsing */ int i = 0; /* used for tracking options */ poptContext optCon; /* context for parsing command-line options */ @@ -269,7 +270,9 @@ int parse_options(int argc, char **argv) { #endif {NULL, 0, 0, NULL, 0}}; - int optind = argc; +// 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) @@ -278,50 +281,6 @@ int parse_options(int argc, char **argv) { optCon = poptGetContext(NULL, optind, (const char **)argv, optionsTable, 0); poptSetOtherOptionHelp(optCon, "[OPTIONS]* "); - /* Now do options processing, get portname */ - - while ((c = poptGetNextOpt(optCon)) >= 0) { - switch (c) { - case 'v': - debuglev++; - break; - case 't': - if (config.timeout == 0) { - config.dont_check_timeout = 1; - config.allow_session_interruption = 1; - } else { - config.dont_check_timeout = 0; - config.allow_session_interruption = 0; - } - break; -#ifdef CONFIG_METADATA - case 'M': - config.metadata_enabled = 1; - break; - case 'g': - if (config.metadata_enabled == 0) - die("If you want to get cover art, you must also select the --metadata-pipename option."); - break; -#endif - case 'S': - if (strcmp(stuffing, "basic") == 0) - config.packet_stuffing = ST_basic; - else if (strcmp(stuffing, "soxr") == 0) -#ifdef HAVE_LIBSOXR - config.packet_stuffing = ST_soxr; -#else - die("soxr option not available -- this version of shairport-sync was built without libsoxr " - "support"); -#endif - else - die("Illegal stuffing option \"%s\" -- must be \"basic\" or \"soxr\"", stuffing); - break; - } - } - if (c < -1) { - die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c)); - } - #ifdef SUPPORT_CONFIG_FILES config_setting_t *setting; const char *str; @@ -519,6 +478,63 @@ int parse_options(int argc, char **argv) { } #endif + +// now, do the command line options again, but this time do them fully -- it's a unix convention that command line +// arguments have precedence over configuration file settings. + + optind = argc; + for (j = 0; j < argc; j++) + if (strcmp(argv[j], "--") == 0) + optind = j; + + optCon = poptGetContext(NULL, optind, (const char **)argv, optionsTable, 0); + poptSetOtherOptionHelp(optCon, "[OPTIONS]* "); + + /* Now do options processing, get portname */ + int tdebuglev = 0; + while ((c = poptGetNextOpt(optCon)) >= 0) { + switch (c) { + case 'v': + tdebuglev++; + break; + case 't': + if (config.timeout == 0) { + config.dont_check_timeout = 1; + config.allow_session_interruption = 1; + } else { + config.dont_check_timeout = 0; + config.allow_session_interruption = 0; + } + break; +#ifdef CONFIG_METADATA + case 'M': + config.metadata_enabled = 1; + break; + case 'g': + if (config.metadata_enabled == 0) + die("If you want to get cover art, you must also select the --metadata-pipename option."); + break; +#endif + case 'S': + if (strcmp(stuffing, "basic") == 0) + config.packet_stuffing = ST_basic; + else if (strcmp(stuffing, "soxr") == 0) +#ifdef HAVE_LIBSOXR + config.packet_stuffing = ST_soxr; +#else + die("soxr option not available -- this version of shairport-sync was built without libsoxr " + "support"); +#endif + else + die("Illegal stuffing option \"%s\" -- must be \"basic\" or \"soxr\"", stuffing); + break; + } + } + if (c < -1) { + die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c)); + } + if (tdebuglev!=0) + debuglev = tdebuglev; return optind + 1; } From ea04dc53a9efbaac7e8347b0f1fa3f781fb464c8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 10 Jul 2015 15:16:12 +0000 Subject: [PATCH 153/220] Bump version --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index bbf345bc..cbb9e028 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.6.9], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.7], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From a94c93d4b6a05dabde76e49891c41fca06bfabba Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 10 Jul 2015 16:27:56 +0100 Subject: [PATCH 154/220] Update RELEASENOTES.md --- RELEASENOTES.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1ffb1c73..f4959e74 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,15 @@ +Version 2.3.7 +---- +* Changes + * Removed the two different buffer lengths for the alsa back end that made a brief appearance in 2.3.5. + +* Enhancements + * Command line arguments are now given precedence over config file settings. This conforms to standard unix practice. + * A `–without-pkg-config` configuration argument now allows for build systems, e.g. for older OpenWrt builds, that haven't fully implemented it. There is still some unhappiness in arch linux builds. + +* More + * Quite a bit of extra diagnostic code was written to investigate clock drift, DAC timings and so on. It was useful but has been commented out. If might be useful in the future. + Version 2.3.5 ---- * Changes From da041bd2816bb06c541125ac016010f6afc88961 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 10 Jul 2015 16:29:14 +0100 Subject: [PATCH 155/220] Update RELEASENOTES.md --- RELEASENOTES.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f4959e74..53a2595c 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,11 +2,9 @@ Version 2.3.7 ---- * Changes * Removed the two different buffer lengths for the alsa back end that made a brief appearance in 2.3.5. - * Enhancements * Command line arguments are now given precedence over config file settings. This conforms to standard unix practice. * A `–without-pkg-config` configuration argument now allows for build systems, e.g. for older OpenWrt builds, that haven't fully implemented it. There is still some unhappiness in arch linux builds. - * More * Quite a bit of extra diagnostic code was written to investigate clock drift, DAC timings and so on. It was useful but has been commented out. If might be useful in the future. From 7de6f4a9788c1fe1ee070e1b09d50b9c49440f2a Mon Sep 17 00:00:00 2001 From: Bill Peck Date: Mon, 27 Jul 2015 10:48:21 -0400 Subject: [PATCH 156/220] minor changes to support fedora and systemd --- Makefile.am | 13 ++++--- configure.ac | 4 +++ scripts/shairport-sync.service | 13 +++++++ shairport-sync.spec | 63 ++++++++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 scripts/shairport-sync.service create mode 100644 shairport-sync.spec diff --git a/Makefile.am b/Makefile.am index 84c20d60..97cebcbc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -52,11 +52,16 @@ endif install-exec-hook: if INSTALL_CONFIG_FILES - cp scripts/shairport-sync.conf /etc/shairport-sync.conf.sample - [ -f /etc/shairport-sync.conf ] || cp scripts/shairport-sync.conf /etc/shairport-sync.conf + [ -e $(DESTDIR)/etc ] || mkdir $(DESTDIR)/etc + cp scripts/shairport-sync.conf $(DESTDIR)/etc/shairport-sync.conf.sample + [ -f $(DESTDIR)/etc/shairport-sync.conf ] || cp scripts/shairport-sync.conf $(DESTDIR)/etc/shairport-sync.conf endif if INSTALL_INITSCRIPT - [ -f /etc/init.d/shairport-sync ] || cp scripts/shairport-sync /etc/init.d/ + [ -e $(DESTDIR)/etc/init.d ] || mkdir -p $(DESTDIR)/etc/init.d + [ -f /etc/init.d/shairport-sync ] || cp scripts/shairport-sync $(DESTDIR)/etc/init.d/ update-rc.d shairport-sync defaults 90 10 endif - +if INSTALL_SYSTEMD + [ -e $(DESTDIR)/usr/lib/systemd/system ] || mkdir -p $(DESTDIR)/usr/lib/systemd/system + cp scripts/shairport-sync.service $(DESTDIR)/usr/lib/systemd/system +endif diff --git a/configure.ac b/configure.ac index cbb9e028..af40f651 100644 --- a/configure.ac +++ b/configure.ac @@ -62,6 +62,10 @@ AC_ARG_WITH([initscript], [ --with-initscript = include a startup script], ,[with_initscript=1]) AM_CONDITIONAL([INSTALL_INITSCRIPT], [test "x$with_initscript" = "x1"]) +AC_ARG_WITH([systemd], +[ --with-systemd = include a systemd service], ,[with_systemd=1]) +AM_CONDITIONAL([INSTALL_SYSTEMD], [test "x$with_systemd" = "x1"]) + # Check if we want to support reading arguments from the command line AC_ARG_WITH([command_line_argument_support], [ --with-command-line-argument-support = read configuration from command line arguments], ,[with_command_line_argument_support=1]) diff --git a/scripts/shairport-sync.service b/scripts/shairport-sync.service new file mode 100644 index 00000000..2cd9bd48 --- /dev/null +++ b/scripts/shairport-sync.service @@ -0,0 +1,13 @@ +[Unit] +Description=ShairportSync AirTunes receiver +After=sound.target +Requires=avahi-daemon.service +After=avahi-daemon.service + +[Service] +ExecStart=/usr/bin/shairport-sync +User=shairport-sync +Group=shairport-sync + +[Install] +WantedBy=multi-user.target diff --git a/shairport-sync.spec b/shairport-sync.spec new file mode 100644 index 00000000..c2694f50 --- /dev/null +++ b/shairport-sync.spec @@ -0,0 +1,63 @@ +Name: shairport-sync +Version: 2.3.7 +Release: 1%{?dist} +Summary: AirTunes emulator. Shairport Sync adds multi-room capability with Audio Synchronisation + +Group: Applications/Multimedia +License: GPL +URL: https://github.com/mikebrady/shairport-sync +Source0: https://github.com/mikebrady/%{name}/archive/%{version}.tar.gz#/%{name}-%{version}.tar.gz +Patch0: shairport-sync-systemd.patch + +BuildRequires: autoconf +BuildRequires: automake +BuildRequires: libconfig-devel +BuildRequires: popt-devel +BuildRequires: openssl-devel +BuildRequires: libdaemon-devel +BuildRequires: avahi-devel +BuildRequires: alsa-lib-devel +BuildRequires: systemd-units +BuildRequires: soxr-devel +Requires: popt +Requires: openssl +Requires: avahi +Requires: libdaemon +Requires: alsa-lib +Requires: soxr + +%description +Shairport Sync emulates an AirPort Express for the purpose of streaming audio from iTunes, iPods, iPhones, iPads and AppleTVs. Audio played by a Shairport Sync-powered device stays synchronised with the source and hence with similar devices playing the same source. Thus, for example, synchronised multi-room audio is possible without difficulty. (Hence the name Shairport Sync, BTW.) + +Shairport Sync does not support AirPlay video or photo streaming. + +%prep +%setup -q +%patch0 -p1 + +%build +autoreconf -i -f +%configure --with-avahi --with-alsa --with-ssl=openssl --with-soxr --without-initscript +make %{?_smp_mflags} + + +%install +make install DESTDIR=%{buildroot} +rm %{buildroot}/etc/shairport-sync.conf.sample + +%pre +getent group %{name} &>/dev/null || groupadd --system %{name} >/dev/null +getent passwd %{name} &> /dev/null || useradd --system -c "%{name} User" \ + -d %{_localstatedir}/%{name} -m -g %{name} -s /sbin/nologin \ + -G audio %{name} >/dev/null + +%files +%config /etc/shairport-sync.conf +/usr/bin/shairport-sync +/usr/share/man/man7/shairport-sync.7.gz +%{_unitdir}/%{name}.service +%doc AUTHORS LICENSES README.md + +%changelog +* Fri Jul 24 2015 Bill Peck 2.3.7-1 +- Initial spec file From 5bd060f73d02cf5360216d636426c2f640016684 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 1 Aug 2015 14:31:18 +0200 Subject: [PATCH 157/220] Make the dummy, pipe and stdout backends optional and rename initscript to systemv --- Makefile.am | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index 97cebcbc..c6f6ed13 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,7 +1,7 @@ SUBDIRS = man bin_PROGRAMS = shairport-sync -shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c audio_dummy.c audio_pipe.c audio_stdout.c +shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c AM_CFLAGS = -Wno-multichar @@ -56,7 +56,7 @@ if INSTALL_CONFIG_FILES cp scripts/shairport-sync.conf $(DESTDIR)/etc/shairport-sync.conf.sample [ -f $(DESTDIR)/etc/shairport-sync.conf ] || cp scripts/shairport-sync.conf $(DESTDIR)/etc/shairport-sync.conf endif -if INSTALL_INITSCRIPT +if INSTALL_SYSTEMV [ -e $(DESTDIR)/etc/init.d ] || mkdir -p $(DESTDIR)/etc/init.d [ -f /etc/init.d/shairport-sync ] || cp scripts/shairport-sync $(DESTDIR)/etc/init.d/ update-rc.d shairport-sync defaults 90 10 From 55f57c1fb492e95066603b6185f5e0c987a8ef3c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 1 Aug 2015 14:32:03 +0200 Subject: [PATCH 158/220] Make dummy, pipe and stdout back ends truly optinal --- audio.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/audio.c b/audio.c index e8f56718..6c2c6fce 100644 --- a/audio.c +++ b/audio.c @@ -41,7 +41,15 @@ extern audio_output audio_pulse; #ifdef CONFIG_ALSA extern audio_output audio_alsa; #endif -extern audio_output audio_dummy, audio_pipe, audio_stdout; +#ifdef CONFIG_DUMMY +extern audio_output audio_dummy; +#endif +#ifdef CONFIG_PIPE +extern audio_output audio_pipe; +#endif +#ifdef CONFIG_STDOUT +extern audio_output audio_stdout; +#endif static audio_output *outputs[] = { #ifdef CONFIG_SNDIO @@ -56,7 +64,16 @@ static audio_output *outputs[] = { #ifdef CONFIG_AO &audio_ao, #endif - &audio_dummy, &audio_pipe, &audio_stdout, NULL}; +#ifdef CONFIG_DUMMY + &audio_dummy, +#endif +#ifdef CONFIG_PIPE + &audio_pipe, +#endif +#ifdef CONFIG_STDOUT + &audio_stdout, +#endif + NULL}; audio_output *audio_get_output(char *name) { audio_output **out; From afd8017f60219cfb7b9df206a8746bd5e213fa61 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 1 Aug 2015 14:33:24 +0200 Subject: [PATCH 159/220] Remove redundant conditional compilation flags --- shairport.c | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/shairport.c b/shairport.c index 0c81e745..b77b1807 100644 --- a/shairport.c +++ b/shairport.c @@ -62,9 +62,7 @@ static int shutting_down = 0; static char *appName = NULL; -#ifdef SUPPORT_CONFIG_FILES - char configuration_file_path[4096]; -#endif +char configuration_file_path[4096]; void shairport_shutdown() { if (shutting_down) @@ -149,9 +147,6 @@ void print_version(void) { #endif #ifdef CONFIG_METADATA strcat(version_string, "-metadata"); -#endif -#ifdef SUPPORT_CONFIG_FILES - strcat(version_string, "-configfile"); #endif printf("%s\n", version_string); } @@ -170,7 +165,6 @@ void usage(char *progname) { printf(" -c, --configfile=FILE read configuration settings from FILE. Default is " "/etc/shairport-sync.conf.\n"); -#ifdef COMMAND_LINE_ARGUMENT_SUPPORT printf(" -v, --verbose -v print debug information; -vv more; -vvv lots\n"); printf(" -p, --port=PORT set RTSP listening port\n"); printf(" -a, --name=NAME set advertised name\n"); @@ -217,10 +211,7 @@ void usage(char *progname) { "--metadata-pipename=/tmp/shairport-sync-metadata.\n"); printf(" --get-coverart send cover art through the metadata pipe.\n"); #endif -#endif -#ifdef SUPPORT_CONFIG_FILES printf("\nGeneral options can be configured in /etc/%s.conf.\n", appName); -#endif printf("\n"); mdns_ls_backends(); printf("\n"); @@ -240,10 +231,7 @@ int parse_options(int argc, char **argv) { {"reconnectToOutput", 'R', POPT_ARG_NONE, NULL, 0, NULL}, {"kill", 'k', POPT_ARG_NONE, NULL, 0, NULL}, {"daemon", 'd', POPT_ARG_NONE, &config.daemonise, 0, NULL}, -#ifdef SUPPORT_CONFIG_FILES {"configfile", 'c', POPT_ARG_STRING, &config.configfile, 0, NULL}, -#endif -#ifdef COMMAND_LINE_ARGUMENT_SUPPORT {"statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL}, {"version", 'V', POPT_ARG_NONE, NULL, 0, NULL}, {"port", 'p', POPT_ARG_INT, &config.port, 0, NULL}, @@ -267,7 +255,6 @@ int parse_options(int argc, char **argv) { {"get-coverart", 'g', POPT_ARG_NONE, &config.get_coverart, 'g', NULL}, #endif POPT_AUTOHELP -#endif {NULL, 0, 0, NULL, 0}}; // we have to parse the command line arguments to look for a config file @@ -281,7 +268,6 @@ int parse_options(int argc, char **argv) { optCon = poptGetContext(NULL, optind, (const char **)argv, optionsTable, 0); poptSetOtherOptionHelp(optCon, "[OPTIONS]* "); -#ifdef SUPPORT_CONFIG_FILES config_setting_t *setting; const char *str; int value; @@ -477,8 +463,6 @@ int parse_options(int argc, char **argv) { } } -#endif - // now, do the command line options again, but this time do them fully -- it's a unix convention that command line // arguments have precedence over configuration file settings. @@ -600,10 +584,8 @@ const char *pid_file_proc(void) { #endif void exit_function() { -#ifdef SUPPORT_CONFIG_FILES if (config.cfg) config_destroy(config.cfg); -#endif } int main(int argc, char **argv) { @@ -619,12 +601,10 @@ int main(int argc, char **argv) { free(basec); // set defaults -#ifdef SUPPORT_CONFIG_FILES strcpy(configuration_file_path, "/etc/"); strcat(configuration_file_path, appName); strcat(configuration_file_path, ".conf"); config.configfile = configuration_file_path; -#endif config.statistics_requested - 0; // don't print stats in the log config.latency = 88200; // AirPlay. Is also reset in rtsp.c when play is about to start @@ -840,9 +820,7 @@ int main(int argc, char **argv) { debug(2, "audio backend desired buffer length is %d.", config.audio_backend_buffer_desired_length); debug(2, "audio backend latency offset is %d.", config.audio_backend_latency_offset); -#ifdef SUPPORT_CONFIG_FILES debug(2, "Configuration file name \"%s\".", config.configfile); -#endif #ifdef CONFIG_METADATA debug(2, "metdata enabled is %d.", config.metadata_enabled); debug(2, "metadata pipename is \"%s\".", config.metadata_pipename); From ff95a96e61effeffdb083018af80f1829dacbab9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 1 Aug 2015 14:34:35 +0200 Subject: [PATCH 160/220] Allow more stack space for thread, include freebsd in the name of condional compilation variable --- player.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/player.c b/player.c index 522b6fa4..29fb1810 100644 --- a/player.c +++ b/player.c @@ -663,7 +663,7 @@ static abuf_t *buffer_get_frame(void) { time_to_wait_for_wakeup_fp *= 4 * 352; // four full 352-frame packets time_to_wait_for_wakeup_fp /= 3; // four thirds of a packet time -#ifdef COMPILE_FOR_LINUX +#ifdef COMPILE_FOR_LINUX_AND_FREEBSD uint64_t time_of_wakeup_fp = local_time_now + time_to_wait_for_wakeup_fp; uint64_t sec = time_of_wakeup_fp >> 32; uint64_t nsec = ((time_of_wakeup_fp & 0xffffffff) * 1000000000) >> 32; @@ -1268,7 +1268,7 @@ int player_play(stream_cfg *stream) { #endif // set the flowcontrol condition variable to wait on a monotonic clock -#ifdef COMPILE_FOR_LINUX +#ifdef COMPILE_FOR_LINUX_AND_FREEBSD pthread_condattr_t attr; pthread_condattr_init(&attr); pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it. @@ -1280,7 +1280,7 @@ int player_play(stream_cfg *stream) { if (rc) debug(1, "Error initialising condition variable."); config.output->start(sampling_rate); - size_t size = (PTHREAD_STACK_MIN + 128 * 1024); + size_t size = (PTHREAD_STACK_MIN + 256 * 1024); pthread_attr_t tattr; pthread_attr_init(&tattr); rc = pthread_attr_setstacksize(&tattr, size); From 43c13ae73766060fe02681876be2dc71515e1b8d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 1 Aug 2015 14:35:48 +0200 Subject: [PATCH 161/220] Remove a redundant conditional compilation stanza and redefine a linux compilation flag to include the name freebsd --- common.h | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/common.h b/common.h index 39855c59..2ed250a2 100644 --- a/common.h +++ b/common.h @@ -18,9 +18,9 @@ #endif #endif -#if defined(__linux__) -/* Linux. --------------------------------------------------- */ -#define COMPILE_FOR_LINUX 1 +#if defined(__linux__) || defined(__FreeBSD__) +/* Linux and FreeBSD */ +#define COMPILE_FOR_LINUX_AND_FREEBSD 1 #endif // struct sockaddr_in6 is bigger than struct sockaddr. derp @@ -77,9 +77,7 @@ typedef struct { char *pidfile; char *logfile; char *errfile; -#ifdef SUPPORT_CONFIG_FILES char *configfile; -#endif uint32_t audio_backend_buffer_desired_length; // this will be the desired number of frames in the // audio backend buffer -- the DAC buffer for ALSA uint32_t audio_backend_latency_offset; // this will be the offset to compensate for any fixed latency From 670dd933a2069c3ff943029f093b2875839614fa Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 1 Aug 2015 14:36:42 +0200 Subject: [PATCH 162/220] Use the renamed linux compilation flag renamed to include the word freebsd --- README.md | 4 +-- RELEASENOTES.md | 18 ++++++++++++++ common.c | 3 ++- configure.ac | 66 +++++++++++++++++++------------------------------ 4 files changed, 48 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 937c0328..fb2fa085 100644 --- a/README.md +++ b/README.md @@ -98,9 +98,9 @@ Choose the appropriate `--with-*` options: Here is an example, suitable for most installations: -`$ ./configure --with-alsa --with-avahi --with-ssl=openssl --with-metadata --with-soxr` +`$ ./configure --with-alsa --with-avahi --with-ssl=openssl --with-metadata --with-systemv --with-soxr` -Omit the `--with-soxr` if the libsoxr library is not available. +Omit the `--with-soxr` if the libsoxr library is not available. For installation into a `systemd` system, replace the `--with-systemv` with `--with-systemd`. `$ make` diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 53a2595c..9c395a7d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,21 @@ +Version 2.3.8 +---- +* Pesky changes you must make + * You must change the `./configure` arguments. The flag `with-initscript` has changed to `with-systemv`. It was previously enabled by default; now you must enable it explicitly. + +* Changes + * Support for installing into `systemd` and Fedora systems. For `systemd` support, use the configuration flag `--with-systemd` in place of `--with-systemv`. + * Renamed `with-initscript` configuration flag to `with-systemv` to better describe its role. + * System V startup script is no longer enabled by default; if you want is, ask for it with the `--with-systemv` configuration flag. + * Added limited support for FreeBSD. You must specify `LDFLAGS='-I/usr/local/lib'` and `CPPFLAGS='-L/usr/local/include'` before running `./configure --with-foo etc.` + * Removed "-configfile" annotation from the version string because it'n no longer optional; it's always there. + * Removed the `dummy`, `pipe` and `stdout` backends from the standard build -- they are now optional and are no longer automatically included in the build. + +* Bug fixes + * Allow more stack space to prevent a segfault in certain configurations. Thanks @joerg-krause. + * Add missing header files. Thanks @joerg-krause. + * Removed some (hopefully) mostly silent bugs from the configure.ac file. + Version 2.3.7 ---- * Changes diff --git a/common.c b/common.c index 83837437..fb8702e0 100644 --- a/common.c +++ b/common.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -472,7 +473,7 @@ double vol2attn(double vol, long max_db, long min_db) { uint64_t get_absolute_time_in_fp() { uint64_t time_now_fp; -#ifdef COMPILE_FOR_LINUX +#ifdef COMPILE_FOR_LINUX_AND_FREEBSD struct timespec tn; // can't use CLOCK_MONOTONIC_RAW as it's not implemented in OpenWrt clock_gettime(CLOCK_MONOTONIC, &tn); diff --git a/configure.ac b/configure.ac index af40f651..e74a187d 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.7], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.8], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) @@ -32,14 +32,14 @@ fi ##### to control how to deal with them AC_ARG_WITH([pkg_config], -[ --with-pkg-config = use pkg-config to find libraries], ,[with_pkg_config=1]) +[ --with-pkg-config = use pkg-config to find libraries], ,[with_pkg_config=yes]) AC_CHECK_LIB([daemon],[daemon_log], , AC_MSG_ERROR(libdaemon needed)) AC_CHECK_LIB([pthread],[pthread_create], , AC_MSG_ERROR(pthread library needed)) AC_CHECK_LIB([m],[exp], , AC_MSG_ERROR(maths library needed)) AC_MSG_RESULT(>>Including libpopt) -if test "x${with_pkg_config}" = x1 ; then +if test "x${with_pkg_config}" = xyes ; then PKG_CHECK_MODULES( [POPT], [popt], [LIBS="${POPT_LIBS} ${LIBS}" @@ -50,54 +50,41 @@ else fi AC_ARG_WITH([dummy],[ --with-dummy = include the dummy audio back end ],[AC_MSG_RESULT(>>Including the dummy audio back end) AC_DEFINE([CONFIG_DUMMY], 1, [Needed by the compiler.]) ], ) -AM_CONDITIONAL([USE_DUMMY], [test "x$with_dummy" = "x1" ]) +AM_CONDITIONAL([USE_DUMMY], [test "x$with_dummy" = "xyes" ]) AC_ARG_WITH([stdout],[ --with-stdout = include the stdout audio back end ],[ AC_MSG_RESULT(>>Including the stdout audio back end) AC_DEFINE([CONFIG_STDOUT], 1, [Needed by the compiler.]) ], ) -AM_CONDITIONAL([USE_STDOUT], [test "x$with_stdout" = "x1" ]) + +AM_CONDITIONAL([USE_STDOUT], [test "x$with_stdout" = "xyes" ]) AC_ARG_WITH([pipe],[ --with-pipe = include the pipe audio back end ],[ AC_MSG_RESULT(>>Including the pipe audio back end) AC_DEFINE([CONFIG_PIPE], 1, [Needed by the compiler.]) ], ) -AM_CONDITIONAL([USE_PIPE], [test "x$with_pipe" = "x1" ]) +AM_CONDITIONAL([USE_PIPE], [test "x$with_pipe" = "xyes" ]) -AC_ARG_WITH([initscript], -[ --with-initscript = include a startup script], ,[with_initscript=1]) -AM_CONDITIONAL([INSTALL_INITSCRIPT], [test "x$with_initscript" = "x1"]) +# Check to see if we should include the System V initscript + +AC_ARG_WITH([systemv], +[ --with-systemv = include a startup script], , ) +AM_CONDITIONAL([INSTALL_SYSTEMV], [test "x$with_systemv" = "xyes"]) + +# Check to see if we should include the systemd stuff to define it as a service AC_ARG_WITH([systemd], -[ --with-systemd = include a systemd service], ,[with_systemd=1]) -AM_CONDITIONAL([INSTALL_SYSTEMD], [test "x$with_systemd" = "x1"]) +[ --with-systemd = include a systemd service], , ) +AM_CONDITIONAL([INSTALL_SYSTEMD], [test "x$with_systemd" = "xyes"]) -# Check if we want to support reading arguments from the command line -AC_ARG_WITH([command_line_argument_support], -[ --with-command-line-argument-support = read configuration from command line arguments], ,[with_command_line_argument_support=1]) - -if test "x${with_command_line_argument_support}" = x1 ; then - AC_MSG_RESULT(>>Including command line argument support) - AC_DEFINE([COMMAND_LINE_ARGUMENT_SUPPORT],[1],[Define to 1 if you want to support command line arguments]) -fi - -# Check if we want to support reading from a configuration file chosen -- i.e. take parameters from /etc/shairport-sync.conf -AC_ARG_WITH([configfile_support], -[ --with-configfile-support = support reading of configuration from a configuration file], ,[with_configfile_support=1]) - -if test "x${with_configfile_support}" = x1 ; then - AC_MSG_RESULT(>>Including configuration file support) - if test "x${with_pkg_config}" = x1 ; then - PKG_CHECK_MODULES( - [LIBCONFIG], [libconfig], - [LIBS="${LIBCONFIG_LIBS} ${LIBS}"]) - else - AC_CHECK_LIB([config],[config_init], , AC_MSG_ERROR([libconfig library needed])) - fi - AC_DEFINE([SUPPORT_CONFIG_FILES],[1],[Define to 1 if you want to support configuration files]) +# Add the libconfig package +if test "x${with_pkg_config}" = xyes ; then + PKG_CHECK_MODULES( + [LIBCONFIG], [libconfig], + [LIBS="${LIBCONFIG_LIBS} ${LIBS}"]) +else + AC_CHECK_LIB([config],[config_init], , AC_MSG_ERROR([libconfig library needed])) fi AC_ARG_WITH([configfiles], -[ --with-configfiles = include configuration files in installation ], ,[with_configfiles=1]) -AM_CONDITIONAL([INSTALL_CONFIG_FILES], [test "x$with_configfiles" = "x1" -a "x$with_configfile_support" = "x1" ]) +[ --with-configfiles = include configuration files in installation ], ,[with_configfiles=yes]) +AM_CONDITIONAL([INSTALL_CONFIG_FILES], [test "x$with_configfiles" = "xyes"]) -# Check to see if we should include the initscript - # Look for piddir flag AC_ARG_WITH(piddir, [ --with-piddir= Specify a pathname to a directory in which to write the PID file.], [ AC_MSG_CHECKING(--with-piddir argument) @@ -146,9 +133,8 @@ AC_ARG_WITH(soxr, [ --with-soxr = choose libsoxr for high-quality interpolation # Look for metadata flag -- set flag for conditional compilation AC_ARG_WITH(metadata, [ --with-metadata = include support for a metadata feed], [ AC_MSG_RESULT(>>Including metadata support) - HAS_METADATA=1 AC_DEFINE([CONFIG_METADATA], 1, [Needed by the compiler.])], ) -AM_CONDITIONAL([USE_METADATA], [test "x$HAS_METADATA" = "x1"]) +AM_CONDITIONAL([USE_METADATA], [test "x$with_metadata" = "xyes"]) # What follows is a bit messy, because if the relevant library is requested, a compiler flag is defined, a file is included in the compilation # and the relevant link files are added. From d570a14fbf930cf8fc483c1cbc6d4ec5da187541 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 1 Aug 2015 14:44:08 +0200 Subject: [PATCH 163/220] Add the identifier freebsd -- we might begin to make more use of it in the future --- configure.ac | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index e74a187d..11ae3daf 100644 --- a/configure.ac +++ b/configure.ac @@ -11,7 +11,7 @@ AC_CONFIG_HEADERS([config.h]) # # Specifying the OS type, defaulting to linux. # -AC_ARG_WITH(os_type, AS_HELP_STRING([--with-os-type=OSType],[Specify the distribution to target: One of linux darwin])) +AC_ARG_WITH(os_type, AS_HELP_STRING([--with-os-type=OSType],[Specify the distribution to target: One of linux freebsd or darwin])) if test "z$with_os_type" = "z"; then with_os_type="linux" fi @@ -24,7 +24,7 @@ AC_PROG_INSTALL PKG_PROG_PKG_CONFIG([0.9.0]) # Checks for libraries. -if test "x${with_os_type}" = xlinux; then +if test "x${with_os_type}" = xlinux -o "x${with_os_type}" = xfreebsd ; then AC_CHECK_LIB([rt],[clock_gettime], , AC_MSG_ERROR(librt needed)) fi From 8637a23af7fd318a9290a8d50aeaaca1f4943098 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 1 Aug 2015 14:50:45 +0200 Subject: [PATCH 164/220] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9c395a7d..358002a4 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -6,7 +6,7 @@ Version 2.3.8 * Changes * Support for installing into `systemd` and Fedora systems. For `systemd` support, use the configuration flag `--with-systemd` in place of `--with-systemv`. * Renamed `with-initscript` configuration flag to `with-systemv` to better describe its role. - * System V startup script is no longer enabled by default; if you want is, ask for it with the `--with-systemv` configuration flag. + * System V startup script is no longer enabled by default; if you want it, ask for it with the `--with-systemv` configuration flag. * Added limited support for FreeBSD. You must specify `LDFLAGS='-I/usr/local/lib'` and `CPPFLAGS='-L/usr/local/include'` before running `./configure --with-foo etc.` * Removed "-configfile" annotation from the version string because it'n no longer optional; it's always there. * Removed the `dummy`, `pipe` and `stdout` backends from the standard build -- they are now optional and are no longer automatically included in the build. From 565d3458e5ca655f2dbcde707560dd7e8c741b21 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 1 Aug 2015 15:38:44 +0200 Subject: [PATCH 165/220] Update RELEASENOTES.md --- RELEASENOTES.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 358002a4..890151b0 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,19 +1,19 @@ Version 2.3.8 ---- * Pesky changes you must make - * You must change the `./configure` arguments. The flag `with-initscript` has changed to `with-systemv`. It was previously enabled by default; now you must enable it explicitly. + * You probably need to change your `./configure` arguments. The flag `with-initscript` has changed to `with-systemv`. It was previously enabled by default; now you must enable it explicitly. * Changes - * Support for installing into `systemd` and Fedora systems. For `systemd` support, use the configuration flag `--with-systemd` in place of `--with-systemv`. + * Support for installing into `systemd` and Fedora systems. For `systemd` support, use the configuration flag `--with-systemd` in place of `--with-systemv`. (Arch Linux users should continue to use the package provided by Elia Cereda.) * Renamed `with-initscript` configuration flag to `with-systemv` to better describe its role. * System V startup script is no longer enabled by default; if you want it, ask for it with the `--with-systemv` configuration flag. * Added limited support for FreeBSD. You must specify `LDFLAGS='-I/usr/local/lib'` and `CPPFLAGS='-L/usr/local/include'` before running `./configure --with-foo etc.` - * Removed "-configfile" annotation from the version string because it'n no longer optional; it's always there. - * Removed the `dummy`, `pipe` and `stdout` backends from the standard build -- they are now optional and are no longer automatically included in the build. + * Removed the `-configfile` annotation from the version string because it's no longer optional; it's always there. + * Removed the `dummy`, `pipe` and `stdout` backends from the standard build – they are now optional and are no longer automatically included in the build. * Bug fixes - * Allow more stack space to prevent a segfault in certain configurations. Thanks @joerg-krause. - * Add missing header files. Thanks @joerg-krause. + * Allow more stack space to prevent a segfault in certain configurations (thanks to https://github.com/joerg-krause). + * Add missing header files(thanks to https://github.com/joerg-krause). * Removed some (hopefully) mostly silent bugs from the configure.ac file. Version 2.3.7 From 30907b130df51156e9e0e2251ce1b7acf6edea69 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 2 Aug 2015 12:38:40 +0200 Subject: [PATCH 166/220] Update configure.ac --- configure.ac | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/configure.ac b/configure.ac index 11ae3daf..06c1c620 100644 --- a/configure.ac +++ b/configure.ac @@ -62,13 +62,13 @@ AM_CONDITIONAL([USE_PIPE], [test "x$with_pipe" = "xyes" ]) # Check to see if we should include the System V initscript AC_ARG_WITH([systemv], -[ --with-systemv = include a startup script], , ) +[ --with-systemv = install a System V startup script during a make install], , ) AM_CONDITIONAL([INSTALL_SYSTEMV], [test "x$with_systemv" = "xyes"]) # Check to see if we should include the systemd stuff to define it as a service AC_ARG_WITH([systemd], -[ --with-systemd = include a systemd service], , ) +[ --with-systemd = install a systemd service description file during a make install], , ) AM_CONDITIONAL([INSTALL_SYSTEMD], [test "x$with_systemd" = "xyes"]) # Add the libconfig package @@ -81,7 +81,7 @@ else fi AC_ARG_WITH([configfiles], -[ --with-configfiles = include configuration files in installation ], ,[with_configfiles=yes]) +[ --with-configfiles = install configuration files during a make install ], ,[with_configfiles=yes]) AM_CONDITIONAL([INSTALL_CONFIG_FILES], [test "x$with_configfiles" = "xyes"]) From 04fadca49786fbd9541f5dd929dfa7e9d00d22c6 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 2 Aug 2015 12:39:46 +0200 Subject: [PATCH 167/220] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 890151b0..74afcbbd 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,6 +1,6 @@ Version 2.3.8 ---- -* Pesky changes you must make +* Annoying changes you must make * You probably need to change your `./configure` arguments. The flag `with-initscript` has changed to `with-systemv`. It was previously enabled by default; now you must enable it explicitly. * Changes From 07af6b1001a57b18598097b381c63d404afa7e9a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 2 Aug 2015 12:46:37 +0200 Subject: [PATCH 168/220] Update RELEASENOTES.md --- RELEASENOTES.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 74afcbbd..fbdf268f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -4,9 +4,9 @@ Version 2.3.8 * You probably need to change your `./configure` arguments. The flag `with-initscript` has changed to `with-systemv`. It was previously enabled by default; now you must enable it explicitly. * Changes - * Support for installing into `systemd` and Fedora systems. For `systemd` support, use the configuration flag `--with-systemd` in place of `--with-systemv`. (Arch Linux users should continue to use the package provided by Elia Cereda.) - * Renamed `with-initscript` configuration flag to `with-systemv` to better describe its role. - * System V startup script is no longer enabled by default; if you want it, ask for it with the `--with-systemv` configuration flag. + * Added limited support for installing into `systemd` and Fedora systems. For `systemd` support, use the configuration flag `--with-systemd` in place of `--with-systemv`. The installation does not do everything needed, such as defining special users and groups. + * Renamed `with-initscript` configuration flag to `with-systemv` to describe its role more accurately. + * A System V startup script is no longer installed by default; if you want it, ask for it with the `--with-systemv` configuration flag. * Added limited support for FreeBSD. You must specify `LDFLAGS='-I/usr/local/lib'` and `CPPFLAGS='-L/usr/local/include'` before running `./configure --with-foo etc.` * Removed the `-configfile` annotation from the version string because it's no longer optional; it's always there. * Removed the `dummy`, `pipe` and `stdout` backends from the standard build – they are now optional and are no longer automatically included in the build. From b365f16b2f307a8bc0689dfcef96b3c9bf795ac5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 6 Aug 2015 11:07:30 +0200 Subject: [PATCH 169/220] Make configuration file spec work correctly if it's relative; honour debug verbosity from the very start --- configure.ac | 2 +- shairport.c | 404 +++++++++++++++++++++------------------ var/shairport-sync.conf | 6 + var/shairport-sync.conf~ | 6 + 4 files changed, 230 insertions(+), 188 deletions(-) create mode 100644 var/shairport-sync.conf create mode 100644 var/shairport-sync.conf~ diff --git a/configure.ac b/configure.ac index 06c1c620..8824e600 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.8], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.9], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) diff --git a/shairport.c b/shairport.c index b77b1807..8acd523b 100644 --- a/shairport.c +++ b/shairport.c @@ -62,7 +62,8 @@ static int shutting_down = 0; static char *appName = NULL; -char configuration_file_path[4096]; +char configuration_file_path[4096+1]; +char actual_configuration_file_path[4096+1]; void shairport_shutdown() { if (shutting_down) @@ -267,200 +268,222 @@ int parse_options(int argc, char **argv) { optCon = poptGetContext(NULL, optind, (const char **)argv, optionsTable, 0); poptSetOtherOptionHelp(optCon, "[OPTIONS]* "); + + /* Now do options processing just to get a debug level */ + debuglev = 0; + while ((c = poptGetNextOpt(optCon)) >= 0) { + switch (c) { + case 'v': + debuglev++; + break; + } + } + if (c < -1) { + die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c)); + } config_setting_t *setting; const char *str; int value; + debug(1,"Looking for the configuration file \"%s\".",config.configfile); + config_init(&config_file_stuff); - debug(1, "Looking for configuration file \"%s\"", config.configfile); - /* Read the file. If there is an error, report it and exit. */ - if (config_read_file(&config_file_stuff, config.configfile)) { - // make config.cfg point to it - config.cfg = &config_file_stuff; - /* Get the Service Name. */ - if (config_lookup_string(config.cfg, "general.name", &str)) - config.apname = (char *)str; - - /* Get the Daemonize setting. */ - if (config_lookup_string(config.cfg, "general.daemonize", &str)) { - if (strcasecmp(str, "no") == 0) - config.daemonise = 0; - else if (strcasecmp(str, "yes") == 0) - config.daemonise = 1; - else - die("Invalid daemonize option choice \"%s\". It should be \"yes\" or \"no\""); - } - - /* 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)) - die("Invalid port number \"%sd\". It should be between 0 and 65535, default is 5000", - value); - 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 < 0) || (value > 65535)) - die("Invalid port range \"%sd\". It should be between 0 and 65535, default is 100", - 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, "soxr") == 0) - config.packet_stuffing = ST_soxr; - else - die("Invalid interpolation option choice \"%s\". It should be \"basic\" or \"soxr\""); - } - - /* Get the statistics setting. */ - if (config_lookup_string(config.cfg, "general.statistics", &str)) { - if (strcasecmp(str, "no") == 0) - config.statistics_requested = 0; - else if (strcasecmp(str, "yes") == 0) - config.statistics_requested = 1; - else - die("Invalid statistics option choice \"%s\". It should be \"yes\" or \"no\""); - } - - /* Get the drift tolerance setting. */ - if (config_lookup_int(config.cfg, "general.drift", &value)) - config.tolerance = value; - - /* Get the resync setting. */ - if (config_lookup_int(config.cfg, "general.resync_threshold", &value)) - config.resyncthreshold = value; - - /* Get the verbosity setting. */ - if (config_lookup_int(config.cfg, "general.log_verbosity", &value)) - 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 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\""); - } - - /* Get the default latency. */ - if (config_lookup_int(config.cfg, "latencies.default", &value)) - config.latency = value; - - /* Get the itunes latency. */ - if (config_lookup_int(config.cfg, "latencies.itunes", &value)) - config.iTunesLatency = value; - - /* Get the AirPlay latency. */ - if (config_lookup_int(config.cfg, "latencies.airplay", &value)) - config.AirPlayLatency = value; - - /* Get the forkedDaapd latency. */ - if (config_lookup_int(config.cfg, "latencies.forkedDaapd", &value)) - config.ForkedDaapdLatency = value; - -#ifdef CONFIG_METADATA - /* Get the metadata setting. */ - if (config_lookup_string(config.cfg, "metadata.enabled", &str)) { - if (strcasecmp(str, "no") == 0) - config.metadata_enabled = 0; - else if (strcasecmp(str, "yes") == 0) - config.metadata_enabled = 1; - else - die("Invalid metadata enabled option choice \"%s\". It should be \"yes\" or \"no\""); - } - - if (config_lookup_string(config.cfg, "metadata.include_cover_art", &str)) { - if (strcasecmp(str, "no") == 0) - config.get_coverart = 0; - else if (strcasecmp(str, "yes") == 0) - config.get_coverart = 1; - else - die("Invalid metadata include_cover_art option choice \"%s\". It should be \"yes\" or " - "\"no\""); - } - - if (config_lookup_string(config.cfg, "metadata.pipe_name", &str)) { - config.metadata_pipename = (char *)str; - } -#endif - - if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_play_begins", &str)) { - config.cmd_start = (char *)str; - } - - if (config_lookup_string(config.cfg, "sessioncontrol.run_this_after_play_ends", &str)) { - config.cmd_stop = (char *)str; - } - - if (config_lookup_string(config.cfg, "sessioncontrol.wait_for_completion", &str)) { - if (strcasecmp(str, "no") == 0) - config.cmd_blocking = 0; - else if (strcasecmp(str, "yes") == 0) - config.cmd_blocking = 1; - else - die("Invalid session control wait_for_completion option choice \"%s\". It should be " - "\"yes\" or \"no\""); - } - - if (config_lookup_string(config.cfg, "sessioncontrol.allow_session_interruption", &str)) { - config.dont_check_timeout = 0; // this is for legacy -- only set by -t 0 - if (strcasecmp(str, "no") == 0) - config.allow_session_interruption = 0; - else if (strcasecmp(str, "yes") == 0) - config.allow_session_interruption = 1; - else - die("Invalid session control allow_interruption option choice \"%s\". It should be \"yes\" " - "or \"no\""); - } - - if (config_lookup_int(config.cfg, "sessioncontrol.session_timeout", &value)) { - config.timeout = value; - config.dont_check_timeout = 0; // this is for legacy -- only set by -t 0 - } - + + char *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 { - if (config_error_type(&config_file_stuff) == CONFIG_ERR_FILE_IO) - debug(1, "Error reading configuration file \"%s\": \"%s\".", + 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)) { + // make config.cfg point to it + config.cfg = &config_file_stuff; + /* Get the Service Name. */ + if (config_lookup_string(config.cfg, "general.name", &str)) + config.apname = (char *)str; + + /* Get the Daemonize setting. */ + if (config_lookup_string(config.cfg, "general.daemonize", &str)) { + if (strcasecmp(str, "no") == 0) + config.daemonise = 0; + else if (strcasecmp(str, "yes") == 0) + config.daemonise = 1; + else + die("Invalid daemonize option choice \"%s\". It should be \"yes\" or \"no\""); + } + + /* 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)) + die("Invalid port number \"%sd\". It should be between 0 and 65535, default is 5000", + value); + 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 < 0) || (value > 65535)) + die("Invalid port range \"%sd\". It should be between 0 and 65535, default is 100", + 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, "soxr") == 0) + config.packet_stuffing = ST_soxr; + else + die("Invalid interpolation option choice \"%s\". It should be \"basic\" or \"soxr\""); + } + + /* Get the statistics setting. */ + if (config_lookup_string(config.cfg, "general.statistics", &str)) { + if (strcasecmp(str, "no") == 0) + config.statistics_requested = 0; + else if (strcasecmp(str, "yes") == 0) + config.statistics_requested = 1; + else + die("Invalid statistics option choice \"%s\". It should be \"yes\" or \"no\""); + } + + /* Get the drift tolerance setting. */ + if (config_lookup_int(config.cfg, "general.drift", &value)) + config.tolerance = value; + + /* Get the resync setting. */ + if (config_lookup_int(config.cfg, "general.resync_threshold", &value)) + config.resyncthreshold = value; + + /* Get the verbosity setting. */ + if (config_lookup_int(config.cfg, "general.log_verbosity", &value)) + 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 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\""); + } + + /* Get the default latency. */ + if (config_lookup_int(config.cfg, "latencies.default", &value)) + config.latency = value; + + /* Get the itunes latency. */ + if (config_lookup_int(config.cfg, "latencies.itunes", &value)) + config.iTunesLatency = value; + + /* Get the AirPlay latency. */ + if (config_lookup_int(config.cfg, "latencies.airplay", &value)) + config.AirPlayLatency = value; + + /* Get the forkedDaapd latency. */ + if (config_lookup_int(config.cfg, "latencies.forkedDaapd", &value)) + config.ForkedDaapdLatency = value; + + #ifdef CONFIG_METADATA + /* Get the metadata setting. */ + if (config_lookup_string(config.cfg, "metadata.enabled", &str)) { + if (strcasecmp(str, "no") == 0) + config.metadata_enabled = 0; + else if (strcasecmp(str, "yes") == 0) + config.metadata_enabled = 1; + else + die("Invalid metadata enabled option choice \"%s\". It should be \"yes\" or \"no\""); + } + + if (config_lookup_string(config.cfg, "metadata.include_cover_art", &str)) { + if (strcasecmp(str, "no") == 0) + config.get_coverart = 0; + else if (strcasecmp(str, "yes") == 0) + config.get_coverart = 1; + else + die("Invalid metadata include_cover_art option choice \"%s\". It should be \"yes\" or " + "\"no\""); + } + + if (config_lookup_string(config.cfg, "metadata.pipe_name", &str)) { + config.metadata_pipename = (char *)str; + } + #endif + + if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_play_begins", &str)) { + config.cmd_start = (char *)str; + } + + if (config_lookup_string(config.cfg, "sessioncontrol.run_this_after_play_ends", &str)) { + config.cmd_stop = (char *)str; + } + + if (config_lookup_string(config.cfg, "sessioncontrol.wait_for_completion", &str)) { + if (strcasecmp(str, "no") == 0) + config.cmd_blocking = 0; + else if (strcasecmp(str, "yes") == 0) + config.cmd_blocking = 1; + else + die("Invalid session control wait_for_completion option choice \"%s\". It should be " + "\"yes\" or \"no\""); + } + + if (config_lookup_string(config.cfg, "sessioncontrol.allow_session_interruption", &str)) { + config.dont_check_timeout = 0; // this is for legacy -- only set by -t 0 + if (strcasecmp(str, "no") == 0) + config.allow_session_interruption = 0; + else if (strcasecmp(str, "yes") == 0) + config.allow_session_interruption = 1; + else + die("Invalid session control allow_interruption option choice \"%s\". It should be \"yes\" " + "or \"no\""); + } + + if (config_lookup_int(config.cfg, "sessioncontrol.session_timeout", &value)) { + config.timeout = value; + config.dont_check_timeout = 0; // this is for legacy -- only set by -t 0 + } + + } else { + if (config_error_type(&config_file_stuff) == CONFIG_ERR_FILE_IO) + debug(1, "Error reading configuration file \"%s\": \"%s\".", + config_error_file(&config_file_stuff), config_error_text(&config_file_stuff)); + else { + die("Line %d of the configuration file \"%s\":\n%s", config_error_line(&config_file_stuff), config_error_file(&config_file_stuff), config_error_text(&config_file_stuff)); - else { - die("Line %d of the configuration file \"%s\":\n%s", config_error_line(&config_file_stuff), - config_error_file(&config_file_stuff), config_error_text(&config_file_stuff)); + } } + free(config_file_real_path); } // now, do the command line options again, but this time do them fully -- it's a unix convention that command line @@ -820,7 +843,14 @@ int main(int argc, char **argv) { debug(2, "audio backend desired buffer length is %d.", config.audio_backend_buffer_desired_length); debug(2, "audio backend latency offset is %d.", config.audio_backend_latency_offset); - debug(2, "Configuration file name \"%s\".", config.configfile); + + char *realConfigPath = realpath(config.configfile,NULL); + if (realConfigPath) { + debug(2, "configuration file name \"%s\" resolves to \"%s\".", config.configfile,realConfigPath); + free(realConfigPath); + } else { + debug(2, "configuration file name \"%s\" can not be resolved.", config.configfile); + } #ifdef CONFIG_METADATA debug(2, "metdata enabled is %d.", config.metadata_enabled); debug(2, "metadata pipename is \"%s\".", config.metadata_pipename); diff --git a/var/shairport-sync.conf b/var/shairport-sync.conf new file mode 100644 index 00000000..93524e37 --- /dev/null +++ b/var/shairport-sync.conf @@ -0,0 +1,6 @@ +general = +{ + port = 8303; + udp_port_base = 8303; + udp_port_range = 10; +}; diff --git a/var/shairport-sync.conf~ b/var/shairport-sync.conf~ new file mode 100644 index 00000000..93524e37 --- /dev/null +++ b/var/shairport-sync.conf~ @@ -0,0 +1,6 @@ +general = +{ + port = 8303; + udp_port_base = 8303; + udp_port_range = 10; +}; From 55c11037a0e724ba02dbac3abe2efeb817dd6a71 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 6 Aug 2015 11:08:22 +0200 Subject: [PATCH 170/220] Un-include test files, duh. --- var/shairport-sync.conf | 6 ------ var/shairport-sync.conf~ | 6 ------ 2 files changed, 12 deletions(-) delete mode 100644 var/shairport-sync.conf delete mode 100644 var/shairport-sync.conf~ diff --git a/var/shairport-sync.conf b/var/shairport-sync.conf deleted file mode 100644 index 93524e37..00000000 --- a/var/shairport-sync.conf +++ /dev/null @@ -1,6 +0,0 @@ -general = -{ - port = 8303; - udp_port_base = 8303; - udp_port_range = 10; -}; diff --git a/var/shairport-sync.conf~ b/var/shairport-sync.conf~ deleted file mode 100644 index 93524e37..00000000 --- a/var/shairport-sync.conf~ +++ /dev/null @@ -1,6 +0,0 @@ -general = -{ - port = 8303; - udp_port_base = 8303; - udp_port_range = 10; -}; From bd5a89d4fbcc787a91206db49089860bcc41dd98 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 6 Aug 2015 12:03:54 +0200 Subject: [PATCH 171/220] Update RELEASENOTES.md --- RELEASENOTES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index fbdf268f..fb6066b2 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,9 @@ +Version 2.3.9 +---- +* Bug fix + * Specifying the configuration file using a *relative* file path now works properly. + * The debug verbosity requested with `-v`, `-vv`, etc. is now honoured before the configuration file is read. It is read and honoured from when the command line arguments are scanned the first time to get a possible configuration file path. + Version 2.3.8 ---- * Annoying changes you must make From fdbf64e9a6857235c41c905a75466dd7cc5e533d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 6 Aug 2015 14:29:58 +0200 Subject: [PATCH 172/220] Make output non-blocking; don't allow the pipe to be stdout --- audio_pipe.c | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/audio_pipe.c b/audio_pipe.c index 210f99f9..d38cd8f8 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -40,24 +40,32 @@ static int fd = -1; char *pipename = NULL; static void start(int sample_rate) { - debug(1, "Pipename to start is \"%s\"", pipename); - if (strcasecmp(pipename, "STDOUT") == 0) - fd = STDOUT_FILENO; - else - fd = open(pipename, O_WRONLY); + // this will leave fd as -1 if a reader hasn't been attached + fd = open(pipename, O_WRONLY | O_NONBLOCK); } -static void play(short buf[], int samples) { int ignore = write(fd, buf, samples * 4); } +static void play(short buf[], int samples) { + // if the file is not open, try to open it. + if (fd == -1) { + fd = open(pipename, O_WRONLY | O_NONBLOCK); + } + // if it's got a reader, write to it. + if (fd != -1) { + int ignore = non_blocking_write(fd, buf, samples * 4); + } +} static void stop(void) { - if (fd != STDOUT_FILENO) - close(fd); +// Don't close the pipe just because a play session has stopped. +// if (fd > 0) +// close(fd); } static int init(int argc, char **argv) { + debug(1, "pipe init"); const char *str; int value; - + config.audio_backend_buffer_desired_length = 44100; // one second. config.audio_backend_latency_offset = 0; @@ -67,6 +75,9 @@ static int init(int argc, char **argv) { if (config_lookup_string(config.cfg, "pipe.name", &str)) { pipename = (char *)str; } + + if ((pipename) && (strcasecmp(pipename, "STDOUT") == 0)) + die("Can't use \"pipe\" backend for STDOUT. Use the \"stdout\" backend instead."); /* Get the desired buffer size setting. */ if (config_lookup_int(config.cfg, "pipe.audio_backend_buffer_desired_length", &value)) { @@ -86,29 +97,25 @@ static int init(int argc, char **argv) { config.audio_backend_latency_offset = value; } } - + if ((pipename == NULL) && (argc != 1)) die("bad or missing argument(s) to pipe"); if (argc == 1) pipename = strdup(argv[0]); - + + // here, create the pipe - if (strcasecmp(pipename, "STDOUT") != 0) - if (mkfifo(pipename, 0644) && errno != EEXIST) - die("Could not create output pipe \"%s\"", pipename); + if (mkfifo(pipename, 0644) && errno != EEXIST) + die("Could not create output pipe \"%s\"", pipename); debug(1, "Pipename is \"%s\"", pipename); - // test open pipe so we error on startup if it's going to fail - start(44100); - stop(); - return 0; } static void deinit(void) { - if ((fd > 0) && (fd != STDOUT_FILENO)) + if (fd > 0) close(fd); } From 1b52e26f8fdc03a8dabd2934a10381bab3194e44 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 6 Aug 2015 14:30:44 +0200 Subject: [PATCH 173/220] Move non-blocking-write code here from rtsp.c --- common.c | 26 ++++++++++++++++++++++++++ common.h | 2 ++ 2 files changed, 28 insertions(+) diff --git a/common.c b/common.c index fb8702e0..bb6dafa2 100644 --- a/common.c +++ b/common.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -510,3 +511,28 @@ uint64_t get_absolute_time_in_fp() { #endif return time_now_fp; } + +ssize_t non_blocking_write(int fd, const void *buf, size_t count) { + // debug(1,"writing %u to pipe...",count); + // we are assuming that the count is always smaller than the FIFO's buffer + struct pollfd ufds[1]; + ssize_t reply; + do { + ufds[0].fd = fd; + ufds[0].events = POLLOUT; + int rv = poll(ufds, 1, 5000); + if (rv == -1) + debug(1, "error waiting for pipe to unblock..."); + if (rv == 0) + debug(1, "timeout waiting for pipe to unblock"); + reply = write(fd, buf, count); + if ((reply == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) + debug(1, "writing to pipe will block..."); + // else + // debug(1,"writing %u to pipe done...",reply); + } while ((reply == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))); + return reply; + + // return write(fd,buf,count); +} + diff --git a/common.h b/common.h index 2ed250a2..4a6c44f0 100644 --- a/common.h +++ b/common.h @@ -90,6 +90,8 @@ int get_requested_connection_state_to_output(); void set_requested_connection_state_to_output(int v); +ssize_t non_blocking_write(int fd, const void *buf, size_t count); // used in a few places + int debuglev; void die(char *format, ...); void warn(char *format, ...); From 84307f6beb7ae1b6d0da6a0f1da10b07c5e8899a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 6 Aug 2015 14:31:40 +0200 Subject: [PATCH 174/220] Move non-blocking-write code from here to common --- rtsp.c | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/rtsp.c b/rtsp.c index 1a81b98f..9340dbfb 100644 --- a/rtsp.c +++ b/rtsp.c @@ -922,30 +922,6 @@ static void metadata_close(void) { fd = -1; } -ssize_t non_blocking_write(int fd, const void *buf, size_t count) { - // debug(1,"writing %u to pipe...",count); - // we are assuming that the count is always smaller than the FIFO's buffer - struct pollfd ufds[1]; - ssize_t reply; - do { - ufds[0].fd = fd; - ufds[0].events = POLLOUT; - int rv = poll(ufds, 1, 5000); - if (rv == -1) - debug(1, "error waiting for pipe to unblock..."); - if (rv == 0) - debug(1, "timeout waiting for pipe to unblock"); - reply = write(fd, buf, count); - if ((reply == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) - debug(1, "writing to pipe will block..."); - // else - // debug(1,"writing %u to pipe done...",reply); - } while ((reply == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))); - return reply; - - // return write(fd,buf,count); -} - void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) { debug(2, "Process metadata with type %x, code %x and length %u.", type, code, length); int ret; From 60e5f18165fb05e67a20409b3300a47292ac9467 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 6 Aug 2015 14:32:56 +0200 Subject: [PATCH 175/220] Bump bugfix number --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 8824e600..b31cbb07 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.9], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.10], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 586f4e72136088b81a215049420c225349bdbb20 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 6 Aug 2015 15:17:21 +0200 Subject: [PATCH 176/220] Update RELEASENOTES.md --- RELEASENOTES.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index fb6066b2..717f12cd 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,12 @@ +Version 2.3.10 +---- +* Bug fix + * The "pipe" backend used output code that would block if the pipe didn't have a reader. This has been replaced by non-blocking code. Here are some implications: + * When the pipe is created, Shairport Sync will not block if a reader isn't present. + * If the pipe doesn't have a reader when Shairport Sync wants to output to it, the output will be discarded. + * If a reader disappears while writing is occuring, the write will time out after five seconds. + * Shairport Sync will only close the pipe on termination. + Version 2.3.9 ---- * Bug fix From 96a059079a7cf9e020de7059f8e73e8a210897f9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 6 Aug 2015 15:54:05 +0200 Subject: [PATCH 177/220] Begin to add documentation about the settins in the configuration file. --- man/shairport-sync.7 | 150 ++++++++++++++++++++++++++- man/shairport-sync.7.xml | 212 +++++++++++++++++++++++++++++++++++++- man/shairport-sync.html | 214 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 572 insertions(+), 4 deletions(-) diff --git a/man/shairport-sync.7 b/man/shairport-sync.7 index d7c05777..a9302b7d 100644 --- a/man/shairport-sync.7 +++ b/man/shairport-sync.7 @@ -2,7 +2,7 @@ .SH NAME shairport-sync \- Synchronised Audio Player for iTunes / AirPlay .SH SYNOPSIS -\fBshairport-sync [-dvw]\fB [-a \fB\fIname\fB]\fB [-A \fB\fIlatency\fB]\fB [-B \fB\fIcommand\fB]\fB [-E \fB\fIcommand\fB]\fB [--forkedDaapdLatency=\fB\fIlatency\fB]\fB [--get-cover-art]\fB [-i \fB\fIlatency\fB]\fB [-L \fB\fIlatency\fB]\fB [-m \fB\fIbackend\fB]\fB [--meta-dir=\fB\fIdirectory\fB]\fB [-o \fB\fIbackend\fB]\fB [--password=\fB\fIsecret\fB]\fB [-r \fB\fIthreshold\fB]\fB [--statistics]\fB [-S \fB\fImode\fB]\fB [-t \fB\fItimeout\fB]\fB [--tolerance=\fB\fIframes\fB]\fB [-- \fB\fIaudio_backend_options\fB]\fB +\fBshairport-sync [-dvw]\fB [-a \fB\fIname\fB]\fB [-A \fB\fIlatency\fB]\fB [-B \fB\fIcommand\fB]\fB [-c \fB\fIconfigurationfile\fB]\fB [-E \fB\fIcommand\fB]\fB [--forkedDaapdLatency=\fB\fIlatency\fB]\fB [--get-cover-art]\fB [-i \fB\fIlatency\fB]\fB [-L \fB\fIlatency\fB]\fB [-m \fB\fIbackend\fB]\fB [--meta-dir=\fB\fIdirectory\fB]\fB [-o \fB\fIbackend\fB]\fB [--password=\fB\fIsecret\fB]\fB [-r \fB\fIthreshold\fB]\fB [--statistics]\fB [-S \fB\fImode\fB]\fB [-t \fB\fItimeout\fB]\fB [--tolerance=\fB\fIframes\fB]\fB [-- \fB\fIaudio_backend_options\fB]\fB shairport-sync -D\fB @@ -20,6 +20,149 @@ shairport-sync -V\fB shairport-sync plays audio streamed from iTunes or from an AirPlay device to an audio device connected via an audio back end. At present, the only fully-implemented back end is for ALSA. A feature of shairport-sync is that the audio is played synchronously. This means that if many devices are playing the same stream at the same time, all the outputs will stay in step with one another. This allows multiple devices play the same source without getting out of phase with one another, enabling, for example, simultaneous multi-room operation. + +Settings can be made using the configuration file (recommended for all new installations) or the by using command-line options. +.SH CONFIGURATION FILE SETTINGS +You should use the configuration file for setting up shairport-sync. This file is normally \fI/etc/shairport-sync.conf\f1. You may need to have root privileges to modify it. + +Settings are organised into groups, for example, there is a "general" group of standard settings, and there is an "alsa" group with settings that pertain to the ALSA back end. + +Most of the settings have sensible default values, so most users will only need to set (1) the service name, (2) a password (if desired) and (3) the output device. If the output device has a hardware mixer that can be used for volume control, then (4) the fact that the output mixer is hardware and (5) the output device mixer's volume-control name should be specificed. It is highly desirable to use the hardware mixer for volume control, if available -- response time is reduced to zero and the processor load is reduced. + +A sample configuration file, with all settings commented out and with some usage notes, is installed at \fI/etc/shairport-sync.conf.sample\f1. + +To retain backwards compatability with previous versions of shairport-sync you can use still use command line options, but any new features, etc. will be available only via configuration file settings. + +The configuration file is processed using the \fIlibconfig\f1 library -- see \fBhttp://www.hyperrealm.com/libconfig/libconfig_manual.html\f1. +.TP +\fB"GENERAL" SETTINGS\f1 +These are the settings available within the \fBgeneral\f1 group: +.TP +\fBname=\f1\fI"service_name"\f1\fB;\f1 +Use this \fIservice_name\f1 to identify this player in iTunes, etc. The default name is "Shairport Sync on ". +.TP +\fBmdns_backend=\f1\fI"avahi"\f1\fB;\f1 +Not used, not tested +.TP +\fBoutput_backend=\f1\fI"alsa"\f1\fB;\f1 +Default is "alsa"; other possibilities are "stdout", "pulse", "dummy". Only alsa supports synchronisation. +.TP +\fBport=\f1\fI5000\f1\fB;\f1 +This is the port on which Shairport Sync listens for service requests. +.TP +\fBudp_port_base=\f1\fI6001\f1\fB;\f1 +Allocation of UDP ports starts from this port number. +.TP +\fBudp_port_range=\f1\fI100\f1\fB;\f1 +Look for free ports in this number of places, starting at the UDP port base. Only three ports are needed. +.TP +\fBpassword=\f1\fI"secret"\f1\fB;\f1 +The default is that no password is needed. +.TP +\fBinterpolation=\f1\fI"basic"\f1\fB;\f1 +Interpolation method, also known as the "stuffing" method. The default is "basic", alternative is "soxr". +.TP +\fBstatistics=\f1\fI"no"\f1\fB;\f1 +Log statistics from time to time. +.TP +\fBdrift=\f1\fI88\f1\fB;\f1 +Allow synchronisation to drift by this number of frames before trying to correct it. +.TP +\fBresync_threshold=\f1\fI2205\f1\fB;\f1 +A synchronisation error greater than this will cause audio to be muted while Shairport Sync attempts to resynchrone with the audio feed; 0 disables it. +.TP +\fBlog_verbosity=\f1\fI0\f1\fB;\f1 +"0" means no verbosity, "3" is most verbose. +.TP +\fBignore_volume_control=\f1\fI"no"\f1\fB;\f1 +Set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. +.TP +\fB"LATENCIES" SETTINGS\f1 +These are the settings available within the \fBlatencies\f1 group: +.TP +\fBdefault=\f1\fI88200\f1\fB;\f1 +Comment Here +.TP +\fBitunes=\f1\fI99400\f1\fB;\f1 +Comment Here +.TP +\fBairplay=\f1\fI88200\f1\fB;\f1 +Comment Here +.TP +\fBforkedDaapd=\f1\fI99400\f1\fB;\f1 +Comment Here +.TP +\fB"METADATA" SETTINGS\f1 +These are the settings available within the \fBmetadata\f1 group: +.TP +\fBenabled=\f1\fI"no"\f1\fB;\f1 +Comment Here +.TP +\fBinclude_cover_art=\f1\fI"no"\f1\fB;\f1 +Comment Here +.TP +\fBpipe_name=\f1\fI"/tmp/shairport-sync-metadata"\f1\fB;\f1 +Comment Here +.TP +\fB"SESSIONCONTROL" SETTINGS\f1 +These are the settings available within the \fBsessioncontrol\f1 group: +.TP +\fBrun_this_before_play_begins=\f1\fI"/path/to/application and args"\f1\fB;\f1 +Comment Here +.TP +\fBrun_this_after_play_ends=\f1\fI"/path/to/application and args"\f1\fB;\f1 +Comment Here +.TP +\fBwait_for_completion=\f1\fI"no"\f1\fB;\f1 +Comment Here +.TP +\fBallow_session_interruption=\f1\fI"no"\f1\fB;\f1 +Comment Here +.TP +\fBsession_timeout=\f1\fI120\f1\fB;\f1 +Comment Here +.TP +\fB"ALSA" SETTINGS\f1 +These are the settings available within the \fBalsa\f1 group: +.TP +\fBoutput_device=\f1\fI"default"\f1\fB;\f1 +Comment Here +.TP +\fBmixer_type=\f1\fI"software"\f1\fB;\f1 +"software" or "hardware" +.TP +\fBmixer_device=\f1\fI"default"\f1\fB;\f1 +actually, the mixer default is whatever the output_device is. Normally you wouldn't have to use this. +.TP +\fBmixer_control_name=\f1\fI"PCM"\f1\fB;\f1 +the name of the mixer to use -- there is no default. +.TP +\fBaudio_backend_latency_offset=\f1\fI0\f1\fB;\f1 +Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. +.TP +\fBaudio_backend_buffer_desired_length=\f1\fI6615\f1\fB;\f1 +If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. +.TP +\fB"PIPE" SETTINGS\f1 +These are the settings available within the \fBpipe\f1 group: +.TP +\fBaudio_backend_latency_offset=\f1\fI0\f1\fB;\f1 +Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. +.TP +\fBaudio_backend_buffer_desired_length=\f1\fI44100\f1\fB;\f1 +Comment Here +.TP +\fBname=\f1\fI"/path/to/pipe"\f1\fB;\f1 +there is no default pipe name for the output +.TP +\fB"STDOUT" SETTINGS\f1 +These are the settings available within the \fBstdout\f1 group: +.TP +\fBaudio_backend_latency_offset=\f1\fI0\f1\fB;\f1 +Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. +.TP +\fBaudio_backend_buffer_desired_length=\f1\fI44100\f1\fB;\f1 +Comment Here .SH OPTIONS Many of the options take sensible default values, so you can normally ignore most of them. See the EXAMPLES section for typical usages. @@ -38,6 +181,9 @@ Execute \fIprogram\f1 when playback is about to begin. Specify the full path to If you want shairport-sync to wait until the command has completed before starting to play, select the \fB-w\f1 option as well. .TP +\fB-c \f1\fIfilename\f1\fB | --configfile=\f1\fIfilename\f1 +Read configuration settings from \fIfilename\f1. The default is to read them from \fI/etc/shairport-sync.conf\f1. For information about configuration settings, see the "Configuration File Settings" section above. +.TP \fB-D | --disconnectFromOutput\f1 Disconnect the shairport-sync daemon from the output device and exit. (Requires that the daemon has written its PID to an agreed file -- see the \fB-d\f1 option). .TP @@ -70,7 +216,7 @@ Kill the shairport-sync daemon and exit. (Requires that the daemon has written i Use this to set the \fIdefault latency\f1, in frames, for audio coming from an unidentified source or from an iTunes Version 9 or earlier source. The standard value for the \fIdefault latency\f1 is 88,200 frames, where there are 44,100 frames to the second. .TP \fB--meta-dir=\f1\fIdirectory\f1 -Listen for metadata coming from the source and send it, along with metadata from shairport-sync itself, to a pipe called \fIshairport-sync-metadata\f1 in the \fIdirectory\f1 you specify. If you add the \fB--get-cover-art\f1 then cover art will be sent through the pipe too. See https://github.com/mikebrady/shairport-sync-metadata-reader for a sample metadata reader. +Listen for metadata coming from the source and send it, along with metadata from shairport-sync itself, to a pipe called \fIshairport-sync-metadata\f1 in the \fIdirectory\f1 you specify. If you add the \fB--get-cover-art\f1 then cover art will be sent through the pipe too. See \fBhttps://github.com/mikebrady/shairport-sync-metadata-reader\f1 for a sample metadata reader. .TP \fB-m \f1\fImdnsbackend\f1\fB | --mdns=\f1\fImdnsbackend\f1 Force the use of the specified mDNS backend to advertise the player on the network. The default is to try all mDNS backends until one works. diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml index 235eeb18..65f81b1e 100644 --- a/man/shairport-sync.7.xml +++ b/man/shairport-sync.7.xml @@ -39,6 +39,7 @@ [-a name] [-A latency] [-B command] + [-c configurationfile] [-E command] [--forkedDaapdLatency=latency] [--get-cover-art] @@ -74,8 +75,210 @@ This allows multiple devices play the same source without getting out of phase with one another, enabling, for example, simultaneous multi-room operation.

- + +

Settings can be made using the configuration file (recommended for all new installations) or the by using command-line options.

+ + +
+

You should use the configuration file for setting up shairport-sync. + This file is normally /etc/shairport-sync.conf. + You may need to have root privileges to modify it.

+ +

Settings are organised into groups, for example, there is a "general" group of + standard settings, and there is an "alsa" group with settings that pertain to the ALSA + back end.

+ +

Most of the settings have sensible default values, so most users will only need to set (1) the service name, (2) a password (if desired) and + (3) the output device. If the output device has a hardware mixer that can be used for volume control, then (4) the fact that the output mixer is hardware and (5) the output device mixer's volume-control name should be specificed. It is highly desirable to use the hardware mixer for volume control, if available -- response time is reduced to zero and the processor load is reduced.

+ +

A sample configuration file, with all settings commented out and with some usage notes, + is installed at /etc/shairport-sync.conf.sample.

+ +

To retain backwards compatability with previous versions of shairport-sync + you can use still use command line options, but any new features, etc. will + be available only via configuration file settings.

+ +

The configuration file is processed using the libconfig library + -- see .

+ + + These are the settings available within the general group: + + + + + + + + + + + + + + + + + + These are the settings available within the latencies group: + + + + + + + + These are the settings available within the metadata group: + + + + + + + + These are the settings available within the sessioncontrol group: + + + + + + + + + These are the settings available within the alsa group: + + + + + + + + + + These are the settings available within the pipe group: + + + + + + + These are the settings available within the stdout group: + + + +
@@ -122,6 +325,13 @@

+ + - These are the settings available within the latencies group: +

There are four default latency settings, chosen automatically. One latency matches the latency used by recent versions of iTunes when playing audio and another matches the latency used by so-called "AirPlay" devices, including iOS devices and iTunes and Quicktime Player when they are playing video. A third latency is used when the audio source is forked-daapd. The fourth latency is the default if no other latency is chosen and is used for older versions of iTunes.

+

forkedDaapd=latency;

+ This is the latency, in frames, used for forkedDaapd sources. Default is 99,400. + +

default=latency;

+ This is the latency, in frames, used when the source is unrecognised. Default is 88,200. + + - These are the settings available within the metadata group: +

These are the settings available within the metadata group:

-

These are the settings available within the metadata group:

+

Shairport Sync can process metadata provided by the source, such as Track Number, Album Name, cover art, etc. and can provide additional metadata such as volume level, pause/resume, etc. It provides the metadata to a pipe, by default /tmp/shairport-sync-metadata. To process metadata, Shairport Sync must have been compiled with metadata support included. The metadata group of settings allow you to enable metadata handling and to control certain aspects of it. These are the settings available within the metadata group:

+ + + + + - - - - - These are the settings available within the pipe group: +

These settings are for the PIPE backend, used to route audio to a named unix pipe. The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second, + stereo.

+

Use the name setting to set the name and location of the pipe.

+

There are two further settings affecting timing that might be useful if the pipe reader is, for example, + a program to play an audio stream such as aplay. The audio_backend_latency_offset affects precisely when the first audio packet is sent + and the audio_backend_buffer_desired_length setting affects the nominal output buffer size.

+

These are the settings available within the pipe group:

- - + + + + - These are the settings available within the stdout group: +

These settings are for the STDOUT backend, used to route audio to standard output ("stdout"). + The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second, stereo.

+

There are two settings affecting timing that might be useful if the stdout reader is, for example, + a program to play an audio stream such as aplay. The audio_backend_latency_offset affects precisely when the first audio packet is sent + and the audio_backend_buffer_desired_length setting affects the nominal output buffer size.

+

These are the settings available within the stdout group:

+ + diff --git a/man/shairport-sync.html b/man/shairport-sync.html index ad655522..8caa34be 100644 --- a/man/shairport-sync.html +++ b/man/shairport-sync.html @@ -197,16 +197,23 @@

"METADATA" SETTINGS

-

Shairport Sync can process metadata provided by the source, such as Track Number, Album Name, cover art, etc. and can provide additional metadata such as volume level, pause/resume, etc. It provides the metadata to a pipe, by default /tmp/shairport-sync-metadata. To process metadata, Shairport Sync must have been compiled with metadata support included. You can check tht this is so by running shairport-sync -V; the identification string will contain the word metadata.

The metadata group of settings allow you to enable metadata handling and to control certain aspects of it:

+

Shairport Sync can process metadata provided by the source, such as Track Number, Album Name, cover art, etc. and can provide additional metadata such as volume level, + pause/resume, etc. It provides the metadata to a pipe, by default /tmp/shairport-sync-metadata. + To process metadata, Shairport Sync must have been compiled with metadata support included. + You can check tht this is so by running shairport-sync -V; the identification string will contain the word metadata.

+

The metadata group of settings allow you to enable metadata handling and to control certain aspects of it:

enabled="choice";

- Set the choice to "yes" to enable Shairport Sync to look for metadata from the audio source and to forward it, along with metadata generated by Shairport Sync itself, to the metadata pipe. The default is "no". + Set the choice to "yes" to enable Shairport Sync to look for metadata from the audio source and to forward it, + along with metadata generated by Shairport Sync itself, to the metadata pipe. The default is "no".

include_cover_art="choice";

- Set the choice to "yes" to enable Shairport Sync to look for cover art from the audio source and to forward it to the metadata pipe. A reason for not enabling cover art is that the images can sometimes be very large and mey delay transmission of subsequent metadata through the pipe. The default is "no". + Set the choice to "yes" to enable Shairport Sync to look for cover art from the audio source and to forward it to the metadata pipe. + A reason for not enabling cover art is that the images can sometimes be very large and mey delay transmission of subsequent metadata through the pipe. + The default is "no".

pipe_name="filepathname";

@@ -214,7 +221,8 @@

"SESSIONCONTROL" SETTINGS

-

Shairport Sync can run programs just before it starts to play an audio stream and just after it finishes. You specify them using the sessioncontrol group settings run_this_before_play_begins and run_this_after_play_ends.

+

Shairport Sync can run programs just before it starts to play an audio stream and just after it finishes. + You specify them using the sessioncontrol group settings run_this_before_play_begins and run_this_after_play_ends.

@@ -229,72 +237,121 @@

wait_for_completion="choice";

- Set choice to "yes" to make Shairport Sync wait until the programs specified in the run_this_before_play_begins and run_this_after_play_ends= have completed execution before continuing. The default is "no". + Set choice to "yes" to make Shairport Sync wait until the programs specified in the run_this_before_play_begins + and run_this_after_play_ends have completed execution before continuing. The default is "no". -

allow_session_interruption="no";

- Comment Here +

allow_session_interruption="choice";

+ If choice is set to "yes", then another source will be able to interrupt an existing play session and start a new one. + When set to "no" (the default), other devices attempting to interrupt a session will fail, receiving a busy signal. -

session_timeout=120;

- Comment Here +

session_timeout=seconds;

+ If a play session has been established and the source disappears without warning (such as a device going out of range of a network) + then wait for seconds seconds before ending the session. Once the session has terminated, other devices can use it. + The default is 120 seconds.

"ALSA" SETTINGS

- These are the settings available within the alsa group: +

These settings are for the ALSA back end, used used to communicate with audio output devices in the ALSA system. + (By the way, you can use tools such as alsamixer or aplay to discover what devices are available.) + Use these settings to select the output device and the mixer control to be used to control the output volume. + You can additionally set the desired size of the output buffer and you can adjust overall latency. Here are the alsa group settings:

-

output_device="default";

- Comment Here +

output_device="output_device";

+ Use the output device called output_device. The default is the device called "device". -

mixer_type="software";

- "software" or "hardware" + +

mixer_control_name="name";

+ Specify the name of the mixer control to be used by shairport-sync to control the volume. + The mixer control must be on the mixer device, which by default is the output device. + If you do not specify a mixer control name, shaiport-sync will control the volume in software. + +

mixer_type="mixer_type";

+ This setting is deprecated and will be removed soon. If you wish to use a mixer control to control the volume, then set mixer_type to "hardware". + The default is "software". -

mixer_device="default";

- actually, the mixer default is whatever the output_device is. Normally you wouldn't have to use this. +

mixer_device="mixer_device";

+ By default, the mixer is assumed to be output_device is. Use this setting to specify a device other than the output device. -

mixer_control_name="PCM";

- the name of the mixer to use -- there is no default. +

audio_backend_latency_offset=offset;

+ Set this offset, in frames, to compensate for a fixed delay in the audio back end. + For example, if the output device delays by 100 ms, set this to -4410. -

audio_backend_latency_offset=0;

- Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. - - -

audio_backend_buffer_desired_length=6615;

- If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. +

audio_backend_buffer_desired_length=length;

+ Use this to set the desired number frames to be in the output device's hardware output buffer. + The default is 6,615 frames, or 0.15 seconds. If set too small, buffer underflow may occur on low-powered machines. + If too large, the response times when using a software mixer become annoying, or it may exceed the hardware buffer size. + It may need to be larger on low-powered machines performing other tasks, such as processing metadata.

"PIPE" SETTINGS

- These are the settings available within the pipe group: +

These settings are for the PIPE backend, used to route audio to a named unix pipe. The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second, + stereo.

+

Use the name setting to set the name and location of the pipe.

+

There are two further settings affecting timing that might be useful if the pipe reader is, for example, + a program to play an audio stream such as aplay. The audio_backend_latency_offset affects precisely when the first audio packet is sent + and the audio_backend_buffer_desired_length setting affects the nominal output buffer size.

+

These are the settings available within the pipe group:

-

audio_backend_latency_offset=0;

- Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. - - -

audio_backend_buffer_desired_length=44100;

- Comment Here - -

name="/path/to/pipe";

- there is no default pipe name for the output + Use this to specify the name and location of the pipe. The pipe will be created and opened when Shairport Sync starts up and will be closed upon shutdown. + Frames of audio will be sent to the pipe in packets of 352 frames and will be discarded if the pipe has not have a reader attached. + The sender will wait for up to five seconds for a packet to be written before discarding it. + + + +

audio_backend_latency_offset=offset_in_frames;

+ + Packets of audio frames are written to the pipe synchronously -- that is, they are written to at exactly the time they should be played. + You can offset the time of initial audio output relative to its nominal time using this setting. + For example to send an audio stream to the pipe 100 milliseconds before it is due to be played, set this to -4410. + + + +

audio_backend_buffer_desired_length=buffer_length_in_frames;

+ + Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to the pipe. + For example, if you send the first packet of audio exactly when it is due and, using a audio_backend_buffer_desired_length setting of 44100, + send subsequent packets of audio a second before they are due to be played, they will be buffered in the pipe reader's buffer, giving it a nominal buffer size of 44,100 frames. + Note that if the pipe reader consumes audio faster or slower than they are supplied, the buffer will empty or overflow eventually -- + Shairport Sync performs no stuffing or interpolation when writing to a pipe. +

"STDOUT" SETTINGS

- These are the settings available within the stdout group: +

These settings are for the STDOUT backend, used to route audio to standard output ("stdout"). + The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second, stereo.

+

There are two settings affecting timing that might be useful if the stdout reader is, for example, + a program to play an audio stream such as aplay. The audio_backend_latency_offset affects precisely when the first audio packet is sent + and the audio_backend_buffer_desired_length setting affects the nominal output buffer size.

+

These are the settings available within the stdout group:

-

audio_backend_latency_offset=0;

- Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. +

audio_backend_latency_offset=offset_in_frames;

+ + Packets of audio frames are written to stdout synchronously -- that is, they are written at exactly the time they should be played. + You can offset the time of initial audio output relative to its nominal time using this setting. + For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -4410. -

audio_backend_buffer_desired_length=44100;

- Comment Here +

audio_backend_buffer_desired_length=buffer_length_in_frames;

+ + Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to stdout. + For example, if you send the first packet of audio exactly when it is due and, using a audio_backend_buffer_desired_length setting of 44100, + send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 44,100 frames. + Note that if the stdout reader consumes audio faster or slower than they are supplied, the buffer will empty or overflow eventually -- + Shairport Sync performs no stuffing or interpolation when writing to stdout. + + + From 3016ed33bfa2f5ed2a4110dbdf5014f2df593d1a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 18 Aug 2015 08:19:36 -0400 Subject: [PATCH 191/220] Small typo fixes --- man/shairport-sync.7 | 10 +++++----- man/shairport-sync.7.xml | 10 +++++----- man/shairport-sync.html | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/man/shairport-sync.7 b/man/shairport-sync.7 index b2737a4d..6301ed32 100644 --- a/man/shairport-sync.7 +++ b/man/shairport-sync.7 @@ -152,7 +152,7 @@ If a play session has been established and the source disappears without warning These settings are for the ALSA back end, used used to communicate with audio output devices in the ALSA system. (By the way, you can use tools such as \fBalsamixer\f1 or \fBaplay\f1 to discover what devices are available.) Use these settings to select the output device and the mixer control to be used to control the output volume. You can additionally set the desired size of the output buffer and you can adjust overall latency. Here are the \fBalsa\f1 group settings: .TP \fBoutput_device=\f1\fI"output_device"\f1\fB;\f1 -Use the output device called \fIoutput_device\f1. The default is the device called "device". +Use the output device called \fIoutput_device\f1. The default is the device called "default". .TP .TP \fBmixer_control_name=\f1\fI"name"\f1\fB;\f1 @@ -181,10 +181,10 @@ These are the settings available within the \fBpipe\f1 group: Use this to specify the name and location of the pipe. The pipe will be created and opened when Shairport Sync starts up and will be closed upon shutdown. Frames of audio will be sent to the pipe in packets of 352 frames and will be discarded if the pipe has not have a reader attached. The sender will wait for up to five seconds for a packet to be written before discarding it. .TP \fBaudio_backend_latency_offset=\f1\fIoffset_in_frames\f1\fB;\f1 -Packets of audio frames are written to the pipe synchronously -- that is, they are written to at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to the pipe 100 milliseconds before it is due to be played, set this to -4410. +Packets of audio frames are written to the pipe synchronously -- that is, they are written to at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to the pipe 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0. .TP \fBaudio_backend_buffer_desired_length=\f1\fIbuffer_length_in_frames\f1\fB;\f1 -Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to the pipe. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length\f1 setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the pipe reader's buffer, giving it a nominal buffer size of 44,100 frames. Note that if the pipe reader consumes audio faster or slower than they are supplied, the buffer will empty or overflow eventually -- Shairport Sync performs no stuffing or interpolation when writing to a pipe. +Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to the pipe. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length\f1 setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the pipe reader's buffer, giving it a nominal buffer size of 44,100 frames. Note that if the pipe reader consumes audio faster or slower than they are supplied, the buffer will empty or overflow eventually -- Shairport Sync performs no stuffing or interpolation when writing to a pipe. Default setting is 44,100. .TP \fB"STDOUT" SETTINGS\f1 These settings are for the STDOUT backend, used to route audio to standard output ("stdout"). The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second, stereo. @@ -194,10 +194,10 @@ There are two settings affecting timing that might be useful if the stdout reade These are the settings available within the \fBstdout\f1 group: .TP \fBaudio_backend_latency_offset=\f1\fIoffset_in_frames\f1\fB;\f1 -Packets of audio frames are written to stdout synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -4410. +Packets of audio frames are written to stdout synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0. .TP \fBaudio_backend_buffer_desired_length=\f1\fIbuffer_length_in_frames\f1\fB;\f1 -Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to stdout. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length\f1 setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 44,100 frames. Note that if the stdout reader consumes audio faster or slower than they are supplied, the buffer will empty or overflow eventually -- Shairport Sync performs no stuffing or interpolation when writing to stdout. +Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to stdout. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length\f1 setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 44,100 frames. Note that if the stdout reader consumes audio faster or slower than they are supplied, the buffer will empty or overflow eventually -- Shairport Sync performs no stuffing or interpolation when writing to stdout. Default setting is 44,100. .SH OPTIONS Many of the options take sensible default values, so you can normally ignore most of them. See the EXAMPLES section for typical usages. diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml index 38b32898..79023243 100644 --- a/man/shairport-sync.7.xml +++ b/man/shairport-sync.7.xml @@ -278,7 +278,7 @@ @@ -356,7 +356,7 @@ Packets of audio frames are written to stdout synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. - For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -4410. + For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0. diff --git a/man/shairport-sync.html b/man/shairport-sync.html index 8caa34be..a936dd53 100644 --- a/man/shairport-sync.html +++ b/man/shairport-sync.html @@ -260,7 +260,7 @@

output_device="output_device";

- Use the output device called output_device. The default is the device called "device". + Use the output device called output_device. The default is the device called "default". @@ -311,7 +311,7 @@ Packets of audio frames are written to the pipe synchronously -- that is, they are written to at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. - For example to send an audio stream to the pipe 100 milliseconds before it is due to be played, set this to -4410. + For example to send an audio stream to the pipe 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0. @@ -321,7 +321,7 @@ For example, if you send the first packet of audio exactly when it is due and, using a audio_backend_buffer_desired_length setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the pipe reader's buffer, giving it a nominal buffer size of 44,100 frames. Note that if the pipe reader consumes audio faster or slower than they are supplied, the buffer will empty or overflow eventually -- - Shairport Sync performs no stuffing or interpolation when writing to a pipe. + Shairport Sync performs no stuffing or interpolation when writing to a pipe. Default setting is 44,100. @@ -338,7 +338,7 @@ Packets of audio frames are written to stdout synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. - For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -4410. + For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0. @@ -348,7 +348,7 @@ For example, if you send the first packet of audio exactly when it is due and, using a audio_backend_buffer_desired_length setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 44,100 frames. Note that if the stdout reader consumes audio faster or slower than they are supplied, the buffer will empty or overflow eventually -- - Shairport Sync performs no stuffing or interpolation when writing to stdout. + Shairport Sync performs no stuffing or interpolation when writing to stdout. Default setting is 44,100. From bc7aadc53e0f8d3b0f65d7f35c9d8b9cc6d584b8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 20 Aug 2015 10:09:50 -0400 Subject: [PATCH 192/220] Drop the update-rc.d from the make install script --- Makefile.am | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 0a4239a0..fcf68059 100644 --- a/Makefile.am +++ b/Makefile.am @@ -59,7 +59,6 @@ endif if INSTALL_SYSTEMV [ -e $(DESTDIR)/etc/init.d ] || mkdir -p $(DESTDIR)/etc/init.d [ -f $(DESTDIR)/etc/init.d/shairport-sync ] || cp scripts/shairport-sync $(DESTDIR)/etc/init.d/ - update-rc.d shairport-sync defaults 90 10 endif if INSTALL_SYSTEMD [ -e $(DESTDIR)/usr/lib/systemd/system ] || mkdir -p $(DESTDIR)/usr/lib/systemd/system From dc81e7d5929aaaed4bc9a38713f236a1a7259af0 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 20 Aug 2015 10:10:53 -0400 Subject: [PATCH 193/220] Bump version --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index e7dd927a..85ac9dfa 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.11], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.12], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From f8f7a3f11dda4214096f21e6d82c8e7e3d0905d5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 20 Aug 2015 10:11:43 -0400 Subject: [PATCH 194/220] Remove mixer_type stuff and edit and reorganise settings and comments --- scripts/shairport-sync.conf | 59 ++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 3bfcde5b..b2ac51a5 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -5,74 +5,73 @@ general = { // name = "Shairport Sync Player"; // This is the name the service will advertise to iTunes. The default is "Shairport Sync on " -// mdns_backend = "avahi"; // not used, not tested -// output_backend = "alsa"; // alsa is default, other possibilities are "stdout", "pulse", "dummy". Only alsa supports synchronisation. -// port = 5000; -// udp_port_base = 6001; // start allocation UDP ports from this port number +// password = "secret"; // comment out this line if you want to have no password +// interpolation = "basic"; // aka "stuffing". Default is "basic", alternative is "soxr". Use "soxr" only if you have a reasonably fast processor. +// output_backend = "alsa"; // Run "shairport-sync -h" to get a list of all output_backends, e.g. "alsa", "pipe", "stdout". The default is the first one. +// mdns_backend = "avahi"; // Run "shairport-sync -h" to get a list of all mdns_backends. The default is the first one. +// port = 5000; // Listen for service requests on this port +// udp_port_base = 6001; // start allocating UDP ports from this port number when needed // udp_port_range = 100; // look for free ports in this number of places, starting at the UDP port base (only three are needed). -// password = "secret"; // default is no password -// interpolation = "basic"; // aka "stuffing". Default is "basic", alternative is "soxr" -// statistics = "no"; // print statistics in the log -// drift = 88; // allow this number of frames of drift before correcting it +// statistics = "no"; // set to "yes" to print statistics in the log +// drift = 88; // allow this number of frames of drift away from exact synchronisation before attempting to correct it // resync_threshold = 2205; // a synchronisation error greater than this will cause resynchronisation; 0 disables it -// log_verbosity = 0; // "0" means no verbosity, "3" is most verbose. +// log_verbosity = 0; // "0" means no debug verbosity, "3" is most verbose. // ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. }; -// Latencies for different sources. +// Latencies for different sources. These have been estimated from listening tests. +// It's probably better to compensate for a delay in the output device using the alsa "audio_backend_latency_offset" setting -- see below. latencies = { -// default = 88200; -// itunes = 99400; +// default = 88200; // used for unrecognised sources and for iTunes up to and including iTunes 9.X. +// itunes = 99400; // used for iTunes 10 or later // airplay = 88200; // forkedDaapd = 99400; }; - // How to deal with metadata, including artwork metadata = { -// enabled = "no"; -// include_cover_art = "no"; +// enabled = "no"; // et to yes to get Shairport Sync to solicit metadata from the source and to pass it on via a pipe +// include_cover_art = "no"; // set to "yes" to get Shairport Sync to solicit cover art from the source and pass it via the pipe. You must also set "enabled" to "yes". // pipe_name = "/tmp/shairport-sync-metadata"; }; // Advanced parameters for controlling how a Shairport Sync runs sessioncontrol = { -// run_this_before_play_begins = "/path/to/application and args"; -// run_this_after_play_ends = "/path/to/application and args"; -// wait_for_completion = "no"; -// allow_session_interruption = "no"; -// session_timeout = 120; +// run_this_before_play_begins = "/full/path/to/application and args"; // make sure the application has executable permission. It it's a script, include the #!... stuff on the first line +// run_this_after_play_ends = "/full/path/to/application and args"; // make sure the application has executable permission. It it's a script, include the #!... stuff on the first line +// wait_for_completion = "no"; // set to "yes" to get Shairport Sync to wait until the "run_this..." applications have terminated before continuing +// allow_session_interruption = "no"; // set to "yes" to allow another device to interrupt Shairport Sync while it's playing from an existing audio source +// session_timeout = 120; // wait for this number of seconds after a source disappears before terminating the session and becoming available again. }; // // Back End Settings // -// These are parameters for the alsa back end, the only back end that supports synchronisation +// These are parameters for the "alsa" audio back end, the only back end that supports synchronised audio. alsa = { -// output_device = "default"; -// mixer_type = "software"; // "software" or "hardware" -// mixer_device = "default"; //actually, the mixer default is whatever the output_device is. Normally you wouldn't have to use this. -// mixer_control_name = "PCM"; // the name of the mixer to use -- there is no default. +// output_device = "default"; // the name of the alsa output device. Use "alsamixer" or "aplay" to find out the names of devices, mixers, etc. +// mixer_control_name = "PCM"; // the name of the mixer to use to adjust output volume. If not specified, volume in adjusted in software. +// mixer_device = "default"; // the mixer_device default is whatever the output_device is. Normally you wouldn't have to use this. // audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. // audio_backend_buffer_desired_length = 6615; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. }; -// These are parameters for the pipe back end, an experimental back end that directs output to a pipe. +// These are parameters for the "pipe" audio back end, a back end that directs raw CD-style audio output to a pipe. No interpolation is done. pipe = { -// audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. -// audio_backend_buffer_desired_length = 44100; // name = "/path/to/pipe"; // there is no default pipe name for the output +// audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. +// audio_backend_buffer_desired_length = 44100; // Having started to send audio at the right time, send all subsequent audio this many frames ahead of time, creating a buffer this size. }; -// These are parameters for the stdout audio back end, an experimental back end that directs output to stdout. +// These are parameters for the "stdout" audio back end, a back end that directs raw CD-style audio output to stdout. No interpolation is done. stdout = { // audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. -// audio_backend_buffer_desired_length = 44100; +// audio_backend_buffer_desired_length = 44100; // Having started to send audio at the right time, send all subsequent audio this many frames ahead of time, creating a buffer this size. }; From a258925eae6ced61cc6bc73d21e3ee8134943f51 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 20 Aug 2015 10:12:53 -0400 Subject: [PATCH 195/220] More edits and typo fixes and update references to mixer_type --- man/shairport-sync.7 | 60 +++++++++++++++-------------- man/shairport-sync.7.xml | 82 ++++++++++++++++++++-------------------- man/shairport-sync.html | 82 ++++++++++++++++++++-------------------- 3 files changed, 113 insertions(+), 111 deletions(-) diff --git a/man/shairport-sync.7 b/man/shairport-sync.7 index 6301ed32..1449c43d 100644 --- a/man/shairport-sync.7 +++ b/man/shairport-sync.7 @@ -17,11 +17,13 @@ shairport-sync -R\fB shairport-sync -V\fB \f1 .SH DESCRIPTION -shairport-sync plays audio streamed from iTunes or from an AirPlay device to an audio device connected via an audio back end. At present, the only fully-implemented back end is for ALSA. +shairport-sync plays audio streamed from iTunes or from an AirPlay device to an ALSA-compatible audio output device. A feature of shairport-sync is that the audio is played synchronously. This means that if many devices are playing the same stream at the same time, all the outputs will stay in step with one another. This allows multiple devices play the same source without getting out of phase with one another, enabling, for example, simultaneous multi-room operation. -Settings can be made using the configuration file (recommended for all new installations) or the by using command-line options. +shairport-sync can additionally be compiled and configured to stream raw audio to a pipe or to stdout. + +Settings can be made using the configuration file (recommended for all new installations) or by using command-line options. .SH CONFIGURATION FILE SETTINGS You should use the configuration file for setting up shairport-sync. This file is normally \fI/etc/shairport-sync.conf\f1. You may need to have root privileges to modify it. @@ -43,13 +45,11 @@ Settings are organised into groups, for example, there is a "general" group of s \fBoutput_device = "hw:0";\f1 -\fBmixer_type = "hardware";\f1 - \fBmixer_control_name = "PCM";\f1 \fB};\f1 -Most settings have sensible default values, so -- as in the example above -- users generally only need to set (1) the service name, (2) a password (if desired) and (3) the output device. If the output device has a hardware mixer that can be used for volume control, then (4) the fact that the output mixer is hardware and (5) the output device mixer's volume-control name should be specificed. It is highly desirable to use the hardware mixer for volume control, if available -- response time is reduced to zero and the processor load is reduced. In the example above, "soxr" interpolation was also enabled. +Most settings have sensible default values, so -- as in the example above -- users generally only need to set (1) the service name, (2) a password (if desired) and (3) the output device. If the output device has a mixer that can be used for volume control, then (4) the volume control's name should be specificed. It is highly desirable to use the output device's mixer for volume control, if available -- response time is reduced to zero and the processor load is reduced. In the example above, "soxr" interpolation was also enabled. A sample configuration file with all possible settings, but with all of them commented out, is installed at \fI/etc/shairport-sync.conf.sample\f1. @@ -73,25 +73,25 @@ Interpolate, or "stuff", the audio stream using the \fImode\f1. Interpolation he Use this \fIsetting\f1 to enable ("yes") or disable ("no") the output of some statistical information on the console or in the log. The default is to disable statistics. .TP \fBmdns_backend=\f1\fI"backend"\f1\fB;\f1 -Shairport Sync has a number of modules of code ("backends") for interacting with the mDNS service to be used to advertise itself. Normally, the first mDNS backend that works is selected. This setting forces the selection of the specific mDNS \fIbackend\f1. The default is "avahi". Perform the command \fBshairport-sync -h\f1 to get a list of available mDNS modules. +shairport-sync has a number of modules of code ("backends") for interacting with the mDNS service to be used to advertise itself. Normally, the first mDNS backend that works is selected. This setting forces the selection of the specific mDNS \fIbackend\f1. The default is "avahi". Perform the command \fBshairport-sync -h\f1 to get a list of available mDNS modules. .TP \fBoutput_backend=\f1\fI"backend"\f1\fB;\f1 -Shairport Sync has a number of modules of code ("backends") through which audio is output. Normally, the first audio backend that works is selected. This setting forces the selection of the specific audio \fIbackend\f1. The default is "alsa". Perform the command \fBshairport-sync -h\f1 to get a list of available audio backends. Only the alsa backend supports synchronisation. +shairport-sync has a number of modules of code ("backends") through which audio is output. Normally, the first audio backend that works is selected. This setting forces the selection of the specific audio \fIbackend\f1. The default is "alsa". Perform the command \fBshairport-sync -h\f1 to get a list of available audio backends. Only the alsa backend supports synchronisation. .TP \fBport=\f1\fIportnumber\f1\fB;\f1 -Use this to specify the \fIportnumber\f1 Shairport Sync uses to listen for service requests from iTunes, etc. The default is port 5000. +Use this to specify the \fIportnumber\f1 shairport-sync uses to listen for service requests from iTunes, etc. The default is port 5000. .TP -\fBudp_port_base=\f1\fI6001\f1\fB;\f1 -When Shairport Sync starts to play audio, it establises three UDP connections to the audio source. Use this setting to specify the starting \fIportnumber\f1 for these three ports. It will pick the first three unused ports starting from \fIportnumber\f1. The default is port 6001. +\fBudp_port_base=\f1\fIportnumber\f1\fB;\f1 +When shairport-sync starts to play audio, it establises three UDP connections to the audio source. Use this setting to specify the starting \fIportnumber\f1 for these three ports. It will pick the first three unused ports starting from \fIportnumber\f1. The default is port 6001. .TP -\fBudp_port_range=\f1\fI100\f1\fB;\f1 -Use this in conjunction with the prevous setting to specify the range of ports that can be checked for availability. Only three ports are needed. The default is 100, thus 100 ports will be checked from port 6001 upwards until three are found. +\fBudp_port_range=\f1\fIrange\f1\fB;\f1 +Use this in conjunction with the prevous setting to specify the \fIrange\f1 of ports that can be checked for availability. Only three ports are needed. The default is 100, thus 100 ports will be checked from port 6001 upwards until three are found. .TP \fBdrift=\f1\fIframes\f1\fB;\f1 Allow playback to drift up to \fIframes\f1 out of exact synchronization before attempting to correct it. The default is 88 frames, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the \fBstatistics\f1 setting to monitor correction levels. Corrections should not greatly exceed net corrections. .TP \fBresync_threshold=\f1\fIthreshold\f1\fB;\f1 -Resynchronise if timings differ by more than \fBthreshold\f1 frames. If the output timing differs from the source timing by more than the threshold, output will be muted and a full resynchronisation will occur. The default threshold is 2,205 frames, i.e. 50 milliseconds. Specify 0 to disable resynchronisation. +Resynchronise if timings differ by more than \fIthreshold\f1 frames. If the output timing differs from the source timing by more than the threshold, output will be muted and a full resynchronisation will occur. The default threshold is 2,205 frames, i.e. 50 milliseconds. Specify 0 to disable resynchronisation. .TP \fBlog_verbosity=\f1\fI0\f1\fB;\f1 Use this to specify how much debugging information should be output or logged. "0" means no debug information, "3" means most debug information. The default is "0". @@ -117,21 +117,21 @@ This is the \fIlatency\f1, in frames, used for forkedDaapd sources. Default is 9 This is the \fIlatency\f1, in frames, used when the source is unrecognised. Default is 88,200. .TP \fB"METADATA" SETTINGS\f1 -Shairport Sync can process metadata provided by the source, such as Track Number, Album Name, cover art, etc. and can provide additional metadata such as volume level, pause/resume, etc. It provides the metadata to a pipe, by default \fI/tmp/shairport-sync-metadata\f1. To process metadata, Shairport Sync must have been compiled with metadata support included. You can check tht this is so by running \fBshairport-sync -V\f1; the identification string will contain the word \fBmetadata\f1. +shairport-sync can process metadata provided by the source, such as Track Number, Album Name, cover art, etc. and can provide additional metadata such as volume level, pause/resume, etc. It provides the metadata to a pipe, by default \fI/tmp/shairport-sync-metadata\f1. To process metadata, shairport-sync must have been compiled with metadata support included. You can check that this is so by running \fBshairport-sync -V\f1; the identification string will contain the word \fBmetadata\f1. The \fBmetadata\f1 group of settings allow you to enable metadata handling and to control certain aspects of it: .TP \fBenabled=\f1\fI"choice"\f1\fB;\f1 -Set the \fIchoice\f1 to "yes" to enable Shairport Sync to look for metadata from the audio source and to forward it, along with metadata generated by Shairport Sync itself, to the metadata pipe. The default is "no". +Set the \fIchoice\f1 to "yes" to enable shairport-sync to look for metadata from the audio source and to forward it, along with metadata generated by shairport-sync itself, to the metadata pipe. The default is "no". .TP \fBinclude_cover_art=\f1\fI"choice"\f1\fB;\f1 -Set the \fIchoice\f1 to "yes" to enable Shairport Sync to look for cover art from the audio source and to forward it to the metadata pipe. A reason for not enabling cover art is that the images can sometimes be very large and mey delay transmission of subsequent metadata through the pipe. The default is "no". +Set the \fIchoice\f1 to "yes" to enable shairport-sync to look for cover art from the audio source and to include it in the feed to the metadata pipe. You must also enable metadata (see above). One reason for not including cover art is that the images can sometimes be very large and may delay transmission of subsequent metadata through the pipe. The default is "no". .TP \fBpipe_name=\f1\fI"filepathname"\f1\fB;\f1 Specify the absolute path name of the pipe through which metadata should be sent The default is \fI/tmp/shairport-sync-metadata\f1". .TP \fB"SESSIONCONTROL" SETTINGS\f1 -Shairport Sync can run programs just before it starts to play an audio stream and just after it finishes. You specify them using the sessioncontrol group settings run_this_before_play_begins and run_this_after_play_ends. +shairport-sync can run programs just before it starts to play an audio stream and just after it finishes. You specify them using the sessioncontrol group settings run_this_before_play_begins and run_this_after_play_ends. .TP \fBrun_this_before_play_begins=\f1\fI"/path/to/application and args"\f1\fB;\f1 Here you can specify a program and its arguments that will be run just before a play session begins. Be careful to include the full path to the application. The application must be marked as executable and, if it is a script, its first line must begin with the standard \fI#!/bin/...\f1 as appropriate. @@ -140,7 +140,7 @@ Here you can specify a program and its arguments that will be run just before a Here you can specify a program and its arguments that will be run just after a play session ends. Be careful to include the full path to the application. The application must be marked as executable and, if it is a script, its first line must begin with the standard \fI#!/bin/...\f1 as appropriate. .TP \fBwait_for_completion=\f1\fI"choice"\f1\fB;\f1 -Set \fIchoice\f1 to "yes" to make Shairport Sync wait until the programs specified in the \fBrun_this_before_play_begins\f1 and \fBrun_this_after_play_ends\f1 have completed execution before continuing. The default is "no". +Set \fIchoice\f1 to "yes" to make shairport-sync wait until the programs specified in the \fBrun_this_before_play_begins\f1 and \fBrun_this_after_play_ends\f1 have completed execution before continuing. The default is "no". .TP \fBallow_session_interruption=\f1\fI"choice"\f1\fB;\f1 If \fBchoice\f1 is set to "yes", then another source will be able to interrupt an existing play session and start a new one. When set to "no" (the default), other devices attempting to interrupt a session will fail, receiving a busy signal. @@ -149,24 +149,24 @@ If \fBchoice\f1 is set to "yes", then another source will be able to interrupt a If a play session has been established and the source disappears without warning (such as a device going out of range of a network) then wait for \fIseconds\f1 seconds before ending the session. Once the session has terminated, other devices can use it. The default is 120 seconds. .TP \fB"ALSA" SETTINGS\f1 -These settings are for the ALSA back end, used used to communicate with audio output devices in the ALSA system. (By the way, you can use tools such as \fBalsamixer\f1 or \fBaplay\f1 to discover what devices are available.) Use these settings to select the output device and the mixer control to be used to control the output volume. You can additionally set the desired size of the output buffer and you can adjust overall latency. Here are the \fBalsa\f1 group settings: +These settings are for the ALSA back end, used to communicate with audio output devices in the ALSA system. (By the way, you can use tools such as \fBalsamixer\f1 or \fBaplay\f1 to discover what devices are available.) Use these settings to select the output device and the mixer control to be used to control the output volume. You can additionally set the desired size of the output buffer and you can adjust overall latency. Here are the \fBalsa\f1 group settings: .TP \fBoutput_device=\f1\fI"output_device"\f1\fB;\f1 Use the output device called \fIoutput_device\f1. The default is the device called "default". .TP .TP \fBmixer_control_name=\f1\fI"name"\f1\fB;\f1 -Specify the \fIname\f1 of the mixer control to be used by shairport-sync to control the volume. The mixer control must be on the mixer device, which by default is the output device. If you do not specify a mixer control name, shaiport-sync will control the volume in software. \fBmixer_type=\f1\fI"mixer_type"\f1\fB;\f1 +Specify the \fIname\f1 of the mixer control to be used by shairport-sync to control the volume. The mixer control must be on the mixer device, which by default is the output device. If you do not specify a mixer control name, shairport-sync will adjust the volume in software. \fBmixer_type=\f1\fI"mixer_type"\f1\fB;\f1 This setting is deprecated and will be removed soon. If you wish to use a mixer control to control the volume, then set \fImixer_type\f1 to "hardware". The default is "software". .TP \fBmixer_device=\f1\fI"mixer_device"\f1\fB;\f1 -By default, the mixer is assumed to be output_device is. Use this setting to specify a device other than the output device. +By default, the mixer is assumed to be output_device. Use this setting to specify a device other than the output device. .TP \fBaudio_backend_latency_offset=\f1\fIoffset\f1\fB;\f1 Set this \fIoffset\f1, in frames, to compensate for a fixed delay in the audio back end. For example, if the output device delays by 100 ms, set this to -4410. .TP \fBaudio_backend_buffer_desired_length=\f1\fIlength\f1\fB;\f1 -Use this to set the desired number frames to be in the output device's hardware output buffer. The default is 6,615 frames, or 0.15 seconds. If set too small, buffer underflow may occur on low-powered machines. If too large, the response times when using a software mixer become annoying, or it may exceed the hardware buffer size. It may need to be larger on low-powered machines performing other tasks, such as processing metadata. +Use this to set the desired number frames to be in the output device's hardware output buffer. The default is 6,615 frames, or 0.15 seconds. If set too small, buffer underflow may occur on low-powered machines. If too large, the response times when using software volume control (i.e. when not using a mixer control to control volume) become annoying, or it may exceed the hardware buffer size. It may need to be larger on low-powered machines that are also performing other tasks, such as processing metadata. .TP \fB"PIPE" SETTINGS\f1 These settings are for the PIPE backend, used to route audio to a named unix pipe. The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second, stereo. @@ -178,13 +178,13 @@ There are two further settings affecting timing that might be useful if the pipe These are the settings available within the \fBpipe\f1 group: .TP \fBname=\f1\fI"/path/to/pipe"\f1\fB;\f1 -Use this to specify the name and location of the pipe. The pipe will be created and opened when Shairport Sync starts up and will be closed upon shutdown. Frames of audio will be sent to the pipe in packets of 352 frames and will be discarded if the pipe has not have a reader attached. The sender will wait for up to five seconds for a packet to be written before discarding it. +Use this to specify the name and location of the pipe. The pipe will be created and opened when shairport-sync starts up and will be closed upon shutdown. Frames of audio will be sent to the pipe in packets of 352 frames and will be discarded if the pipe has not have a reader attached. The sender will wait for up to five seconds for a packet to be written before discarding it. .TP \fBaudio_backend_latency_offset=\f1\fIoffset_in_frames\f1\fB;\f1 Packets of audio frames are written to the pipe synchronously -- that is, they are written to at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to the pipe 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0. .TP \fBaudio_backend_buffer_desired_length=\f1\fIbuffer_length_in_frames\f1\fB;\f1 -Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to the pipe. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length\f1 setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the pipe reader's buffer, giving it a nominal buffer size of 44,100 frames. Note that if the pipe reader consumes audio faster or slower than they are supplied, the buffer will empty or overflow eventually -- Shairport Sync performs no stuffing or interpolation when writing to a pipe. Default setting is 44,100. +Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to the pipe. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length\f1 setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the pipe reader's buffer, giving it a nominal buffer size of 44,100 frames. Note that if the pipe reader consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- shairport-sync performs no stuffing or interpolation when writing to a pipe. Default setting is 44,100 frames. .TP \fB"STDOUT" SETTINGS\f1 These settings are for the STDOUT backend, used to route audio to standard output ("stdout"). The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second, stereo. @@ -197,8 +197,10 @@ These are the settings available within the \fBstdout\f1 group: Packets of audio frames are written to stdout synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0. .TP \fBaudio_backend_buffer_desired_length=\f1\fIbuffer_length_in_frames\f1\fB;\f1 -Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to stdout. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length\f1 setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 44,100 frames. Note that if the stdout reader consumes audio faster or slower than they are supplied, the buffer will empty or overflow eventually -- Shairport Sync performs no stuffing or interpolation when writing to stdout. Default setting is 44,100. +Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to stdout. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length\f1 setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 44,100 frames. Note that if the stdout reader consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- shairport-sync performs no stuffing or interpolation when writing to stdout. Default setting is 44,100 frames. .SH OPTIONS +Note: if you are setting up shairport-sync for the first time or are updating an existing installation, you are encouraged to use the configuration file settings described above. Most of the options described below simply replicate the configuration settings and are retained to provide backward compatability with older installations of shairport-sync. + Many of the options take sensible default values, so you can normally ignore most of them. See the EXAMPLES section for typical usages. The command line for shairport-sync can take two kinds of options: regular \fBprogram options\f1 and \fBaudio backend options\f1. Program options are always listed first, followed by any audio backend options, preceded by a \fB--\f1 symbol. @@ -308,17 +310,17 @@ Use the specified output \fIdevice\f1. You may specify a card, e.g. \fBhw:0\f1, Use the specified hardware \fImixer\f1 for volume control. Use this to specify where the mixer is to be found. For example, if the mixer is associated with a card, as is often the case, specify the card, e.g. \fBhw:0\f1. If (unusually) the mixer is associated with a specific device on a card, specify the device, e.g. \fBhw:0,1\f1. The default is the device named in the \fB-d\f1 option, if given, or the device named \fBdefault\f1. .TP \fB-t \f1\fIdevicetype\f1 -The type of the output device is \fIdevicetype\f1 which must be \fBhardware\f1 or \fBsoftware\f1. The default is \fBsoftware\f1. If you specify \fBhardware\f1 you must also specify the output device using the \fB-d\f1 option and the name of the volume control using the \fB-c\f1 option. You may also need to specify the mixer using the \fB-m\f1 option. +This option is deprecated and is ignored. For your information, its functionality has been automatically incorporated in the -c option -- if you specify a mixer name with the -c option, it is assumed that the mixer is implemented in hardware. .SH EXAMPLES Here is a slightly contrived but typical example: -shairport-sync \fB-d\f1 \fB-a "Joe's Stereo"\f1 \fB-S soxr\f1 \fB--\f1 \fB-d hw:1,0\f1 \fB-m hw:1\f1 \fB-t hardware\f1 \fB-c PCM\f1 +shairport-sync \fB-d\f1 \fB-a "Joe's Stereo"\f1 \fB-S soxr\f1 \fB--\f1 \fB-d hw:1,0\f1 \fB-m hw:1\f1 \fB-c PCM\f1 -The program will run in daemon mode ( \fB-d\f1 ), will be visible as "Joe's Stereo" ( \fB-a "Joe's Stereo"\f1 ) and will use the SoX Resampler Library-based stuffing ( \fB-S soxr\f1 ). The audio backend options following the \fB--\f1 separator specify that the audio will be output on output 0 of soundcard 1 ( \fB-d hw:1,0\f1 ) and will take advantage of the same sound card's hardware (\fB-t hardware\f1) mixer ( \fB-m hw:1\f1 ) using the level control named "PCM" ( \fB-c "PCM"\f1 ). +The program will run in daemon mode ( \fB-d\f1 ), will be visible as "Joe's Stereo" ( \fB-a "Joe's Stereo"\f1 ) and will use the SoX Resampler Library-based stuffing ( \fB-S soxr\f1 ). The audio backend options following the \fB--\f1 separator specify that the audio will be output on output 0 of soundcard 1 ( \fB-d hw:1,0\f1 ) and will take advantage of the same sound card's mixer ( \fB-m hw:1\f1 ) using the level control named "PCM" ( \fB-c "PCM"\f1 ). The example above is slightly contrived in order to show the use of the \fB-m\f1 option. Typically, output 0 is the default output of a card, so the output device could be written \fB-d hw:1\f1 and then the mixer option would be unnecessary, giving the following, simpler, command: -shairport-sync \fB-d\f1 \fB-a "Joe's Stereo"\f1 \fB-S soxr\f1 \fB--\f1 \fB-d hw:1\f1 \fB-t hardware\f1 \fB-c PCM\f1 +shairport-sync \fB-d\f1 \fB-a "Joe's Stereo"\f1 \fB-S soxr\f1 \fB--\f1 \fB-d hw:1\f1 \fB-c PCM\f1 .SH CREDITS Mike Brady developed shairport-sync from the original shairport by James Laird. diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml index 79023243..14c8be65 100644 --- a/man/shairport-sync.7.xml +++ b/man/shairport-sync.7.xml @@ -66,8 +66,7 @@

shairport-sync plays audio streamed from iTunes or from an AirPlay - device to an audio device connected via an audio back end. At present, - the only fully-implemented back end is for ALSA.

+ device to an ALSA-compatible audio output device.

A feature of shairport-sync is that the audio is played synchronously. This means that if many devices are playing the same stream at the same @@ -76,7 +75,9 @@ enabling, for example, simultaneous multi-room operation.

-

Settings can be made using the configuration file (recommended for all new installations) or the by using command-line options.

+

shairport-sync can additionally be compiled and configured to stream raw audio to a pipe or to stdout.

+ +

Settings can be made using the configuration file (recommended for all new installations) or by using command-line options.

@@ -97,12 +98,11 @@

alsa = {

output_device = "hw:0";

-

mixer_type = "hardware";

mixer_control_name = "PCM";

};

Most settings have sensible default values, so -- as in the example above -- users generally only need to set (1) the service name, (2) a password (if desired) and - (3) the output device. If the output device has a hardware mixer that can be used for volume control, then (4) the fact that the output mixer is hardware and (5) the output device mixer's volume-control name should be specificed. It is highly desirable to use the hardware mixer for volume control, if available -- response time is reduced to zero and the processor load is reduced. In the example above, "soxr" interpolation was also enabled.

+ (3) the output device. If the output device has a mixer that can be used for volume control, then (4) the volume control's name should be specificed. It is highly desirable to use the output device's mixer for volume control, if available -- response time is reduced to zero and the processor load is reduced. In the example above, "soxr" interpolation was also enabled.

A sample configuration file with all possible settings, but with all of them commented out, is installed at /etc/shairport-sync.conf.sample.

@@ -149,23 +149,23 @@ -

Shairport Sync can process metadata provided by the source, such as Track Number, Album Name, cover art, etc. and can provide additional metadata such as volume level, +

shairport-sync can process metadata provided by the source, such as Track Number, Album Name, cover art, etc. and can provide additional metadata such as volume level, pause/resume, etc. It provides the metadata to a pipe, by default /tmp/shairport-sync-metadata. - To process metadata, Shairport Sync must have been compiled with metadata support included. - You can check tht this is so by running shairport-sync -V; the identification string will contain the word metadata.

+ To process metadata, shairport-sync must have been compiled with metadata support included. + You can check that this is so by running shairport-sync -V; the identification string will contain the word metadata.

The metadata group of settings allow you to enable metadata handling and to control certain aspects of it:

-

Shairport Sync can run programs just before it starts to play an audio stream and just after it finishes. +

shairport-sync can run programs just before it starts to play an audio stream and just after it finishes. You specify them using the sessioncontrol group settings run_this_before_play_begins and run_this_after_play_ends.

@@ -255,7 +256,7 @@ -

These settings are for the ALSA back end, used used to communicate with audio output devices in the ALSA system. +

These settings are for the ALSA back end, used to communicate with audio output devices in the ALSA system. (By the way, you can use tools such as alsamixer or aplay to discover what devices are available.) Use these settings to select the output device and the mixer control to be used to control the output volume. You can additionally set the desired size of the output buffer and you can adjust overall latency. Here are the alsa group settings:

@@ -285,7 +286,7 @@

mixer_control_name="name";

Specify the name of the mixer control to be used by shairport-sync to control the volume. The mixer control must be on the mixer device, which by default is the output device. - If you do not specify a mixer control name, shaiport-sync will control the volume in software. + If you do not specify a mixer control name, shairport-sync will adjust the volume in software.

mixer_type="mixer_type";

This setting is deprecated and will be removed soon. If you wish to use a mixer control to control the volume, then set mixer_type to "hardware". @@ -293,7 +294,7 @@ @@ -319,7 +321,7 @@ @@ -338,8 +340,8 @@ Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to the pipe. For example, if you send the first packet of audio exactly when it is due and, using a audio_backend_buffer_desired_length setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the pipe reader's buffer, giving it a nominal buffer size of 44,100 frames. - Note that if the pipe reader consumes audio faster or slower than they are supplied, the buffer will empty or overflow eventually -- - Shairport Sync performs no stuffing or interpolation when writing to a pipe. Default setting is 44,100. + Note that if the pipe reader consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- + shairport-sync performs no stuffing or interpolation when writing to a pipe. Default setting is 44,100 frames. @@ -365,8 +367,8 @@ Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to stdout. For example, if you send the first packet of audio exactly when it is due and, using a audio_backend_buffer_desired_length setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 44,100 frames. - Note that if the stdout reader consumes audio faster or slower than they are supplied, the buffer will empty or overflow eventually -- - Shairport Sync performs no stuffing or interpolation when writing to stdout. Default setting is 44,100. + Note that if the stdout reader consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- + shairport-sync performs no stuffing or interpolation when writing to stdout. Default setting is 44,100 frames. @@ -374,6 +376,10 @@ +

Note: if you are setting up shairport-sync for the first time or are updating an existing installation, + you are encouraged to use the configuration file settings described above. Most of the options described below + simply replicate the configuration settings and are retained to provide backward compatability with older installations of shairport-sync.

+

Many of the options take sensible default values, so you can normally ignore most of them. See the EXAMPLES section for typical usages.

@@ -700,11 +706,8 @@

-t devicetype

- The type of the output device is devicetype which must be - hardware or software. The default is software. - If you specify hardware you must also specify the output device using the - -d option and the name of the volume control using the -c option. - You may also need to specify the mixer using the -m option. + This option is deprecated and is ignored. For your information, its functionality has been automatically incorporated in the -c option + -- if you specify a mixer name with the -c option, it is assumed that the mixer is implemented in hardware.

@@ -717,7 +720,6 @@ -- -d hw:1,0 -m hw:1 - -t hardware -c PCM

The program will run in daemon mode ( -d ), will be visible as @@ -725,8 +727,7 @@ Library-based stuffing ( -S soxr ). The audio backend options following the -- separator specify that the audio will be output on output 0 of soundcard 1 ( - -d hw:1,0 ) and will take advantage of the same sound card's hardware - (-t hardware) mixer ( -m hw:1 ) + -d hw:1,0 ) and will take advantage of the same sound card's mixer ( -m hw:1 ) using the level control named "PCM" ( -c "PCM" ).

The example above is slightly contrived in order to show the use of the -m option. @@ -738,7 +739,6 @@ -S soxr -- -d hw:1 - -t hardware -c PCM diff --git a/man/shairport-sync.html b/man/shairport-sync.html index a936dd53..c587b03e 100644 --- a/man/shairport-sync.html +++ b/man/shairport-sync.html @@ -46,8 +46,7 @@

Description

shairport-sync plays audio streamed from iTunes or from an AirPlay - device to an audio device connected via an audio back end. At present, - the only fully-implemented back end is for ALSA.

+ device to an ALSA-compatible audio output device.

A feature of shairport-sync is that the audio is played synchronously. This means that if many devices are playing the same stream at the same @@ -56,7 +55,9 @@ enabling, for example, simultaneous multi-room operation.

-

Settings can be made using the configuration file (recommended for all new installations) or the by using command-line options.

+

shairport-sync can additionally be compiled and configured to stream raw audio to a pipe or to stdout.

+ +

Settings can be made using the configuration file (recommended for all new installations) or by using command-line options.

@@ -79,12 +80,11 @@

alsa = {

output_device = "hw:0";

-

mixer_type = "hardware";

mixer_control_name = "PCM";

};

Most settings have sensible default values, so -- as in the example above -- users generally only need to set (1) the service name, (2) a password (if desired) and - (3) the output device. If the output device has a hardware mixer that can be used for volume control, then (4) the fact that the output mixer is hardware and (5) the output device mixer's volume-control name should be specificed. It is highly desirable to use the hardware mixer for volume control, if available -- response time is reduced to zero and the processor load is reduced. In the example above, "soxr" interpolation was also enabled.

+ (3) the output device. If the output device has a mixer that can be used for volume control, then (4) the volume control's name should be specificed. It is highly desirable to use the output device's mixer for volume control, if available -- response time is reduced to zero and the processor load is reduced. In the example above, "soxr" interpolation was also enabled.

A sample configuration file with all possible settings, but with all of them commented out, is installed at /etc/shairport-sync.conf.sample.

@@ -131,23 +131,23 @@

mdns_backend="backend";

- Shairport Sync has a number of modules of code ("backends") for interacting with the mDNS service to be used to advertise itself. Normally, the first mDNS backend that works is selected. This setting forces the selection of the specific mDNS backend. The default is "avahi". Perform the command shairport-sync -h to get a list of available mDNS modules. + shairport-sync has a number of modules of code ("backends") for interacting with the mDNS service to be used to advertise itself. Normally, the first mDNS backend that works is selected. This setting forces the selection of the specific mDNS backend. The default is "avahi". Perform the command shairport-sync -h to get a list of available mDNS modules.

output_backend="backend";

- Shairport Sync has a number of modules of code ("backends") through which audio is output. Normally, the first audio backend that works is selected. This setting forces the selection of the specific audio backend. The default is "alsa". Perform the command shairport-sync -h to get a list of available audio backends. Only the alsa backend supports synchronisation. + shairport-sync has a number of modules of code ("backends") through which audio is output. Normally, the first audio backend that works is selected. This setting forces the selection of the specific audio backend. The default is "alsa". Perform the command shairport-sync -h to get a list of available audio backends. Only the alsa backend supports synchronisation.

port=portnumber;

- Use this to specify the portnumber Shairport Sync uses to listen for service requests from iTunes, etc. The default is port 5000. + Use this to specify the portnumber shairport-sync uses to listen for service requests from iTunes, etc. The default is port 5000. -

udp_port_base=6001;

- When Shairport Sync starts to play audio, it establises three UDP connections to the audio source. Use this setting to specify the starting portnumber for these three ports. It will pick the first three unused ports starting from portnumber. The default is port 6001. +

udp_port_base=portnumber;

+ When shairport-sync starts to play audio, it establises three UDP connections to the audio source. Use this setting to specify the starting portnumber for these three ports. It will pick the first three unused ports starting from portnumber. The default is port 6001. -

udp_port_range=100;

- Use this in conjunction with the prevous setting to specify the range of ports that can be checked for availability. Only three ports are needed. The default is 100, thus 100 ports will be checked from port 6001 upwards until three are found. +

udp_port_range=range;

+ Use this in conjunction with the prevous setting to specify the range of ports that can be checked for availability. Only three ports are needed. The default is 100, thus 100 ports will be checked from port 6001 upwards until three are found.

drift=frames;

@@ -159,7 +159,7 @@

resync_threshold=threshold;

- Resynchronise if timings differ by more than threshold frames. + Resynchronise if timings differ by more than threshold frames. If the output timing differs from the source timing by more than the threshold, output will be muted and a full resynchronisation will occur. The default threshold is 2,205 frames, i.e. 50 @@ -197,22 +197,23 @@

"METADATA" SETTINGS

-

Shairport Sync can process metadata provided by the source, such as Track Number, Album Name, cover art, etc. and can provide additional metadata such as volume level, +

shairport-sync can process metadata provided by the source, such as Track Number, Album Name, cover art, etc. and can provide additional metadata such as volume level, pause/resume, etc. It provides the metadata to a pipe, by default /tmp/shairport-sync-metadata. - To process metadata, Shairport Sync must have been compiled with metadata support included. - You can check tht this is so by running shairport-sync -V; the identification string will contain the word metadata.

+ To process metadata, shairport-sync must have been compiled with metadata support included. + You can check that this is so by running shairport-sync -V; the identification string will contain the word metadata.

The metadata group of settings allow you to enable metadata handling and to control certain aspects of it:

enabled="choice";

- Set the choice to "yes" to enable Shairport Sync to look for metadata from the audio source and to forward it, - along with metadata generated by Shairport Sync itself, to the metadata pipe. The default is "no". + Set the choice to "yes" to enable shairport-sync to look for metadata from the audio source and to forward it, + along with metadata generated by shairport-sync itself, to the metadata pipe. The default is "no".

include_cover_art="choice";

- Set the choice to "yes" to enable Shairport Sync to look for cover art from the audio source and to forward it to the metadata pipe. - A reason for not enabling cover art is that the images can sometimes be very large and mey delay transmission of subsequent metadata through the pipe. + Set the choice to "yes" to enable shairport-sync to look for cover art from the audio source and to include it in the feed to the metadata pipe. + You must also enable metadata (see above). + One reason for not including cover art is that the images can sometimes be very large and may delay transmission of subsequent metadata through the pipe. The default is "no". @@ -221,7 +222,7 @@

"SESSIONCONTROL" SETTINGS

-

Shairport Sync can run programs just before it starts to play an audio stream and just after it finishes. +

shairport-sync can run programs just before it starts to play an audio stream and just after it finishes. You specify them using the sessioncontrol group settings run_this_before_play_begins and run_this_after_play_ends.

@@ -237,7 +238,7 @@

wait_for_completion="choice";

- Set choice to "yes" to make Shairport Sync wait until the programs specified in the run_this_before_play_begins + Set choice to "yes" to make shairport-sync wait until the programs specified in the run_this_before_play_begins and run_this_after_play_ends have completed execution before continuing. The default is "no". @@ -253,7 +254,7 @@

"ALSA" SETTINGS

-

These settings are for the ALSA back end, used used to communicate with audio output devices in the ALSA system. +

These settings are for the ALSA back end, used to communicate with audio output devices in the ALSA system. (By the way, you can use tools such as alsamixer or aplay to discover what devices are available.) Use these settings to select the output device and the mixer control to be used to control the output volume. You can additionally set the desired size of the output buffer and you can adjust overall latency. Here are the alsa group settings:

@@ -267,7 +268,7 @@

mixer_control_name="name";

Specify the name of the mixer control to be used by shairport-sync to control the volume. The mixer control must be on the mixer device, which by default is the output device. - If you do not specify a mixer control name, shaiport-sync will control the volume in software. + If you do not specify a mixer control name, shairport-sync will adjust the volume in software.

mixer_type="mixer_type";

This setting is deprecated and will be removed soon. If you wish to use a mixer control to control the volume, then set mixer_type to "hardware". @@ -275,7 +276,7 @@

mixer_device="mixer_device";

- By default, the mixer is assumed to be output_device is. Use this setting to specify a device other than the output device. + By default, the mixer is assumed to be output_device. Use this setting to specify a device other than the output device.

audio_backend_latency_offset=offset;

@@ -286,8 +287,9 @@

audio_backend_buffer_desired_length=length;

Use this to set the desired number frames to be in the output device's hardware output buffer. The default is 6,615 frames, or 0.15 seconds. If set too small, buffer underflow may occur on low-powered machines. - If too large, the response times when using a software mixer become annoying, or it may exceed the hardware buffer size. - It may need to be larger on low-powered machines performing other tasks, such as processing metadata. + If too large, the response times when using software volume control (i.e. when not using a mixer control to control volume) become annoying, + or it may exceed the hardware buffer size. + It may need to be larger on low-powered machines that are also performing other tasks, such as processing metadata.

"PIPE" SETTINGS

@@ -301,7 +303,7 @@

name="/path/to/pipe";

- Use this to specify the name and location of the pipe. The pipe will be created and opened when Shairport Sync starts up and will be closed upon shutdown. + Use this to specify the name and location of the pipe. The pipe will be created and opened when shairport-sync starts up and will be closed upon shutdown. Frames of audio will be sent to the pipe in packets of 352 frames and will be discarded if the pipe has not have a reader attached. The sender will wait for up to five seconds for a packet to be written before discarding it. @@ -320,8 +322,8 @@ Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to the pipe. For example, if you send the first packet of audio exactly when it is due and, using a audio_backend_buffer_desired_length setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the pipe reader's buffer, giving it a nominal buffer size of 44,100 frames. - Note that if the pipe reader consumes audio faster or slower than they are supplied, the buffer will empty or overflow eventually -- - Shairport Sync performs no stuffing or interpolation when writing to a pipe. Default setting is 44,100. + Note that if the pipe reader consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- + shairport-sync performs no stuffing or interpolation when writing to a pipe. Default setting is 44,100 frames. @@ -347,8 +349,8 @@ Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to stdout. For example, if you send the first packet of audio exactly when it is due and, using a audio_backend_buffer_desired_length setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 44,100 frames. - Note that if the stdout reader consumes audio faster or slower than they are supplied, the buffer will empty or overflow eventually -- - Shairport Sync performs no stuffing or interpolation when writing to stdout. Default setting is 44,100. + Note that if the stdout reader consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- + shairport-sync performs no stuffing or interpolation when writing to stdout. Default setting is 44,100 frames. @@ -358,6 +360,10 @@

Options

+

Note: if you are setting up shairport-sync for the first time or are updating an existing installation, + you are encouraged to use the configuration file settings described above. Most of the options described below + simply replicate the configuration settings and are retained to provide backward compatability with older installations of shairport-sync.

+

Many of the options take sensible default values, so you can normally ignore most of them. See the EXAMPLES section for typical usages.

@@ -688,11 +694,8 @@

-t devicetype

- The type of the output device is devicetype which must be - hardware or software. The default is software. - If you specify hardware you must also specify the output device using the - -d option and the name of the volume control using the -c option. - You may also need to specify the mixer using the -m option. + This option is deprecated and is ignored. For your information, its functionality has been automatically incorporated in the -c option + -- if you specify a mixer name with the -c option, it is assumed that the mixer is implemented in hardware.

@@ -706,7 +709,6 @@ -- -d hw:1,0 -m hw:1 - -t hardware -c PCM
@@ -715,8 +717,7 @@ Library-based stuffing ( -S soxr ). The audio backend options following the -- separator specify that the audio will be output on output 0 of soundcard 1 ( - -d hw:1,0 ) and will take advantage of the same sound card's hardware - (-t hardware) mixer ( -m hw:1 ) + -d hw:1,0 ) and will take advantage of the same sound card's mixer ( -m hw:1 ) using the level control named "PCM" ( -c "PCM" ).

The example above is slightly contrived in order to show the use of the -m option. @@ -728,7 +729,6 @@ -S soxr -- -d hw:1 - -t hardware -c PCM
From 49c93576811f611ce45c66648866fda9ad674676 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 20 Aug 2015 10:14:28 -0400 Subject: [PATCH 196/220] Update to ask users to run update-rc.d manually plus miscellaneous small edits --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c1aba237..acdb41b0 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,16 @@ Omit the `--with-soxr` if the libsoxr library is not available. For installation `$ make` -Run `$sudo make install` to install `shairport-sync` along with a default configuration file and a System V startup script, if chosen, to launch it automatically at system startup. The settings are the most basic defaults, so you will want to edit the configuration — the file is `/etc/shairport-sync.conf` — to give the service a name, use a specific sound card, use the hardware mixer and volume control, etc. — there are some examples in the sample configuration file. +will build the application. Next, run: + +``` +`$sudo make install` +`$sudo update-rc.d shairport-sync defaults 90 10` +``` + +to install `shairport-sync` along with a `man` page, a default configuration file and a System V startup script to launch it automatically at system startup. +The settings are the most basic defaults, so you will want to edit the configuration — the file is `/etc/shairport-sync.conf` — to give the service a name, +use a specific sound card and mixer control, etc. — there are some examples in the sample configuration file. *Man Page* From 312bcd03ba3cff7e7104d2c5d2089c6efcbe4ebe Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 20 Aug 2015 10:15:12 -0400 Subject: [PATCH 197/220] Remove mixer_type or -t stuff, add messages about deprecation --- audio_alsa.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index b3335d7e..9b423f9e 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -85,7 +85,6 @@ static int64_t accumulated_delay, accumulated_da_delay; static void help(void) { printf(" -d output-device set the output device [default*|...]\n" - " -t mixer-type set the mixer type [software*|hardware]\n" " -m mixer-device set the mixer device ['output-device'*|...]\n" " -c mixer-control set the mixer control [Master*|...]\n" " -i mixer-index set the mixer index [0*|...]\n" @@ -103,7 +102,7 @@ static int init(int argc, char **argv) { - // get settings from settings file first, allow them to be over-ridden by command line options + // get settings from settings file first, allow them to be overridden by command line options if (config.cfg != NULL) { /* Get the desired buffer size setting. */ @@ -131,15 +130,13 @@ static int init(int argc, char **argv) { alsa_out_dev = (char *)str; } + /* Get the Mixer Type setting. */ + if (config_lookup_string(config.cfg, "alsa.mixer_type", &str)) { - if (strcasecmp(str, "software") == 0) - hardware_mixer = 0; - else if (strcasecmp(str, "hardware") == 0) - hardware_mixer = 1; - else - die("Invalid alsa mixer option choice \"%s\". It should be \"software\" or \"hardware\""); + inform("The alsa mixer_type setting is deprecated and has been ignored. FYI, using the \"mixer_control_name\" setting automatically chooses a hardware mixer."); } + /* Get the Mixer Device Name. */ if (config_lookup_string(config.cfg, "alsa.mixer_device", &str)) { @@ -149,6 +146,7 @@ static int init(int argc, char **argv) { /* Get the Mixer Control Name. */ if (config_lookup_string(config.cfg, "alsa.mixer_control_name", &str)) { alsa_mix_ctrl = (char *)str; + hardware_mixer = 1; } } @@ -162,15 +160,17 @@ static int init(int argc, char **argv) { case 'd': alsa_out_dev = optarg; break; + case 't': - if (strcmp(optarg, "hardware") == 0) - hardware_mixer = 1; + inform("The alsa backend -t option is deprecated and has been ignored. FYI, using the -c option automatically chooses a hardware mixer."); break; + case 'm': alsa_mix_dev = optarg; break; case 'c': alsa_mix_ctrl = optarg; + hardware_mixer = 1; break; case 'i': alsa_mix_index = strtol(optarg, NULL, 10); From 8f41c048b465c47010665edcedc14e0a1781cfad Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 20 Aug 2015 14:49:16 -0400 Subject: [PATCH 198/220] Update README.md --- README.md | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index acdb41b0..5e3cacd4 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This branch — "development" — is unstable. To access the stable branch, plea More Information ---------- -Shairport Sync works by using timing information and timestamps present in data coming from the audio source (e.g. an iPhone) to "play" audio at exactly the right time. It does this by monitoring and controlling the "latency" — the time between a sound frame is supposed to be played, as specified by its `timestamp`, and the time when it is actually played by the audio output device, usually a Digital to Audio Converter (DAC). Timestamps are measured relative to the source computer's clocks, the `source clock`, but timing must be done relative to the clock of the computer running Shairport Sync, the `local clock`. The source and local clocks are synchronised, usually to within a fraction of a millisecond, using a variant of NTP synchronisation protocols. +Shairport Sync works by using timing information and timestamps present in data coming from the audio source (e.g. an iPhone) to "play" audio at exactly the right time. It does this by monitoring and controlling the *latency* — the time between a sound frame is supposed to be played, as specified by its `timestamp`, and the time when it is actually played by the audio output device, usually a Digital to Audio Converter (DAC). Timestamps are measured relative to the source computer's clocks, the `source clock`, but timing must be done relative to the clock of the computer running Shairport Sync, the `local clock`. The source and local clocks are synchronised, usually to within a fraction of a millisecond, using a variant of NTP synchronisation protocols. To maintain the exact latency required, if an output device is running slow relative to the source, Shairport Sync will delete frames of audio to allow the device to keep up. If the output device is running fast, Shairport Sync will insert frames to keep time. The number of frames inserted or deleted is so small as to be almost inaudible on normal audio material. Frames are inserted or deleted as necessary at pseudorandom intervals. Alternatively, with `libsoxr` support, Shairport Sync can resample the audio feed to ensure the output device can keep up. This is less obtrusive than insertion and deletion but requires a good deal of processing power — most embedded devices probably can't support it. The process of insertion/deletion or resampling is rather inelegantly called “stuffing”. @@ -107,13 +107,15 @@ Here is an example, suitable for installations such as Ubuntu and Raspbian: Omit the `--with-soxr` if the libsoxr library is not available. For installation into a `systemd` system, replace the `--with-systemv` with `--with-systemd`. +Enter: + `$ make` -will build the application. Next, run: +to build the application. Next, run: ``` -`$sudo make install` -`$sudo update-rc.d shairport-sync defaults 90 10` +$sudo make install +$sudo update-rc.d shairport-sync defaults 90 10 ``` to install `shairport-sync` along with a `man` page, a default configuration file and a System V startup script to launch it automatically at system startup. @@ -150,16 +152,14 @@ general = ``` The `alsa` group is used to specify properties of the output device. The most obvious setting is the name of the output device which you can set using the `output_device` tag. -The following `alsa` group settings are very important for maximum performance. If your audio device has a hardware mixer and volume control, then Shairport Sync can use them to give instant response to volume and mute commands and it can offload some work from the processor. -* The `mixer_type` tag allows you to specify the type of audio mixer -- `software` (default) or `hardware`. -* The `mixer_control_name` tag allows you to specify the name of the volume control on the hardware mixer. -* The `mixer_device` tag allows you specify where the mixer is. By default, the mixer is to be found where you specify with the `output_device` tag, so you only need to use the `mixer_device` tag if the mixer is elsewhere. This can happen if you specify a *device* rather than a *card* with the `output_device` tag, because normally a mixer is associated with a *card* rather than a device. Suppose you wish to use the output device `5` of card `hw:0` and the mixer volume-control named `PCM`: +The following `alsa` group settings are very important for maximum performance. If your audio device has a mixer that can be use to control the volume, then Shairport Sync can use it to give instant response to volume and mute commands and it can offload some work from the processor. +* The `mixer_control_name` tag allows you to specify the name of the mixer volume control. +* The `mixer_device` tag allows you specify where the mixer is. By default, the mixer is on the `output_device`, so you only need to use the `mixer_device` tag if the mixer is elsewhere. This can happen if you specify a *device* rather than a *card* with the `output_device` tag, because normally a mixer is associated with a *card* rather than a device. Suppose you wish to use the output device `5` of card `hw:0` and the mixer volume-control named `PCM`: ``` alsa = { output_device = "hw:0,5"; - mixer_type = "hardware"; mixer_device = "hw:0"; mixer_control_name = "PCM"; // ... other alsa settings @@ -211,7 +211,6 @@ general = { alsa = { output_device = "hw:0"; - mixer_type = "hardware"; mixer_control_name = "PCM"; }; ``` @@ -225,7 +224,6 @@ general = { alsa = { output_device = "hw:1"; - mixer_type = "hardware"; mixer_control_name = "PCM"; }; ``` @@ -238,7 +236,6 @@ general = { alsa = { output_device = "hw:1"; - mixer_type = "hardware"; mixer_control_name = "Speaker"; }; ``` @@ -251,7 +248,6 @@ general = { alsa = { output_device = "hw:1"; - mixer_type = "hardware"; mixer_control_name = "PCM"; }; ``` @@ -265,7 +261,6 @@ general = { }; alsa = { - mixer_type = "hardware"; mixer_control_name = "PCM"; }; ``` @@ -277,7 +272,6 @@ general = { }; alsa = { - mixer_type = "hardware"; mixer_control_name = "Speaker"; }; ``` From 8488f974e9266c744038a38b2583349c82e9ec94 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 20 Aug 2015 15:13:18 -0400 Subject: [PATCH 199/220] Update RELEASENOTES.md --- RELEASENOTES.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f1251ade..6857b77e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,20 @@ +Version 2.3.12 +---- +Annoying changes you probably need to do something about +* The `alsa` group `mixer_type` setting is deprecated and you should stop using it. Its functionality has been subsumed into `mixer_name` – when you specify a `mixer_name` it automatically chooses the `hardware` mixer type. + +Changes +* `update-rc.d` has been removed from the installation script for System V because it causes problems for package makers. It's now noted in the user installation instructions. + +Enhancements +* Larger range of interpolation. Shairport Sync has previously constrained not to make interpolations ("corrections") of more than about 1 per 1000 real frames. This contraint has been relaxed, and it is now able to make corrections of up to 1 in 352 real frames. This might result in a faster and undesirably sudden correction early during a play session, so a number of further changes have been made. The full set of these changes is as follows: + * No corrections happen for the first five seconds. + * Corrections of up to about 1 in 1000 for the next 25 seconds. + * Corrections of up to 1 in 352 thereafter. + +Documentation Update +* Nearly there with updates concerning the configuration file. + Version 2.3.11 ---- Documentation Update @@ -5,8 +22,8 @@ Documentation Update Version 2.3.10 ---- -* Bug fix - * The "pipe" backend used output code that would block if the pipe didn't have a reader. This has been replaced by non-blocking code. Here are some implications: +Bug fix +* The "pipe" backend used output code that would block if the pipe didn't have a reader. This has been replaced by non-blocking code. Here are some implications: * When the pipe is created, Shairport Sync will not block if a reader isn't present. * If the pipe doesn't have a reader when Shairport Sync wants to output to it, the output will be discarded. * If a reader disappears while writing is occuring, the write will time out after five seconds. From 45b17a48dbe3963e3bc4d869d6b62325a04a5065 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 20 Aug 2015 15:18:21 -0400 Subject: [PATCH 200/220] Update RELEASENOTES.md --- RELEASENOTES.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6857b77e..2065e991 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,18 +1,20 @@ Version 2.3.12 ---- -Annoying changes you probably need to do something about +**Note** +* We're getting ready to release the development branch as the new, stable, master branch at 2.4. If you're packaging Shairport Sync, you might prefer to wait a short while as we add a little polish before the release. + +**Changes** +* `update-rc.d` has been removed from the installation script for System V because it causes problems for package makers. It's now noted in the user installation instructions. * The `alsa` group `mixer_type` setting is deprecated and you should stop using it. Its functionality has been subsumed into `mixer_name` – when you specify a `mixer_name` it automatically chooses the `hardware` mixer type. -Changes -* `update-rc.d` has been removed from the installation script for System V because it causes problems for package makers. It's now noted in the user installation instructions. -Enhancements +**Enhancements** * Larger range of interpolation. Shairport Sync has previously constrained not to make interpolations ("corrections") of more than about 1 per 1000 real frames. This contraint has been relaxed, and it is now able to make corrections of up to 1 in 352 real frames. This might result in a faster and undesirably sudden correction early during a play session, so a number of further changes have been made. The full set of these changes is as follows: * No corrections happen for the first five seconds. * Corrections of up to about 1 in 1000 for the next 25 seconds. * Corrections of up to 1 in 352 thereafter. -Documentation Update +**Documentation Update** * Nearly there with updates concerning the configuration file. Version 2.3.11 From 0b7d9913d16d8958e66da6f39258873c2841a5b7 Mon Sep 17 00:00:00 2001 From: Bill Peck Date: Thu, 20 Aug 2015 15:47:25 -0400 Subject: [PATCH 201/220] Update release. drop unnecessary patch. fix to enable systemd --- shairport-sync.spec | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/shairport-sync.spec b/shairport-sync.spec index c2694f50..3e2fe663 100644 --- a/shairport-sync.spec +++ b/shairport-sync.spec @@ -1,5 +1,5 @@ Name: shairport-sync -Version: 2.3.7 +Version: 2.3.12 Release: 1%{?dist} Summary: AirTunes emulator. Shairport Sync adds multi-room capability with Audio Synchronisation @@ -7,7 +7,6 @@ Group: Applications/Multimedia License: GPL URL: https://github.com/mikebrady/shairport-sync Source0: https://github.com/mikebrady/%{name}/archive/%{version}.tar.gz#/%{name}-%{version}.tar.gz -Patch0: shairport-sync-systemd.patch BuildRequires: autoconf BuildRequires: automake @@ -33,11 +32,10 @@ Shairport Sync does not support AirPlay video or photo streaming. %prep %setup -q -%patch0 -p1 %build autoreconf -i -f -%configure --with-avahi --with-alsa --with-ssl=openssl --with-soxr --without-initscript +%configure --with-avahi --with-alsa --with-ssl=openssl --with-soxr --with-systemd make %{?_smp_mflags} From ecb4861f6b119d10666e7362ce6a43de302b2de1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 22 Aug 2015 18:41:04 -0400 Subject: [PATCH 202/220] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5e3cacd4..dfa980b4 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,8 @@ $ autoreconf -i -f Choose the appropriate `--with-*` options: - `--with-alsa` for the ALSA audio back end. This is required. +- `--with-stdout` include an optional backend module to enable raw audio to be output through standard output (stdout). +- `--with-pipe` include an optional backend module to enable raw audio to be output through a unix pipe. - `--with-avahi` or `--with-tinysvcmdns` for mdns support. Avahi is a widely-used system-wide zero-configuration networking (zeroconf) service — it may already be in your system. If you don't have Avahi, or similar, then consider including tinysvcmdns, which is a tiny zeroconf service embedded inside the shairport-sync application itself. To enable multicast for `tinysvcmdns`, you may have to add a default route with the following command: `route add -net 224.0.0.0 netmask 224.0.0.0 eth0` (substitute the correct network port for `eth0`). You should not have more than one zeroconf service on the same system — bad things may happen, according to RFC 6762, §15. - `--with-ssl=openssl` or `--with-ssl=polarssl` for encryption and related utilities using either OpenSSL or PolarSSL. - `--with-soxr` for libsoxr-based resampling. From dd359d49f59b268fc565e300f672d8139924e50d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 22 Aug 2015 18:51:09 -0400 Subject: [PATCH 203/220] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dfa980b4..6c4ec796 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,8 @@ Choose the appropriate `--with-*` options: - `--with-piddir` for specifying where the PID file should be stored. This directory is normally chosen automatically. The directory must be writable. If you use this option, you may have to edit the init script to search for the PID file in your new location. - `--with-metadata` to add support for Shairport Sync to pipe metadata to a compatible application of your choice. See https://github.com/mikebrady/shairport-sync-metadata-reader for a sample metadata reader. - `--with-systemv` to install a System V init script at the `make install` stage. Default is not to to install. -- `--with-configfiles` to install a configuration file and sample at the `make install` stage. Default is to install. An existing `/etc/shairport-sync.conf` will not be overwritten. +- `--with-systemd` to install a systemd service description at the `make install` stage. Default is not to to install. +- `--with-configfile` to install a configuration file and a separate sample file at the `make install` stage. Default is to install. An existing `/etc/shairport-sync.conf` will not be overwritten. - `--with-pkg-config` to use pkg-config to find libraries. Default is to use pkg-config — this option is for special purpose use. Here is an example, suitable for installations such as Ubuntu and Raspbian: From 68e4873cc6cfb3c7d7a5de26625dd392b50d333f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 25 Aug 2015 22:14:33 +0100 Subject: [PATCH 204/220] Update README.md --- README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6c4ec796..543de6a1 100644 --- a/README.md +++ b/README.md @@ -49,11 +49,25 @@ Note: Historically, Shairport Sync has taken its settings from command line argu Building And Installing --------------------- +If you wish to install Shairport Sync on OpenWrt, Arch or Fedora platforms, please follow the appropriate instructions below. Otherwise follow the General Build Instructions. Then, when the progam has been installed, refer to the section on Configuring Shairport Sync that follows. + +**OpenWrt:** If you're interested in Shairport Sync for OpenWrt, there's an OpenWrt package at https://github.com/mikebrady/shairport-sync-for-openwrt. OpenWrt doesn't support the IQaudIO Pi-DAC. +**Arch Linux:** An Arch Linux installation package is available (thanks!) at [EliaCereda/shairport-sync-PKGBUILD](https://github.com/EliaCereda/shairport-sync-PKGBUILD). -Otherwise, follow these instructions. +**Fedora:** +Download the tarball from the "releases" tab on github or use `wget` and then use `rpmbuild`. This example is for version 2.3.12: +``` +% wget -O shairport-sync.2.3.12.tar.gz https://github.com/mikebrady/shairport-sync/archive/2.3.12.tar.gz +% rpmbuild -ta shairport-sync-2.3.12.tar.gz +``` +The `-ta` means "build all from this tarball". (Thanks to https://github.com/p3ck for the script.) + +**General Build Instructions** + +To build Shairport Sync from sources on Debian, Ubuntu, Raspian, etc. follow these instructions. The following libraries are required: * OpenSSL or PolarSSL From 732c1f55cc779a1535de664ffeabb161eb5b7847 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 26 Aug 2015 19:28:52 +0100 Subject: [PATCH 205/220] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 543de6a1..21f4132e 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ An Arch Linux installation package is available (thanks!) at [EliaCereda/shairp **Fedora:** Download the tarball from the "releases" tab on github or use `wget` and then use `rpmbuild`. This example is for version 2.3.12: ``` -% wget -O shairport-sync.2.3.12.tar.gz https://github.com/mikebrady/shairport-sync/archive/2.3.12.tar.gz +% wget -O shairport-sync-2.3.12.tar.gz https://github.com/mikebrady/shairport-sync/archive/2.3.12.tar.gz % rpmbuild -ta shairport-sync-2.3.12.tar.gz ``` The `-ta` means "build all from this tarball". (Thanks to https://github.com/p3ck for the script.) From 5772eecd4b5bdebae61438ea67fa5cd3c0689e17 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 26 Aug 2015 21:08:32 +0100 Subject: [PATCH 206/220] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 85ac9dfa..80b950e0 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.12], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.13], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From c742aaea4bb2db836c1ae5368e9cf5c8771cbc4f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 26 Aug 2015 21:09:13 +0100 Subject: [PATCH 207/220] Update shairport-sync.spec --- shairport-sync.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shairport-sync.spec b/shairport-sync.spec index 3e2fe663..23899180 100644 --- a/shairport-sync.spec +++ b/shairport-sync.spec @@ -1,5 +1,5 @@ Name: shairport-sync -Version: 2.3.12 +Version: 2.3.13 Release: 1%{?dist} Summary: AirTunes emulator. Shairport Sync adds multi-room capability with Audio Synchronisation From 1b0b4659568105b899d7695ab7188541703d70ef Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 26 Aug 2015 21:10:41 +0100 Subject: [PATCH 208/220] Update shairport-sync.spec --- shairport-sync.spec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shairport-sync.spec b/shairport-sync.spec index 23899180..70f29d88 100644 --- a/shairport-sync.spec +++ b/shairport-sync.spec @@ -59,3 +59,5 @@ getent passwd %{name} &> /dev/null || useradd --system -c "%{name} User" \ %changelog * Fri Jul 24 2015 Bill Peck 2.3.7-1 - Initial spec file +* Wed Aug 26 2015 Mike Brady 2.3.13-1 +- Harmonise release numbers From bc979a13aff484200db80a2887fded0a74e1843a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 26 Aug 2015 21:12:14 +0100 Subject: [PATCH 209/220] Update RELEASENOTES.md --- RELEASENOTES.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 2065e991..dff4db5f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,11 @@ +Version 2.3.13 +---- +**Note** +* We're getting ready to release the development branch as the new, stable, master branch at 2.4. If you're packaging Shairport Sync, you might prefer to wait a short while as we add a little polish before the release. + +**Changes** +* Harmonise version numbers on the release and on the shairport.spec file used in Fedora. + Version 2.3.12 ---- **Note** From 75f036d15299d97294da8d23273554f23d8d4c24 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 26 Aug 2015 21:20:03 +0100 Subject: [PATCH 210/220] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 21f4132e..6a279f60 100644 --- a/README.md +++ b/README.md @@ -60,8 +60,8 @@ An Arch Linux installation package is available (thanks!) at [EliaCereda/shairp **Fedora:** Download the tarball from the "releases" tab on github or use `wget` and then use `rpmbuild`. This example is for version 2.3.12: ``` -% wget -O shairport-sync-2.3.12.tar.gz https://github.com/mikebrady/shairport-sync/archive/2.3.12.tar.gz -% rpmbuild -ta shairport-sync-2.3.12.tar.gz +% wget -O shairport-sync-2.3.13.1.tar.gz https://github.com/mikebrady/shairport-sync/archive/2.3.13.1.tar.gz +% rpmbuild -ta shairport-sync-2.3.13.1.tar.gz ``` The `-ta` means "build all from this tarball". (Thanks to https://github.com/p3ck for the script.) From f80b2f27faffb137ca7d31e50e8813eb22de0f91 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 26 Aug 2015 21:21:01 +0100 Subject: [PATCH 211/220] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 80b950e0..d199effa 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.13], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.3.13.1], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 44ef78467d6ba86279c7239d236d267dc6dca199 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 26 Aug 2015 21:21:49 +0100 Subject: [PATCH 212/220] Update shairport-sync.spec --- shairport-sync.spec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shairport-sync.spec b/shairport-sync.spec index 70f29d88..1b3dc698 100644 --- a/shairport-sync.spec +++ b/shairport-sync.spec @@ -1,5 +1,5 @@ Name: shairport-sync -Version: 2.3.13 +Version: 2.3.13.1 Release: 1%{?dist} Summary: AirTunes emulator. Shairport Sync adds multi-room capability with Audio Synchronisation @@ -57,7 +57,7 @@ getent passwd %{name} &> /dev/null || useradd --system -c "%{name} User" \ %doc AUTHORS LICENSES README.md %changelog +* Wed Aug 26 2015 Mike Brady 2.3.13.1-1 +- Harmonise release numbers * Fri Jul 24 2015 Bill Peck 2.3.7-1 - Initial spec file -* Wed Aug 26 2015 Mike Brady 2.3.13-1 -- Harmonise release numbers From 2ef8e651d0593431301ae4c470b53e3f22331321 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 26 Aug 2015 21:30:04 +0100 Subject: [PATCH 213/220] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 6a279f60..3feb474b 100644 --- a/README.md +++ b/README.md @@ -58,8 +58,14 @@ If you're interested in Shairport Sync for OpenWrt, there's an OpenWrt package a An Arch Linux installation package is available (thanks!) at [EliaCereda/shairport-sync-PKGBUILD](https://github.com/EliaCereda/shairport-sync-PKGBUILD). **Fedora:** +Install the pre-requisites, if necessary. +``` +% sudo yum install alsa-lib-devel autoconf automake avahi-devel libconfig-devel libdaemon-deve; openssl-devel popt-devel soxr-devel +``` Download the tarball from the "releases" tab on github or use `wget` and then use `rpmbuild`. This example is for version 2.3.12: ``` +% sudo yum install alsa-lib-devel autoconf automake avahi-devel libconfig-devel libdaemon-deve; openssl-devel popt-devel soxr-devel + % wget -O shairport-sync-2.3.13.1.tar.gz https://github.com/mikebrady/shairport-sync/archive/2.3.13.1.tar.gz % rpmbuild -ta shairport-sync-2.3.13.1.tar.gz ``` From d756b8342ba73911901c50500a3615ec7b79209c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 26 Aug 2015 22:15:05 +0100 Subject: [PATCH 214/220] Update README.md --- README.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3feb474b..56270b8e 100644 --- a/README.md +++ b/README.md @@ -58,19 +58,28 @@ If you're interested in Shairport Sync for OpenWrt, there's an OpenWrt package a An Arch Linux installation package is available (thanks!) at [EliaCereda/shairport-sync-PKGBUILD](https://github.com/EliaCereda/shairport-sync-PKGBUILD). **Fedora:** -Install the pre-requisites, if necessary. +Install the toolchain and pre-requisites, if necessary: ``` +% sudo yum install make automake gcc gcc-c++ kernel-devel % sudo yum install alsa-lib-devel autoconf automake avahi-devel libconfig-devel libdaemon-deve; openssl-devel popt-devel soxr-devel ``` -Download the tarball from the "releases" tab on github or use `wget` and then use `rpmbuild`. This example is for version 2.3.12: +Download the tarball from the "releases" tab on github or use `wget` and then use `rpmbuild`. This example is for version 2.3.13.1: ``` -% sudo yum install alsa-lib-devel autoconf automake avahi-devel libconfig-devel libdaemon-deve; openssl-devel popt-devel soxr-devel - % wget -O shairport-sync-2.3.13.1.tar.gz https://github.com/mikebrady/shairport-sync/archive/2.3.13.1.tar.gz % rpmbuild -ta shairport-sync-2.3.13.1.tar.gz ``` The `-ta` means "build all from this tarball". (Thanks to https://github.com/p3ck for the script.) +The RPM will be built in a directory and will have a pathname like, for example, `~/rpmbuild/RPMS/i686/shairport-sync-2.3.13.1-1.fc22.i686.rpm` You should then install it with (for this example): +``` +%sudo rpm -i ~/rpmbuild/RPMS/i686/shairport-sync-2.3.13.1-1.fc22.i686.rpm +``` +You may have to manually create the directory `/var/shairport-sync` for the installation to succeed. Having edited the configuration file `/etc/shairport-sync.conf` as appropriate (see below), start the service with: +``` +%sudo systemctl start shairport-sync.service +``` + + **General Build Instructions** To build Shairport Sync from sources on Debian, Ubuntu, Raspian, etc. follow these instructions. From c7c2aaeb05ef7087b518353a4f034f38f4337892 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 26 Aug 2015 22:20:03 +0100 Subject: [PATCH 215/220] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 56270b8e..9032cb35 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ An Arch Linux installation package is available (thanks!) at [EliaCereda/shairp Install the toolchain and pre-requisites, if necessary: ``` % sudo yum install make automake gcc gcc-c++ kernel-devel -% sudo yum install alsa-lib-devel autoconf automake avahi-devel libconfig-devel libdaemon-deve; openssl-devel popt-devel soxr-devel +% sudo yum install alsa-lib-devel autoconf automake avahi-devel libconfig-devel libdaemon-devel openssl-devel popt-devel soxr-devel ``` Download the tarball from the "releases" tab on github or use `wget` and then use `rpmbuild`. This example is for version 2.3.13.1: ``` From 0ead6cbb0955a617c6dbfd85edb34ea590a5f1e4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 26 Aug 2015 22:25:10 +0100 Subject: [PATCH 216/220] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9032cb35..7e736f1d 100644 --- a/README.md +++ b/README.md @@ -33,13 +33,13 @@ What else? Status ------ -Shairport Sync works on a wide variety of Linux devices. It works on standard Ubuntu laptops, on the Raspberry Pi with Raspian, Arch Linux and OpenWrt, and it runs on a Linksys NSLU2 and a TP-Link 710N using OpenWrt. It works with built-in audio and with a variety of USB-connected audio amplifiers and DACs, including a cheapo USB "3D Sound" dongle, a first generation iMic and a Topping TP30 amplifier with a USB DAC input. +Shairport Sync works on a wide variety of Linux devices. It works on standard Ubuntu laptops, on the Raspberry Pi with Raspian, Arch Linux and OpenWrt, and it runs on a Linksys NSLU2 and a TP-Link 710N using OpenWrt. It works with built-in audio and with a variety of USB-connected audio amplifiers and DACs, including a cheapo USB "3D Sound" dongle, a first generation iMic and a Topping TP30 amplifier with a USB DAC input. It will not work properly – if at all – with a PulseAudio (pseudo-)output device. Shairport Sync runs well on the Raspberry Pi. It can drive the built-in sound card, though the audio out of the card is of poor quality. USB-connected sound cards work well on recent versions of Raspian; however older versions of Raspian appear to suffer from a problem — see http://www.raspberrypi.org/forums/viewtopic.php?t=23544, so it is wise to update. Shairport Sync works well with the IQAudIO Pi-DAC — see http://www.iqaudio.com. At the time of writing, OpenWrt trunk does not support USB audio well on the Raspberry Pi. -Shairport Sync runs on Ubuntu and Debian inside VMWare Fusion 7 on a Mac, but synchronisation does not work — possibly because the soundcard is being emulated. +Shairport Sync runs on Ubuntu, Debian, Fedora and OpenWrt inside VMWare Fusion 7 on a Mac, but synchronisation does not work — possibly because the soundcard is being emulated. Shairport Sync will output to alsa cards, to standard output and to pipes using appropriate backends. You can try compiling additional backends in as you wish, but it definitely will not work properly with them. Maybe someday... @@ -74,7 +74,7 @@ The RPM will be built in a directory and will have a pathname like, for example, ``` %sudo rpm -i ~/rpmbuild/RPMS/i686/shairport-sync-2.3.13.1-1.fc22.i686.rpm ``` -You may have to manually create the directory `/var/shairport-sync` for the installation to succeed. Having edited the configuration file `/etc/shairport-sync.conf` as appropriate (see below), start the service with: +You may have to manually create the directory `/var/shairport-sync` for the installation to succeed. Having edited the configuration file `/etc/shairport-sync.conf` as appropriate (see "Configuring Shairport Sync" below), start the service with: ``` %sudo systemctl start shairport-sync.service ``` From 45f492e7dace89de2604bad6644b91a9d30909df Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 26 Aug 2015 22:27:28 +0100 Subject: [PATCH 217/220] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7e736f1d..8bcc2b3a 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ There are four default latency settings, chosen automatically. One latency match Shairport Sync is a pretty substantial rewrite of the fantastic work done in Shairport 1.0 by James Laird and others — please see https://github.com/abrasive/shairport/blob/master/README.md#contributors-to-version-1x for a list of the contributors to Shairport 1.x and Shairport 0.x. From a "heritage" point of view, Shairport Sync is a fork of Shairport 1.0. -Shairport Sync is designed for Linux and ALSA. It must have direct access to the output device, which must be a sound card capable of working with 44,100 samples per second interleaved PCM stereo (you'll get a message in the logfile if there's a problem). +Shairport Sync is designed for Linux and ALSA. It must have direct access to the output device, which must be a real sound card capable of working with 44,100 samples per second interleaved PCM stereo (you'll get a message in the logfile if there's a problem). For more about the motivation behind Shairport Sync, please see the wiki at https://github.com/mikebrady/shairport-sync/wiki. @@ -45,7 +45,7 @@ Shairport Sync will output to alsa cards, to standard output and to pipes using For information about changes and updates, please refer to the RELEASENOTES.md file in the distribution. -Note: Historically, Shairport Sync has taken its settings from command line arguments. While this is still the case, it does not always work well across distributions. Accordingly, from version 2.4 onwards, Shairport Sync reads settings from the file `/etc/shairport-sync.conf`. Access to new settings will be provided only in the settings file. +Note: Historically, Shairport Sync has taken its settings from command line arguments. While this is still the case, it does not always work well across distributions. Accordingly, from version 2.4 onwards, Shairport Sync reads settings from the file `/etc/shairport-sync.conf`. Access to new features will only be provided via the settings file. Building And Installing --------------------- From 13e0fb1042c62532950241e77b369a4af622d841 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 26 Aug 2015 22:30:39 +0100 Subject: [PATCH 218/220] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8bcc2b3a..4a827c34 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Shairport Sync runs well on the Raspberry Pi. It can drive the built-in sound ca At the time of writing, OpenWrt trunk does not support USB audio well on the Raspberry Pi. -Shairport Sync runs on Ubuntu, Debian, Fedora and OpenWrt inside VMWare Fusion 7 on a Mac, but synchronisation does not work — possibly because the soundcard is being emulated. +Shairport Sync runs on Ubuntu, OpenWrt, Debian, Arch Linux and Fedora inside VMWare Fusion 7 on a Mac, but synchronisation in inaccurate — possibly because the soundcard is being emulated. Shairport Sync will output to alsa cards, to standard output and to pipes using appropriate backends. You can try compiling additional backends in as you wish, but it definitely will not work properly with them. Maybe someday... From 38a1dc3bcfd6dd3d766123f5520d97614e1f62cf Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 27 Aug 2015 19:50:05 +0100 Subject: [PATCH 219/220] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4a827c34..6c3a301c 100644 --- a/README.md +++ b/README.md @@ -74,8 +74,9 @@ The RPM will be built in a directory and will have a pathname like, for example, ``` %sudo rpm -i ~/rpmbuild/RPMS/i686/shairport-sync-2.3.13.1-1.fc22.i686.rpm ``` -You may have to manually create the directory `/var/shairport-sync` for the installation to succeed. Having edited the configuration file `/etc/shairport-sync.conf` as appropriate (see "Configuring Shairport Sync" below), start the service with: +You may have to manually create the directory `/var/shairport-sync` for the installation to succeed. Having edited the configuration file `/etc/shairport-sync.conf` as appropriate (see "Configuring Shairport Sync" below), enable and start the service with: ``` +%sudo systemctl enable shairport-sync.service %sudo systemctl start shairport-sync.service ``` From 24b831456e518e581e072c9173a2e854e01a37ae Mon Sep 17 00:00:00 2001 From: Mke Brady Date: Thu, 27 Aug 2015 21:36:05 +0100 Subject: [PATCH 220/220] Get ready for 2.4 stable release --- README.md | 25 ++++++++++++------------- RELEASENOTES.md | 22 +++++++++++++++++++++- configure.ac | 2 +- shairport-sync.spec | 4 +++- 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 6c3a301c..9b33c0aa 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -Shairport Sync (Development Branch) +Shairport Sync ============= Shairport Sync emulates an AirPort Express for the purpose of streaming audio from iTunes, iPods, iPhones, iPads and AppleTVs. Audio played by a Shairport Sync-powered device stays synchronised with the source and hence with similar devices playing the same source. In this way, synchronised multi-room audio is possible without difficulty. (Hence the name Shairport Sync, BTW.) Shairport Sync does not support AirPlay video or photo streaming. -This branch — "development" — is unstable. To access the stable branch, please switch to the "master" branch. +This branch -- "master" -- is the stable branch of Shairport Sync. To access the development version, please switch to the "development" branch. More Information ---------- @@ -39,7 +39,7 @@ Shairport Sync runs well on the Raspberry Pi. It can drive the built-in sound ca At the time of writing, OpenWrt trunk does not support USB audio well on the Raspberry Pi. -Shairport Sync runs on Ubuntu, OpenWrt, Debian, Arch Linux and Fedora inside VMWare Fusion 7 on a Mac, but synchronisation in inaccurate — possibly because the soundcard is being emulated. +Shairport Sync runs on Ubuntu, OpenWrt, Debian, Arch Linux and Fedora inside VMWare Fusion on a Mac, but synchronisation in inaccurate — possibly because the soundcard is being emulated. Shairport Sync will output to alsa cards, to standard output and to pipes using appropriate backends. You can try compiling additional backends in as you wish, but it definitely will not work properly with them. Maybe someday... @@ -55,7 +55,7 @@ If you wish to install Shairport Sync on OpenWrt, Arch or Fedora platforms, plea If you're interested in Shairport Sync for OpenWrt, there's an OpenWrt package at https://github.com/mikebrady/shairport-sync-for-openwrt. OpenWrt doesn't support the IQaudIO Pi-DAC. **Arch Linux:** -An Arch Linux installation package is available (thanks!) at [EliaCereda/shairport-sync-PKGBUILD](https://github.com/EliaCereda/shairport-sync-PKGBUILD). +An Arch Linux installation package is available at [EliaCereda/shairport-sync-PKGBUILD](https://github.com/EliaCereda/shairport-sync-PKGBUILD). **Fedora:** Install the toolchain and pre-requisites, if necessary: @@ -63,23 +63,23 @@ Install the toolchain and pre-requisites, if necessary: % sudo yum install make automake gcc gcc-c++ kernel-devel % sudo yum install alsa-lib-devel autoconf automake avahi-devel libconfig-devel libdaemon-devel openssl-devel popt-devel soxr-devel ``` -Download the tarball from the "releases" tab on github or use `wget` and then use `rpmbuild`. This example is for version 2.3.13.1: +Download the tarball from the "releases" tab on github or use `wget` and then use `rpmbuild`. This example is for version 2.4: ``` -% wget -O shairport-sync-2.3.13.1.tar.gz https://github.com/mikebrady/shairport-sync/archive/2.3.13.1.tar.gz -% rpmbuild -ta shairport-sync-2.3.13.1.tar.gz +% wget -O shairport-sync-2.4.tar.gz https://github.com/mikebrady/shairport-sync/archive/2.4.tar.gz +% rpmbuild -ta shairport-sync-2.4.tar.gz ``` -The `-ta` means "build all from this tarball". (Thanks to https://github.com/p3ck for the script.) +The `-ta` means "build all from this tarball". -The RPM will be built in a directory and will have a pathname like, for example, `~/rpmbuild/RPMS/i686/shairport-sync-2.3.13.1-1.fc22.i686.rpm` You should then install it with (for this example): +The RPM will be built in a directory and will have a pathname like, for example, `~/rpmbuild/RPMS/i686/shairport-sync-2.4-1.fc22.i686.rpm` You should then install it with (for this example): ``` -%sudo rpm -i ~/rpmbuild/RPMS/i686/shairport-sync-2.3.13.1-1.fc22.i686.rpm +%sudo rpm -i ~/rpmbuild/RPMS/i686/shairport-sync-2.4-1.fc22.i686.rpm ``` You may have to manually create the directory `/var/shairport-sync` for the installation to succeed. Having edited the configuration file `/etc/shairport-sync.conf` as appropriate (see "Configuring Shairport Sync" below), enable and start the service with: ``` %sudo systemctl enable shairport-sync.service %sudo systemctl start shairport-sync.service ``` - +Sincere thanks to all package contributors! **General Build Instructions** @@ -115,7 +115,6 @@ Download Shairport Sync: Next, `cd` into the shairport-sync directory and execute the following commands: ``` -$ git checkout development #switch to the development branch of shairport-sync $ autoreconf -i -f ``` @@ -157,7 +156,7 @@ use a specific sound card and mixer control, etc. — there are some examples in *Man Page* -You can view the man page here: http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/development/man/shairport-sync.html +You can view the man page here: http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/master/man/shairport-sync.html Configuring Shairport Sync diff --git a/RELEASENOTES.md b/RELEASENOTES.md index dff4db5f..de425aba 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,10 +1,30 @@ +Version 2.4 +---- +**Stable release** + +This stable release is the culmination of the 2.3.X sequence of development releases. + +**Change Summary** + +Changes from the previous stable version -- 2.2.5 -- are summarised here: + * Settings are now read from a configuration file. Command-line settings are supported but discouraged. + * Metadata is now supported -- it can be delivered to a unix pipe for processing by a helper application. See https://github.com/mikebrady/shairport-sync-metadata-reader for a sample metadata reader. + * Raw PCM audio can be delivered to standard output ("stdout") or to a unix pipe. The internal architecture has changed considerably to support this. + * Support for compilation on OpenWrt back to Attitude Adjustment. + * Version 2.4 uses the libconfig library. + * Runs on a wider range of platforms, including Arch Linux and Fedora. + * Bug fixes. + +Please note that building instructions have changed slightly from the previous version. +Also, the `-t hardware/software` option has been deprecated in the alsa back end. + Version 2.3.13 ---- **Note** * We're getting ready to release the development branch as the new, stable, master branch at 2.4. If you're packaging Shairport Sync, you might prefer to wait a short while as we add a little polish before the release. **Changes** -* Harmonise version numbers on the release and on the shairport.spec file used in Fedora. +* Harmonise version numbers on the release and on the `shairport.spec` file used in Fedora. Version 2.3.12 ---- diff --git a/configure.ac b/configure.ac index d199effa..4c3d3afd 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [2.3.13.1], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.4], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) diff --git a/shairport-sync.spec b/shairport-sync.spec index 1b3dc698..5c1308e8 100644 --- a/shairport-sync.spec +++ b/shairport-sync.spec @@ -1,5 +1,5 @@ Name: shairport-sync -Version: 2.3.13.1 +Version: 2.4 Release: 1%{?dist} Summary: AirTunes emulator. Shairport Sync adds multi-room capability with Audio Synchronisation @@ -57,6 +57,8 @@ getent passwd %{name} &> /dev/null || useradd --system -c "%{name} User" \ %doc AUTHORS LICENSES README.md %changelog +* Thu Aug 27 2015 Mike Brady 2.4 +- Prepare for stable release 2.4 * Wed Aug 26 2015 Mike Brady 2.3.13.1-1 - Harmonise release numbers * Fri Jul 24 2015 Bill Peck 2.3.7-1