From 46bcdce76483f1f0e73b9b065cbd68a10ff82659 Mon Sep 17 00:00:00 2001 From: mike Date: Sun, 24 Apr 2016 21:25:24 +0100 Subject: [PATCH 01/90] Added option for mono output --- common.h | 1 + player.c | 18 ++++++++++++++++++ shairport.c | 13 +++++++++++++ 3 files changed, 32 insertions(+) diff --git a/common.h b/common.h index 7520b0c0..25e24bbd 100644 --- a/common.h +++ b/common.h @@ -77,6 +77,7 @@ typedef struct { int32_t ForkedDaapdLatency; // supplied with --ForkedDaapdLatency option int daemonise; int statistics_requested,use_negotiated_latencies; + int mono; char *cmd_start, *cmd_stop; int cmd_blocking; int tolerance; // allow this much drift before attempting to correct it diff --git a/player.c b/player.c index a0903025..041b02d6 100644 --- a/player.c +++ b/player.c @@ -378,6 +378,24 @@ void player_put_packet(seq_t seqno, uint32_t timestamp, uint8_t *data, int len) abuf->ready = 1; abuf->timestamp = timestamp; abuf->sequence_number = seqno; + + if (config.mono) { + signed short *v = abuf->data; + int i; + int both; + for (i=frame_size;i;i--) { + int both = *v + *(v+1); + if (both > INT16_MAX) { + both = INT16_MAX; + } else if (both < INT16_MIN) { + both = INT16_MIN; + } + short sboth = (short)both; + *v++ = sboth; + *v++ = sboth; + } + } + } else { debug(1,"Bad audio packet detected and discarded."); abuf->ready = 0; diff --git a/shairport.c b/shairport.c index 931c270b..2bf7649d 100644 --- a/shairport.c +++ b/shairport.c @@ -207,6 +207,7 @@ void usage(char *progname) { 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(" --mono convert all outgoing audio to mono.\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"); @@ -253,6 +254,7 @@ int parse_options(int argc, char **argv) { {"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}, + {"mono", 0, POPT_ARG_NONE, &config.mono, 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}, @@ -411,6 +413,16 @@ int parse_options(int argc, char **argv) { die("Invalid ignore_volume_control option choice \"%s\". It should be \"yes\" or \"no\""); } + /* Get the mono setting */ + if (config_lookup_string(config.cfg, "general.mono", &str)) { + if (strcasecmp(str, "no") == 0) + config.mono = 0; + else if (strcasecmp(str, "yes") == 0) + config.mono = 1; + else + die("Invalid mono option choice \"%s\". It should be \"yes\" or \"no\""); + } + /* Get the regtype -- the service type and protocol, separated by a dot. Default is "_raop._tcp" */ if (config_lookup_string(config.cfg, "general.regtype", &str)) config.regtype = strdup(str); @@ -960,6 +972,7 @@ int main(int argc, char **argv) { debug(1, "drift tolerance is %d frames.", config.tolerance); debug(1, "password is \"%s\".", config.password); debug(1, "ignore_volume_control is %d.", config.ignore_volume_control); + debug(1, "mono is %d.", config.mono); debug(1, "disable_synchronization is %d.", config.no_sync); debug(1, "audio backend desired buffer length is %d.", config.audio_backend_buffer_desired_length); From d21f9e046f846a6949155ee197a3bbfab14c5126 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 25 Apr 2016 09:21:02 +0200 Subject: [PATCH 02/90] Update to reflect development status --- README.md | 36 +++--------------------------------- configure.ac | 2 +- 2 files changed, 4 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index f2066ae8..92998d65 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 is the stable "master" branch. Changes and updates are incorporated into this branch relatively slowly. To access the development version, where all the latest changes are made first, please switch to the "development" branch. +This is the unstable "development" branch. Changes and updates are incorporated into this branch quickly. To access the stable version, where changes are made after due time, please switch to the "master" branch. More Information ---------- @@ -49,42 +49,11 @@ For information about changes and updates, please refer to the RELEASENOTES.md f 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 +Building And Installing the Development Version --------------------- -If you wish to install Shairport Sync on OpenWrt, Arch or Fedora platforms, please follow the appropriate instructions below. Limited support is also available for Mac OS X. Otherwise follow the General Build Instructions. Then, when the program has been installed, refer to the section on Configuring Shairport Sync that follows. - -**Note** - The following procedures will install the shairport-sync application into your system. Before continuing, you should check to see if shairport-sync is already installed – you can use `which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere. (If the existing installation of shairport-sync is where the new copy will be installed into, it will be overwritten; sometimes, however, the installation is to another location, so it is safer, initially, to delete previous versions manually.) -**Ubuntu:** -Personal Package Archives for Shairport Sync master and development branches are available at https://launchpad.net/~dantheperson. A `shairport-sync` installer package is available in Ubuntu 16.04, currently in its late beta phase. - -**Debian:** -`shairport-sync` is in the Debian archive and is scheduled for release with Debian Stretch (9): https://tracker.debian.org/shairport-sync. A backport for Debian Jessie (8) may be provided given enough demand. - -**OpenWrt:** -There is a Shairport Sync package in OpenWrt `trunk`. Also, there's an OpenWrt package at https://github.com/mikebrady/shairport-sync-for-openwrt, including one that builds back to `Barrier Breaker`. - -**Arch Linux:** -Shairport Sync is available for `x86_64` and `i686` platforms in the Arch Linux Community Repository -- search for `shairport-sync`. See also https://www.archlinux.org/packages/. - -An Arch Linux installation package, suitable for compilation on any platform, is available at [EliaCereda/shairport-sync-PKGBUILD](https://github.com/EliaCereda/shairport-sync-PKGBUILD). - -**Mac OS X:** -A [HomeBrew](http://brew.sh) package exists for Shairport Sync. With HomeBrew installed, Shairport Sync can be installed using the command `$brew install shairport-sync`. Note that the installation uses the `libao` library and so synchronisation is not available — playback glitches will occur occasionally, when the `ao` system's buffers overflow or underflow. - -**Fedora:** -Please see the guide at [FEDORA.md](https://github.com/mikebrady/shairport-sync/blob/master/FEDORA.md). - -**Cygwin:** -Please see the guide at [CYGWIN.md](https://github.com/mikebrady/shairport-sync/blob/master/CYGWIN.md). - -Sincere thanks to all package contributors! - -**General Build Instructions** - To build Shairport Sync from sources on Debian, Ubuntu, Raspbian, etc. follow these instructions. The following libraries are required: @@ -119,6 +88,7 @@ Download Shairport Sync: Next, `cd` into the shairport-sync directory and execute the following command: ``` +$ git checkout development $ autoreconf -i -f ``` (Note that the `autoreconf...` step may take some time on less powerful machines.) diff --git a/configure.ac b/configure.ac index fe28922e..20113f06 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.8.3], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.8.3.1], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 5c5c25324f8698b07330bac6508f65143f7c4531 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 25 Apr 2016 09:27:04 +0200 Subject: [PATCH 03/90] Update RELEASENOTES.md --- RELEASENOTES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 26de412e..599d6d98 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,7 @@ +Version 2.8.3.1 – Development Version +---- +Added a new `mono` setting -- thanks to [faceless2](https://github.com/faceless2). Documentation to follow. + Version 2.8.3 – Stable Version ---- A bug in 2.8.2 caused Avahi to fail at startup under some circumstances with older installations. The problem was that sometimes the `regtype` setting would not be initialised properly. From 52376ff43cc777a28cd93fc276a5fd33b7510199 Mon Sep 17 00:00:00 2001 From: mike Date: Mon, 25 Apr 2016 12:03:39 +0100 Subject: [PATCH 04/90] Check for null regtype --- mdns_tinysvcmdns.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mdns_tinysvcmdns.c b/mdns_tinysvcmdns.c index 60bdf360..049737b4 100644 --- a/mdns_tinysvcmdns.c +++ b/mdns_tinysvcmdns.c @@ -125,6 +125,9 @@ static int mdns_tinysvcmdns_register(char *apname, int port) { txt = txtwithoutmetadata; + if (config.regtype == NULL) + die("tinysvcmdns: regtype is null"); + char* extendedregtype = malloc(strlen(config.regtype)+strlen(".local")+1); if (extendedregtype==NULL) From d02bb0e336e1df88058ad55e61834973b9adc4fd Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 27 Apr 2016 10:23:15 +0200 Subject: [PATCH 05/90] If the volume is out of range 0 -- -30 or -144, return minimum vol --- common.c | 1 + 1 file changed, 1 insertion(+) diff --git a/common.c b/common.c index bc077732..a86fd8e2 100644 --- a/common.c +++ b/common.c @@ -465,6 +465,7 @@ double vol2attn(double vol, long max_db, long min_db) { } else if (vol != -144.0) { debug(1, "Volume request value %f is out of range: should be from 0.0 to -30.0 or -144.0.", vol); + vol_setting = min_db; // for safety, return the lowest setting... } else { vol_setting = min_db; // for safety, return the lowest setting... } From fd56780575f5a255062e4faf51759f79bdfb07f0 Mon Sep 17 00:00:00 2001 From: mike Date: Wed, 27 Apr 2016 12:09:19 +0100 Subject: [PATCH 06/90] Added option to broadcast metadata over UDP --- README.md | 10 +++++++++- common.h | 3 +++ rtsp.c | 35 +++++++++++++++++++++++++++++++++++ scripts/shairport-sync.conf | 3 +++ shairport.c | 13 +++++++++++++ 5 files changed, 63 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 92998d65..af63b91f 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ What else? * Hardware Mute — Shairport Sync will mute properly if the hardware supports it. * Fast Response — With hardware volume control, response is instantaneous; otherwise the response time is 0.15 seconds. * 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 deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in iOS. +* Metadata — Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in iOS. * Raw Audio — Shairport Sync can deliver raw PCM audio to standard output or to a pipe. This output is delivered synchronously with the source after the appropriate latency and is not interpolated or "stuffed" on its way through Shairport Sync. * Autotools and Libtool Support — the Shairport Sync build process uses GNU `autotools` and `libtool` to examine and configure the build environment — important for portability and for cross compilation. Previous versions of Shairport looked at the current system to determine which packages were available, instead of looking at the target system for what packages were available. @@ -342,6 +342,14 @@ alsa = { }; ``` +Metadata broadcasting over UDP +------------------------------ +As an alternative to sending metadata to a pipe, the `socket_address` and `socket_port` tags may be set in the metadata group to cause Shairport Sync to broadcast UDP packets containing the track metadata. + +The advantage of UDP is that packets can be sent to a single listener or, if a multicast address is used, to multiple listeners. It also allows metadata to be routed to a different host. However UDP has a maximum packet size of about 65000 bytes; while large enough for most data, Cover Art will often exceed this value. Any metadata exceeding this limit will not be sent over the socket interface. The maximum packet size may be set with the `socket_msglength` tag to any value between 500 and 65000 to control this - lower values may be used to ensure that each UDP packet is sent in a single network frame. The default is 500. Other than this restriction, metadata sent over the socket interface is identical to metadata sent over the pipe interface. + +The UDP metadata format is very simple - the first four bytes are the metadata *type*, and the next four bytes are the metadata *code* (both are sent in network byte order - see https://github.com/mikebrady/shairport-sync-metadata-reader for a definition of those terms). The remaining bytes of the packet, if any, make up the raw value of the metadata. + Latency ------- Latency is the exact time from a sound signal's original timestamp until that signal actually "appears" on the output of the audio output device, usually a Digital to Audio Converter (DAC), irrespective of any internal delays, processing times, etc. in the computer. diff --git a/common.h b/common.h index 25e24bbd..1b0cf639 100644 --- a/common.h +++ b/common.h @@ -50,6 +50,9 @@ typedef struct { #ifdef CONFIG_METADATA int metadata_enabled; char *metadata_pipename; + char *metadata_sockaddr; + int metadata_sockport; + int metadata_sockmsglength; int get_coverart; #endif uint8_t hw_addr[6]; diff --git a/rtsp.c b/rtsp.c index 66cd9b9b..4d973009 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1023,6 +1023,9 @@ char *base64_encode_so(const unsigned char *data, size_t input_length, static int fd = -1; static int dirty = 0; pc_queue metadata_queue; +static int metadata_sock; +static struct sockaddr_in metadata_sockaddr; +static char *metadata_sockmsg; #define metadata_queue_size 500 metadata_package metadata_queue_items[metadata_queue_size]; @@ -1032,6 +1035,24 @@ void metadata_create(void) { if (config.metadata_enabled == 0) return; + // Unlike metadata pipe, socket is opened once and stays open, + // so we can call it in create + if (config.metadata_sockaddr && config.metadata_sockport) { + metadata_sock = socket(AF_INET, SOCK_DGRAM, 0); + if (metadata_sock < 0) { + debug(1, "Could not open metadata socket"); + } else { + bzero((char *)&metadata_sockaddr, sizeof(metadata_sockaddr)); + metadata_sockaddr.sin_family = AF_INET; + metadata_sockaddr.sin_addr.s_addr = inet_addr(config.metadata_sockaddr); + metadata_sockaddr.sin_port = htons(config.metadata_sockport); + if (!(metadata_sockmsg = malloc(config.metadata_sockmsglength))) { + die("Could not malloc metadata socket buffer"); + } + memset(metadata_sockmsg, 0, config.metadata_sockmsglength); + } + } + size_t pl = strlen(config.metadata_pipename) + 1; char *path = malloc(pl + 1); @@ -1070,6 +1091,20 @@ void metadata_process(uint32_t type, uint32_t code, char *data, debug(2, "Process metadata with type %x, code %x and length %u.", type, code, length); int ret; + + if (metadata_sock >= 0 && length < config.metadata_sockmsglength - 8) { + char *ptr = metadata_sockmsg; + uint32_t v; + v = htonl(type); + memcpy(ptr, &v, 4); + ptr += 4; + v = htonl(code); + memcpy(ptr, &v, 4); + ptr += 4; + memcpy(ptr, data, length); + sendto(metadata_sock, metadata_sockmsg, length + 8, 0, (struct sockaddr *)&metadata_sockaddr, sizeof(metadata_sockaddr)); + } + // readers may go away and come back if (fd < 0) metadata_open(); diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 050ee364..a75298b6 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -27,6 +27,9 @@ metadata = // 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"; +// socket_address = "226.0.0.1"; // if set to a host name or IP address, UDP packets containing metadata will be sent to this address. May be a multicast address. "socket-port" must be non-zero and "enabled" must be set to yes" +// socket_port = "5555"; // if socket_address is set, the port to send UDP packets to +// socket_msglength = "65000"; // the maximum packet size for any UDP metadata. This will be clipped to be between 500 or 65000. The default is 500. }; // Advanced parameters for controlling how a Shairport Sync runs diff --git a/shairport.c b/shairport.c index b50fa0a8..041986c5 100644 --- a/shairport.c +++ b/shairport.c @@ -477,6 +477,17 @@ int parse_options(int argc, char **argv) { if (config_lookup_string(config.cfg, "metadata.pipe_name", &str)) { config.metadata_pipename = (char *)str; } + + if (config_lookup_string(config.cfg, "metadata.socket_address", &str)) { + config.metadata_sockaddr = (char *)str; + } + if (config_lookup_int(config.cfg, "metadata.socket_port", &value)) { + config.metadata_sockport = value; + } + config.metadata_sockmsglength = 500; + if (config_lookup_int(config.cfg, "metadata.socket_msglength", &value)) { + config.metadata_sockmsglength = value < 500 ? 500 : value > 65000 ? 65000 : value; + } #endif if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_play_begins", &str)) { @@ -991,6 +1002,8 @@ int main(int argc, char **argv) { #ifdef CONFIG_METADATA debug(1, "metdata enabled is %d.", config.metadata_enabled); debug(1, "metadata pipename is \"%s\".", config.metadata_pipename); + debug(1, "metadata socket address is \"%s\" port %d.", config.metadata_sockaddr, config.metadata_sockport); + debug(1, "metadata socket packet size is \"%d\".", config.metadata_sockmsglength); debug(1, "get-coverart is %d.", config.get_coverart); #endif From c886b9a9e771150286e4dd0ce6deb989ce97151b Mon Sep 17 00:00:00 2001 From: rkam Date: Sat, 30 Apr 2016 19:14:18 -0700 Subject: [PATCH 07/90] Init metadata_sock to -1 so it doesn't look like an fd. --- rtsp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtsp.c b/rtsp.c index 4d973009..e8867042 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1023,7 +1023,7 @@ char *base64_encode_so(const unsigned char *data, size_t input_length, static int fd = -1; static int dirty = 0; pc_queue metadata_queue; -static int metadata_sock; +static int metadata_sock = -1; static struct sockaddr_in metadata_sockaddr; static char *metadata_sockmsg; #define metadata_queue_size 500 From ac68037782f6cee1ac44c9e49bdc728a8a610dd2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 1 May 2016 22:36:19 +0100 Subject: [PATCH 08/90] Add a local argument fo rtp_setup so we know what local ip address to use --- rtp.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtp.h b/rtp.h index 9adfb534..446142d0 100644 --- a/rtp.h +++ b/rtp.h @@ -13,7 +13,7 @@ void *rtp_audio_receiver(void *arg); void *rtp_control_receiver(void *arg); void *rtp_timing_receiver(void *arg); -void rtp_setup(SOCKADDR *remote, int controlport, int timingport, uint32_t active_remote, +void rtp_setup(SOCKADDR *local, 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); From 5028cd1f9fc59f6f3d1e5ab7381773fa654a4606 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 1 May 2016 22:37:57 +0100 Subject: [PATCH 09/90] Add information about local ip address so we can send back connections to the client from the same ip number it used to contact us --- rtp.c | 91 ++++++++++++++++++++++++++++------------------------------ rtsp.c | 40 ++++++++++++++++++++++++-- 2 files changed, 81 insertions(+), 50 deletions(-) diff --git a/rtp.c b/rtp.c index 79aefa4b..501dc66d 100644 --- a/rtp.c +++ b/rtp.c @@ -59,7 +59,8 @@ typedef struct time_ping_record { static int running = 0; static char client_ip_string[INET6_ADDRSTRLEN]; // the ip string pointing to the client -static short client_ip_family; // AF_INET / AF_INET6 +static char self_ip_string[INET6_ADDRSTRLEN]; // the ip string being used by this program -- it could be one of many, so we need to know it +static short connection_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 @@ -543,47 +544,27 @@ void *rtp_timing_receiver(void *arg) { return NULL; } -static int bind_port(SOCKADDR *remote, int *sock) { +static int bind_port(int ip_family,const char *self_ip_address, int *sock) { struct addrinfo hints, *info; memset(&hints, 0, sizeof(hints)); - hints.ai_family = remote->SAFAMILY; + hints.ai_family = ip_family; hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = AI_PASSIVE; - char buffer[10]; + char buffer[20]; // 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); - - ret = getaddrinfo(NULL, buffer, &hints, &info); - + ret = getaddrinfo(self_ip_address, buffer, &hints, &info); if (ret < 0) die("failed to get usable addrinfo?! %s.", gai_strerror(ret)); - - *sock = socket(remote->SAFAMILY, SOCK_DGRAM, IPPROTO_UDP); - -/* - // this doesn't compile properly with OpenWrt Barrier Breaker. - #if defined(__linux__) - #ifdef AF_INET6 - // now, if we are on IPv6, prefer a public ipv6 address - if (remote->SAFAMILY==AF_INET6) { - int value = IPV6_PREFER_SRC_PUBLIC; - ret = setsockopt(*sock, IPPROTO_IPV6, IPV6_ADDR_PREFERENCES, &value, sizeof(value)); - if (ret<0) - die("error: could not select a preference for public IPv6 address"); - } - #endif - #endif -*/ + *sock = socket(ip_family, SOCK_DGRAM, IPPROTO_UDP); ret = bind(*sock, info->ai_addr, info->ai_addrlen); - 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); @@ -592,7 +573,6 @@ static int bind_port(SOCKADDR *remote, int *sock) { die("error: could not bind a UDP port!"); } - int sport; SOCKADDR local; socklen_t local_len = sizeof(local); @@ -611,8 +591,13 @@ static int bind_port(SOCKADDR *remote, int *sock) { return sport; } -void rtp_setup(SOCKADDR *remote, int cport, int tport, uint32_t active_remote, int *lsport, +void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t active_remote, int *lsport, int *lcport, int *ltport) { + + // this gets the local and remote ip numbers (and ports used for the TCD stuff) + // we use the local stuff to specify the address we are coming from and + // we use the remote stuff to specify where we're goint to + if (running) die("rtp_setup called with active stream!"); @@ -621,28 +606,39 @@ void rtp_setup(SOCKADDR *remote, int cport, int tport, uint32_t active_remote, i client_active_remote = active_remote; // print out what we know about the client - void *addr; + void *client_addr,*self_addr; char *ipver; - int port; - char portstr[20]; - client_ip_family = remote->SAFAMILY; // keep information about the kind of ip of the client + int client_port,self_port; + char client_port_str[20]; + char self_addr_str[20]; + + connection_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); + if (connection_ip_family == AF_INET6) { ipver = "IPv6"; + struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)remote; + client_addr = &(sa6->sin6_addr); + client_port = ntohs(sa6->sin6_port); + sa6 = (struct sockaddr_in6 *)local; + self_addr = &(sa6->sin6_addr); + self_port = ntohs(sa6->sin6_port); } #endif - if (remote->SAFAMILY == AF_INET) { - struct sockaddr_in *sa4 = (struct sockaddr_in *)remote; - addr = &(sa4->sin_addr); - port = ntohs(sa4->sin_port); + if (connection_ip_family == AF_INET) { ipver = "IPv4"; + struct sockaddr_in *sa4 = (struct sockaddr_in *)remote; + client_addr = &(sa4->sin_addr); + client_port = ntohs(sa4->sin_port); + sa4 = (struct sockaddr_in *)local; + self_addr = &(sa4->sin_addr); + self_port = ntohs(sa4->sin_port); } - inet_ntop(remote->SAFAMILY, addr, client_ip_string, + inet_ntop(connection_ip_family, client_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); + inet_ntop(connection_ip_family, self_addr, self_ip_string, + sizeof(self_ip_string)); // keep the client's ip number + debug(1, "Connection via %s from AirPlay client at: %s:%u to this Shairport Sync AirPlay server at: %s:%u.", ipver, client_ip_string, client_port,self_ip_string,self_port); // set up a the record of the remote's control socket struct addrinfo hints; @@ -650,8 +646,9 @@ void rtp_setup(SOCKADDR *remote, int cport, int tport, uint32_t active_remote, i memset(&rtp_client_control_socket, 0, sizeof(rtp_client_control_socket)); memset(&hints, 0, sizeof hints); - hints.ai_family = remote->SAFAMILY; + hints.ai_family = connection_ip_family; hints.ai_socktype = SOCK_DGRAM; + char portstr[20]; snprintf(portstr, 20, "%d", cport); if (getaddrinfo(client_ip_string, portstr, &hints, &servinfo) != 0) die("Can't get address of client's control port"); @@ -667,7 +664,7 @@ void rtp_setup(SOCKADDR *remote, int cport, int tport, uint32_t active_remote, i // 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_family = connection_ip_family; hints.ai_socktype = SOCK_DGRAM; snprintf(portstr, 20, "%d", tport); if (getaddrinfo(client_ip_string, portstr, &hints, &servinfo) != 0) @@ -683,9 +680,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); - *lcport = bind_port(remote, &control_socket); - *ltport = bind_port(remote, &timing_socket); + *lsport = bind_port(connection_ip_family,self_ip_string,&audio_socket); + *lcport = bind_port(connection_ip_family,self_ip_string,&control_socket); + *ltport = bind_port(connection_ip_family,self_ip_string,&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 e8867042..f101affe 100644 --- a/rtsp.c +++ b/rtsp.c @@ -89,7 +89,7 @@ static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER; typedef struct { int fd; stream_cfg stream; - SOCKADDR remote; + SOCKADDR remote,local; int stop; int running; pthread_t thread; @@ -806,7 +806,7 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, tport = atoi(p); // rtsp_take_player(); - rtp_setup(&conn->remote, cport, tport, active_remote, &lsport, &lcport, + rtp_setup(&conn->local, &conn->remote, cport, tport, active_remote, &lsport, &lcport, <port); if (!lsport) goto error; @@ -1904,12 +1904,46 @@ void rtsp_listen_loop(void) { memset(conn, 0, sizeof(rtsp_conn_info)); socklen_t slen = sizeof(conn->remote); - debug(1, "New RTSP connection on port %d", config.port); conn->fd = accept(acceptfd, (struct sockaddr *)&conn->remote, &slen); if (conn->fd < 0) { + debug(1, "New RTSP connection on port %d not accepted:", config.port); perror("failed to accept connection"); free(conn); } else { + SOCKADDR *local_info = (SOCKADDR*)&conn->local; + socklen_t size_of_reply = sizeof(*local_info); + memset(local_info,0,sizeof(SOCKADDR)); + if (getsockname(conn->fd, (struct sockaddr*)local_info, &size_of_reply)==0) { + char host[1024]; + char service[20]; + + // IPv4: + if (local_info->SAFAMILY==AF_INET) { + char ip4[INET_ADDRSTRLEN]; // space to hold the IPv4 string + struct sockaddr_in *sa = (struct sockaddr_in*)local_info; // pretend this is loaded with something + + inet_ntop(AF_INET, &(sa->sin_addr), ip4, INET_ADDRSTRLEN); + unsigned short int tport = ntohs(sa->sin_port); + debug(1,"New RTSP connection at: %s:%u", ip4,tport); + } +#ifdef AF_INET6 + if (local_info->SAFAMILY==AF_INET6) { + // IPv6: + + char ip6[INET6_ADDRSTRLEN]; // space to hold the IPv6 string + struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)local_info; ; // pretend this is loaded with something + + inet_ntop(AF_INET6, &(sa6->sin6_addr), ip6, INET6_ADDRSTRLEN); + u_int16_t tport = ntohs(sa6->sin6_port); + + debug(1,"New RTSP connection at: %s:%u", ip6,tport); + } + #endif + + } else { + debug(1,"Error figuring out Shairport Sync's own ipnumber"); + } + usleep(500000); pthread_t rtsp_conversation_thread; ret = pthread_create(&rtsp_conversation_thread, NULL, From 1bb56b4ca10083d299f28b8b03e248eab937da93 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 2 May 2016 09:07:39 +0100 Subject: [PATCH 10/90] Change mono setting to playback_mode setting and add to config file. --- common.h | 7 ++++++- player.c | 2 +- scripts/shairport-sync.conf | 1 + shairport.c | 20 +++++++++----------- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/common.h b/common.h index 1b0cf639..6e5331d5 100644 --- a/common.h +++ b/common.h @@ -43,6 +43,11 @@ enum stuffing_type { ST_soxr, } type; +enum playback_mode_type { + ST_stereo = 0, + ST_mono, +} playback_mode_type; + typedef struct { config_t *cfg; char *password; @@ -80,7 +85,7 @@ typedef struct { int32_t ForkedDaapdLatency; // supplied with --ForkedDaapdLatency option int daemonise; int statistics_requested,use_negotiated_latencies; - int mono; + enum playback_mode_type playback_mode; char *cmd_start, *cmd_stop; int cmd_blocking; int tolerance; // allow this much drift before attempting to correct it diff --git a/player.c b/player.c index 041b02d6..1a2cf1a6 100644 --- a/player.c +++ b/player.c @@ -379,7 +379,7 @@ void player_put_packet(seq_t seqno, uint32_t timestamp, uint8_t *data, int len) abuf->timestamp = timestamp; abuf->sequence_number = seqno; - if (config.mono) { + if (config.playback_mode==ST_mono) { signed short *v = abuf->data; int i; int both; diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index a75298b6..2f185669 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -19,6 +19,7 @@ general = // 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. // volume_range_db = 60 ; // use this to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range. // regtype = "_raop._tcp"; // Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp". +// playback_mode = "stereo"; // This can be "stereo" or "mono". Default is "stereo". }; // How to deal with metadata, including artwork diff --git a/shairport.c b/shairport.c index 041986c5..8be3a35e 100644 --- a/shairport.c +++ b/shairport.c @@ -207,7 +207,6 @@ void usage(char *progname) { 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(" --mono convert all outgoing audio to mono.\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"); @@ -254,7 +253,6 @@ int parse_options(int argc, char **argv) { {"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}, - {"mono", 0, POPT_ARG_NONE, &config.mono, 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}, @@ -413,14 +411,14 @@ int parse_options(int argc, char **argv) { die("Invalid ignore_volume_control option choice \"%s\". It should be \"yes\" or \"no\""); } - /* Get the mono setting */ - if (config_lookup_string(config.cfg, "general.mono", &str)) { - if (strcasecmp(str, "no") == 0) - config.mono = 0; - else if (strcasecmp(str, "yes") == 0) - config.mono = 1; + /* Get the playback_mode setting */ + if (config_lookup_string(config.cfg, "general.playback_mode", &str)) { + if (strcasecmp(str, "stereo") == 0) + config.playback_mode = ST_stereo; + else if (strcasecmp(str, "mono") == 0) + config.playback_mode = ST_mono; else - die("Invalid mono option choice \"%s\". It should be \"yes\" or \"no\""); + die("Invalid playback_mode choice \"%s\". It should be \"stereo\" (default) or \"mono\""); } /* Get the regtype -- the service type and protocol, separated by a dot. Default is "_raop._tcp" */ @@ -977,14 +975,14 @@ int main(int argc, char **argv) { debug(2, "AirPlayLatency is %d.", config.AirPlayLatency); debug(2, "iTunesLatency is %d.", config.iTunesLatency); debug(2, "forkedDaapdLatency is %d.", config.ForkedDaapdLatency); - debug(1, "stuffing option is \"%d\".", config.packet_stuffing); + debug(1, "stuffing option is \"%d\" (0-basic, 1-soxr).", config.packet_stuffing); debug(1, "resync time is %d.", config.resyncthreshold); debug(1, "allow a session to be interrupted: %d.", config.allow_session_interruption); debug(1, "busy timeout time is %d.", config.timeout); debug(1, "drift tolerance is %d frames.", config.tolerance); debug(1, "password is \"%s\".", config.password); debug(1, "ignore_volume_control is %d.", config.ignore_volume_control); - debug(1, "mono is %d.", config.mono); + debug(1, "playback_mode is %d (0-stereo, 1-mono).", config.playback_mode); debug(1, "disable_synchronization is %d.", config.no_sync); debug(1, "audio backend desired buffer length is %d.", config.audio_backend_buffer_desired_length); From c50af16abb8a3eb7f2ab9c1e9f8e7a08fa270db1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 2 May 2016 09:45:00 +0100 Subject: [PATCH 11/90] Disable avahi browser function --- mdns_avahi.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mdns_avahi.c b/mdns_avahi.c index 8efc6207..065322d7 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -287,11 +287,11 @@ static int avahi_register(char *srvname, int srvport) { return -1; } - // we need this to detect the IPv6 number we're advertising... - if (!(sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, config.regtype, NULL, 0, browse_callback, client))) { - warn("Failed to create service browser: %s\n", avahi_strerror(avahi_client_errno(client))); - return -1; - } + // we need this to detect the IPv6 number we're advertising... + // if (!(sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, config.regtype, NULL, 0, browse_callback, client))) { + // warn("Failed to create service browser: %s\n", avahi_strerror(avahi_client_errno(client))); + // return -1; + // } if (avahi_threaded_poll_start(tpoll) < 0) { From 1fd794af719aff52be149c96c1d11b653aa44136 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 2 May 2016 09:45:44 +0100 Subject: [PATCH 12/90] tidy up diagnostic messages --- rtp.c | 16 ++++++++-------- rtsp.c | 25 +++++++++++++++---------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/rtp.c b/rtp.c index 501dc66d..1ffd7633 100644 --- a/rtp.c +++ b/rtp.c @@ -607,16 +607,14 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t // print out what we know about the client void *client_addr,*self_addr; - char *ipver; int client_port,self_port; - char client_port_str[20]; - char self_addr_str[20]; + char client_port_str[64]; + char self_addr_str[64]; connection_ip_family = remote->SAFAMILY; // keep information about the kind of ip of the client #ifdef AF_INET6 if (connection_ip_family == AF_INET6) { - ipver = "IPv6"; struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)remote; client_addr = &(sa6->sin6_addr); client_port = ntohs(sa6->sin6_port); @@ -626,7 +624,6 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t } #endif if (connection_ip_family == AF_INET) { - ipver = "IPv4"; struct sockaddr_in *sa4 = (struct sockaddr_in *)remote; client_addr = &(sa4->sin_addr); client_port = ntohs(sa4->sin_port); @@ -634,11 +631,14 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t self_addr = &(sa4->sin_addr); self_port = ntohs(sa4->sin_port); } + inet_ntop(connection_ip_family, client_addr, client_ip_string, - sizeof(client_ip_string)); // keep the client's ip number + sizeof(client_ip_string)); inet_ntop(connection_ip_family, self_addr, self_ip_string, - sizeof(self_ip_string)); // keep the client's ip number - debug(1, "Connection via %s from AirPlay client at: %s:%u to this Shairport Sync AirPlay server at: %s:%u.", ipver, client_ip_string, client_port,self_ip_string,self_port); + sizeof(self_ip_string)); + + debug(1, "Set up play connection from %s to self at %s.", client_ip_string,self_ip_string); + // set up a the record of the remote's control socket struct addrinfo hints; diff --git a/rtsp.c b/rtsp.c index f101affe..af0f8f78 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1914,34 +1914,39 @@ void rtsp_listen_loop(void) { socklen_t size_of_reply = sizeof(*local_info); memset(local_info,0,sizeof(SOCKADDR)); if (getsockname(conn->fd, (struct sockaddr*)local_info, &size_of_reply)==0) { - char host[1024]; - char service[20]; // IPv4: if (local_info->SAFAMILY==AF_INET) { char ip4[INET_ADDRSTRLEN]; // space to hold the IPv4 string - struct sockaddr_in *sa = (struct sockaddr_in*)local_info; // pretend this is loaded with something - + char remote_ip4[INET_ADDRSTRLEN]; // space to hold the IPv4 string + struct sockaddr_in *sa = (struct sockaddr_in*)local_info; inet_ntop(AF_INET, &(sa->sin_addr), ip4, INET_ADDRSTRLEN); unsigned short int tport = ntohs(sa->sin_port); - debug(1,"New RTSP connection at: %s:%u", ip4,tport); + sa = (struct sockaddr_in*)&conn->remote; + inet_ntop(AF_INET, &(sa->sin_addr), remote_ip4, INET_ADDRSTRLEN); + unsigned short int rport = ntohs(sa->sin_port); + debug(1,"New RTSP connection from %s:%u to self at %s:%u.",remote_ip4,rport,ip4,tport); } #ifdef AF_INET6 if (local_info->SAFAMILY==AF_INET6) { // IPv6: char ip6[INET6_ADDRSTRLEN]; // space to hold the IPv6 string - struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)local_info; ; // pretend this is loaded with something - + char remote_ip6[INET6_ADDRSTRLEN]; // space to hold the IPv6 string + struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)local_info; // pretend this is loaded with something inet_ntop(AF_INET6, &(sa6->sin6_addr), ip6, INET6_ADDRSTRLEN); u_int16_t tport = ntohs(sa6->sin6_port); - - debug(1,"New RTSP connection at: %s:%u", ip6,tport); + + sa6 = (struct sockaddr_in6*)&conn->remote; // pretend this is loaded with something + inet_ntop(AF_INET6, &(sa6->sin6_addr), remote_ip6, INET6_ADDRSTRLEN); + u_int16_t rport = ntohs(sa6->sin6_port); + + debug(1,"New RTSP connection from [%s]:%u to self at [%s]:%u.",remote_ip6,rport,ip6,tport); } #endif } else { - debug(1,"Error figuring out Shairport Sync's own ipnumber"); + debug(1,"Error figuring out Shairport Sync's own IP number."); } usleep(500000); From 4a45244804efb2a3c07ae401408a75e60952d474 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 2 May 2016 09:47:50 +0100 Subject: [PATCH 13/90] To reflect changes -- always use the IP number we started with, playback_mode instead of mono --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 20113f06..7eb521d1 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.8.3.1], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.8.3.2], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From dc591cb073845a0abf5884d72455f43006e0e97a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 2 May 2016 10:33:55 +0100 Subject: [PATCH 14/90] Update RELEASENOTES.md --- RELEASENOTES.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 599d6d98..a4d52892 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,10 @@ +Version 2.8.3.2 – Unstable! Development Version +---- +**Bug Fix** +The networking subsystem has been modified to always use the same IP number during a session. Background: the computer Shairport Sync is running on can have may IP numbers active at the same time -- various IPv6 numbers and also various IPv4 numbers. During a play session, Shairport Sync has to create a number of connections back to the source, and up until now, it would use an automatically-assigned IP number for itself, but that number might now be the number used earlier in the session. From now on, it will always use the same IP number it used when the connection was first established. + +Changed the `mono` setting for a `playback_mode` setting with two possible values `stereo` (default) and `mono`. + Version 2.8.3.1 – Development Version ---- Added a new `mono` setting -- thanks to [faceless2](https://github.com/faceless2). Documentation to follow. From 3de20db85722e3b95cdb72f846bfbba31141919f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 2 May 2016 10:34:34 +0100 Subject: [PATCH 15/90] Update RELEASENOTES.md --- RELEASENOTES.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a4d52892..d9fe5b6d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,9 +1,10 @@ -Version 2.8.3.2 – Unstable! Development Version +Version 2.8.3.2 – Development Version ---- **Bug Fix** -The networking subsystem has been modified to always use the same IP number during a session. Background: the computer Shairport Sync is running on can have may IP numbers active at the same time -- various IPv6 numbers and also various IPv4 numbers. During a play session, Shairport Sync has to create a number of connections back to the source, and up until now, it would use an automatically-assigned IP number for itself, but that number might now be the number used earlier in the session. From now on, it will always use the same IP number it used when the connection was first established. -Changed the `mono` setting for a `playback_mode` setting with two possible values `stereo` (default) and `mono`. +The networking subsystem has been modified to always use the same IP number during a session. Background: the computer Shairport Sync is running on can have may IP numbers active at the same time – various IPv6 numbers and also various IPv4 numbers. During a play session, Shairport Sync has to create a number of connections back to the source, and up until now, it would use an automatically-assigned IP number for itself, but that number might now be the number used earlier in the session. From now on, it will always use the same IP number it used when the connection was first established. + +Changed the `mono` setting for a `playback_mode` setting with two possible values: `stereo` (default) and `mono`. Version 2.8.3.1 – Development Version ---- From 1b0c637f699457de2751fe62b71b28ae10bd5f89 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 2 May 2016 10:38:59 +0100 Subject: [PATCH 16/90] Update RELEASENOTES.md --- RELEASENOTES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d9fe5b6d..e4f1f7ef 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,7 +2,8 @@ Version 2.8.3.2 – Development Version ---- **Bug Fix** -The networking subsystem has been modified to always use the same IP number during a session. Background: the computer Shairport Sync is running on can have may IP numbers active at the same time – various IPv6 numbers and also various IPv4 numbers. During a play session, Shairport Sync has to create a number of connections back to the source, and up until now, it would use an automatically-assigned IP number for itself, but that number might now be the number used earlier in the session. From now on, it will always use the same IP number it used when the connection was first established. +The networking subsystem has been modified to always use the same IP number during a session. Background: the computer Shairport Sync is running on can have many IP numbers active at the same time – various IPv6 numbers and also various IPv4 numbers. During a play session, when Shairport Sync has to create connections back to the source, it would use an automatically-assigned IP number for itself, but that number might not be same as the the number used earlier in the session. From now on, it will always use the same IP number it used when the connection was first established. + Changed the `mono` setting for a `playback_mode` setting with two possible values: `stereo` (default) and `mono`. From bbff2d1efb565ab5f578a4c7e715555d7bfcfcd3 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 2 May 2016 13:58:17 +0100 Subject: [PATCH 17/90] Get IPv6 working properly --- rtp.c | 46 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/rtp.c b/rtp.c index 1ffd7633..f7d4b259 100644 --- a/rtp.c +++ b/rtp.c @@ -544,39 +544,46 @@ void *rtp_timing_receiver(void *arg) { return NULL; } -static int bind_port(int ip_family,const char *self_ip_address, int *sock) { - struct addrinfo hints, *info; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = ip_family; - hints.ai_socktype = SOCK_DGRAM; - hints.ai_flags = AI_PASSIVE; - - char buffer[20]; - +static int bind_port(int ip_family,const char *self_ip_address, int *sock) { // look for a port in the range, if any was specified. int desired_port = config.udp_port_base; int ret; + + int local_socket = socket(ip_family, SOCK_DGRAM, IPPROTO_UDP); + if (local_socket== -1) + die("Could not allocate a socket."); + SOCKADDR myaddr; do { - snprintf(buffer, 10, "%d", desired_port); - ret = getaddrinfo(self_ip_address, buffer, &hints, &info); - if (ret < 0) - die("failed to get usable addrinfo?! %s.", gai_strerror(ret)); - *sock = socket(ip_family, SOCK_DGRAM, IPPROTO_UDP); - ret = bind(*sock, info->ai_addr, info->ai_addrlen); - freeaddrinfo(info); + memset(&myaddr,0,sizeof(myaddr)); + if (ip_family==AF_INET) { + struct sockaddr_in *sa = (struct sockaddr_in *)&myaddr; + sa->sin_family = AF_INET; + sa->sin_port = ntohs(desired_port); + inet_pton(AF_INET,self_ip_address,&(sa->sin_addr)); + ret = bind(local_socket,(struct sockaddr*)sa, sizeof(struct sockaddr_in)); + } +#ifdef AF_INET6 + if (ip_family==AF_INET6) { + struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&myaddr; + sa6->sin6_family = AF_INET6; + sa6->sin6_port = ntohs(desired_port); + ret = bind(local_socket,(struct sockaddr*)sa6, sizeof(struct sockaddr_in6)); + } +#endif + } 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) { + close(local_socket); die("error: could not bind a UDP port!"); } int sport; SOCKADDR local; socklen_t local_len = sizeof(local); - getsockname(*sock, (struct sockaddr *)&local, &local_len); + getsockname(local_socket, (struct sockaddr *)&local, &local_len); #ifdef AF_INET6 if (local.SAFAMILY == AF_INET6) { struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&local; @@ -587,7 +594,8 @@ static int bind_port(int ip_family,const char *self_ip_address, int *sock) { struct sockaddr_in *sa = (struct sockaddr_in *)&local; sport = ntohs(sa->sin_port); } - + + *sock = local_socket; return sport; } From 8f9a431988fecdb8cb5e835568fe607dfcc14baa Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 2 May 2016 16:01:05 +0100 Subject: [PATCH 18/90] Update RELEASENOTES.md --- RELEASENOTES.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e4f1f7ef..6548e9a5 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,4 +1,4 @@ -Version 2.8.3.2 – Development Version +Version 2.8.3.3 – Development Version ---- **Bug Fix** @@ -7,6 +7,9 @@ The networking subsystem has been modified to always use the same IP number duri Changed the `mono` setting for a `playback_mode` setting with two possible values: `stereo` (default) and `mono`. +Version 2.8.3.2 – Deleted +---- + Version 2.8.3.1 – Development Version ---- Added a new `mono` setting -- thanks to [faceless2](https://github.com/faceless2). Documentation to follow. From 99afe8ef293b57fc40b0e1cf0d27d4228e88f0cb Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 2 May 2016 17:08:00 +0100 Subject: [PATCH 19/90] Wait for network to have started http://unix.stackexchange.com/questions/126009/cause-a-script-to-execute-after-networking-has-started --- scripts/shairport-sync.service.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/shairport-sync.service.in b/scripts/shairport-sync.service.in index 3cc662e7..c59efd94 100644 --- a/scripts/shairport-sync.service.in +++ b/scripts/shairport-sync.service.in @@ -3,6 +3,8 @@ Description=ShairportSync AirTunes receiver After=sound.target Requires=avahi-daemon.service After=avahi-daemon.service +Wants=network-online.target +After=network.target network-online.target [Service] ExecStart=@prefix@/bin/shairport-sync From 76724fd06d368168dd9f301b825efaadee87e590 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 2 May 2016 21:46:08 +0100 Subject: [PATCH 20/90] Fix omitted line. Thanks to https://github.com/ejurgensen --- rtp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/rtp.c b/rtp.c index f7d4b259..811b4e6b 100644 --- a/rtp.c +++ b/rtp.c @@ -567,6 +567,7 @@ static int bind_port(int ip_family,const char *self_ip_address, int *sock) { struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&myaddr; sa6->sin6_family = AF_INET6; sa6->sin6_port = ntohs(desired_port); + inet_pton(AF_INET6,self_ip_address,&(sa6->sin6_addr)); ret = bind(local_socket,(struct sockaddr*)sa6, sizeof(struct sockaddr_in6)); } #endif From 20b322a9a22579f1dd7532d88cdb266495086fcf Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 2 May 2016 22:39:24 +0100 Subject: [PATCH 21/90] Update RELEASENOTES.md --- RELEASENOTES.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6548e9a5..b44caab2 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,4 +1,4 @@ -Version 2.8.3.3 – Development Version +Version 2.8.3.4 – Development Version ---- **Bug Fix** @@ -7,6 +7,9 @@ The networking subsystem has been modified to always use the same IP number duri Changed the `mono` setting for a `playback_mode` setting with two possible values: `stereo` (default) and `mono`. +Version 2.8.3.3 – Deleted +---- + Version 2.8.3.2 – Deleted ---- From cc040329369b6b163445e03b810e74293cea7c68 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 2 May 2016 22:39:52 +0100 Subject: [PATCH 22/90] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 7eb521d1..ebb42c57 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.8.3.2], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.8.3.4], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 6cfc9f9897b7d0775e0cd7ae7b28e6b00c650051 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 6 May 2016 16:28:40 +0000 Subject: [PATCH 23/90] Demote some debug messages, add check for empty or null buffer argument to pcm_snd_writei --- audio_alsa.c | 78 ++++++++++++++++++++++++++++------------------------ 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 2a2096a6..85b86fcc 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -290,9 +290,9 @@ static int init(int argc, char **argv) { if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) { // Raspberry Pi does this debug(1, "Lowest dB value is a mute."); - 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); @@ -465,7 +465,7 @@ int open_alsa_device(void) { if (alsa_characteristics_already_listed==0) { alsa_characteristics_already_listed=1; - + int log_level = 2; // the level at which debug information should be output int rc; snd_pcm_access_t access_type; snd_pcm_format_t format_type; @@ -476,7 +476,7 @@ int open_alsa_device(void) { int dir; snd_pcm_uframes_t frames; - debug(1,"PCM handle name = '%s'", + debug(log_level,"PCM handle name = '%s'", snd_pcm_name(alsa_handle)); // ret = snd_pcm_hw_params_any(alsa_handle, alsa_params); @@ -486,103 +486,103 @@ int open_alsa_device(void) { // alsa_out_dev); // } - debug(1,"alsa device parameters:"); + debug(log_level,"alsa device parameters:"); snd_pcm_hw_params_get_access(alsa_params,&access_type); - debug(1," access type = %s", snd_pcm_access_name(access_type)); + debug(log_level," access type = %s", snd_pcm_access_name(access_type)); snd_pcm_hw_params_get_format(alsa_params,&format_type); - debug(1," format = '%s' (%s)",snd_pcm_format_name(format_type),snd_pcm_format_description(format_type)); + debug(log_level," format = '%s' (%s)",snd_pcm_format_name(format_type),snd_pcm_format_description(format_type)); snd_pcm_hw_params_get_subformat(alsa_params,&subformat_type); - debug(1," subformat = '%s' (%s)",snd_pcm_subformat_name(subformat_type),snd_pcm_subformat_description(subformat_type)); + debug(log_level," subformat = '%s' (%s)",snd_pcm_subformat_name(subformat_type),snd_pcm_subformat_description(subformat_type)); snd_pcm_hw_params_get_channels(alsa_params, &uval); - debug(1," number of channels = %u", uval); + debug(log_level," number of channels = %u", uval); sval = snd_pcm_hw_params_get_sbits(alsa_params); - debug(1," number of significant bits = %d", sval); + debug(log_level," number of significant bits = %d", sval); snd_pcm_hw_params_get_rate(alsa_params, &uval, &dir); switch (dir) { case -1: - debug(1," rate = %u frames per second (<).", uval); + debug(log_level," rate = %u frames per second (<).", uval); break; case 0: - debug(1," rate = %u frames per second (precisely).", uval); + debug(log_level," rate = %u frames per second (precisely).", uval); break; case 1: - debug(1," rate = %u frames per second (>).", uval); + debug(log_level," rate = %u frames per second (>).", uval); break; } if (snd_pcm_hw_params_get_rate_numden(alsa_params,&uval, &uval2)==0) - debug(1," precise (rational) rate = %.3f frames per second (i.e. %u/%u).", uval, uval2, ((double)uval)/uval2); + debug(log_level," precise (rational) rate = %.3f frames per second (i.e. %u/%u).", uval, uval2, ((double)uval)/uval2); else - debug(1," precise (rational) rate information unavailable."); + debug(log_level," precise (rational) rate information unavailable."); snd_pcm_hw_params_get_period_time(alsa_params,&uval, &dir); switch (dir) { case -1: - debug(1," period_time = %u us (<).", uval); + debug(log_level," period_time = %u us (<).", uval); break; case 0: - debug(1," period_time = %u us (precisely).", uval); + debug(log_level," period_time = %u us (precisely).", uval); break; case 1: - debug(1," period_time = %u us (>).", uval); + debug(log_level," period_time = %u us (>).", uval); break; } snd_pcm_hw_params_get_period_size(alsa_params,&frames, &dir); switch (dir) { case -1: - debug(1," period_size = %lu frames (<).", frames); + debug(log_level," period_size = %lu frames (<).", frames); break; case 0: - debug(1," period_size = %lu frames (precisely).", frames); + debug(log_level," period_size = %lu frames (precisely).", frames); break; case 1: - debug(1," period_size = %lu frames (>).", frames); + debug(log_level," period_size = %lu frames (>).", frames); break; } snd_pcm_hw_params_get_buffer_time(alsa_params,&uval, &dir); switch (dir) { case -1: - debug(1," buffer_time = %u us (<).", uval); + debug(log_level," buffer_time = %u us (<).", uval); break; case 0: - debug(1," buffer_time = %u us (precisely).", uval); + debug(log_level," buffer_time = %u us (precisely).", uval); break; case 1: - debug(1," buffer_time = %u us (>).", uval); + debug(log_level," buffer_time = %u us (>).", uval); break; } snd_pcm_hw_params_get_buffer_size(alsa_params,&frames); switch (dir) { case -1: - debug(1," buffer_size = %lu frames (<).", frames); + debug(log_level," buffer_size = %lu frames (<).", frames); break; case 0: - debug(1," buffer_size = %lu frames (precisely).", frames); + debug(log_level," buffer_size = %lu frames (precisely).", frames); break; case 1: - debug(1," buffer_size = %lu frames (>).", frames); + debug(log_level," buffer_size = %lu frames (>).", frames); break; } snd_pcm_hw_params_get_periods(alsa_params, &uval, &dir); switch (dir) { case -1: - debug(1," periods_per_buffer = %u (<).", uval); + debug(log_level," periods_per_buffer = %u (<).", uval); break; case 0: - debug(1," periods_per_buffer = %u (precisely).", uval); + debug(log_level," periods_per_buffer = %u (precisely).", uval); break; case 1: - debug(1," periods_per_buffer = %u (>).", uval); + debug(log_level," periods_per_buffer = %u (>).", uval); break; } } @@ -656,11 +656,17 @@ static void play(short buf[], int 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) { - debug(1, "Error %d writing %d samples in play(): \"%s\".", err, samples, - snd_strerror(err)); - ignore = snd_pcm_recover(alsa_handle, err, 1); + if (buf==NULL) + debug(1,"NULL buffer passed to pcm_writei -- skipping it"); + if (samples==0) + debug(1,"empty buffer being passed to pcm_writei -- skipping it"); + if ((samples!=0) && (buf!=NULL)) { + err = snd_pcm_writei(alsa_handle, (char *)buf, samples); + if (err < 0) { + debug(1, "Error %d writing %d samples in play(): \"%s\".", err, samples, + snd_strerror(err)); + ignore = snd_pcm_recover(alsa_handle, err, 1); + } } } else { debug(1, "Error -- ALSA device in incorrect state (%d) for play.", From f3dcf8d69b724c51aa56d181f549e93c0757c084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Krause?= Date: Fri, 6 May 2016 22:03:44 +0200 Subject: [PATCH 24/90] Add support for ALSA softvol plugin The ALSA softvol plugin allows the user to add a new volume control and control the sound volume by software. This is necessary if the sound card can't control the volume by hardware. An example configuration in the `~/.asoundrc` file might look like this: ``` pcm.softvol { type softvol slave { pcm "hw:0" } control { name "Master" card 0 } min_dB -57.2 max_dB -6.2 } ctl.softvol { type hw card 0 } ``` The corresponding entries in the shairport-sync would be: ``` alsa = { output_device = "softvol"; mixer_control_name = "Master"; } ``` Please check the (ALSA documentation)[http://alsa.opensrc.org/Softvol] and the (ALSA PCM library reference)[http://www.alsa-project.org/alsa-doc/alsa-lib/pcm_plugins.html] If shairport-syncs alsa backend does not have success in getting the dB volume information from the hardware device it tries to get these information from a software volume control with the name mixer device name as for the hardware mixer. --- audio_alsa.c | 63 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 2a2096a6..f29f3b30 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -59,9 +59,9 @@ audio_output audio_alsa = { .flush = &flush, .delay = &delay, .play = &play, - .mute = NULL, // to be set later on... - .volume = NULL, // to be set later on... - .parameters = NULL // to be set later on... + .mute = &mute, + .volume = &volume, + .parameters = ¶meters }; static pthread_mutex_t alsa_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -70,7 +70,8 @@ static unsigned int desired_sample_rate; static snd_pcm_t *alsa_handle = NULL; static snd_pcm_hw_params_t *alsa_params = NULL; - +static snd_ctl_t *ctl = NULL; +static snd_ctl_elem_id_t *elem_id = NULL; static snd_mixer_t *alsa_mix_handle = NULL; static snd_mixer_elem_t *alsa_mix_elem = NULL; static snd_mixer_selem_id_t *alsa_mix_sid = NULL; @@ -82,7 +83,7 @@ static char *alsa_mix_dev = NULL; static char *alsa_mix_ctrl = "Master"; static int alsa_mix_index = 0; static int hardware_mixer = 0; - +static int has_softvol = 0; static int play_number; static int64_t accumulated_delay, accumulated_da_delay; @@ -299,8 +300,29 @@ static int init(int argc, char **argv) { } else { // use the linear scale and do the db conversion ourselves debug(1, "note: the hardware mixer specified -- \"%s\" -- does not have " - "a dB volume scale, so it can't be used.", + "a dB volume scale, so it can't be used. Trying software " + "volume control.", alsa_mix_ctrl); + + if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) + die("Cannot open control \"%s\"", alsa_mix_dev); + if (snd_ctl_elem_id_malloc(&elem_id) < 0) + die("Cannot allocate memory for control \"%s\"", alsa_mix_dev); + snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_MIXER); + snd_ctl_elem_id_set_name(elem_id, alsa_mix_ctrl); + + if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb, + &alsa_mix_maxdb) == 0) { + debug(1, "Volume control \"%s\" has dB volume from %f to %f.", + alsa_mix_ctrl, + (1.0 * alsa_mix_mindb) / 100.0, + (1.0 * alsa_mix_maxdb) / 100.0); + has_softvol = 1; + } else { + debug(1, "Cannot get the dB range from the volume control \"%s\"", + alsa_mix_ctrl); + } + /* debug(1, "Min and max volumes are %d and %d.",alsa_mix_minv,alsa_mix_maxv); @@ -728,11 +750,30 @@ static void volume(double vol) { debug(2, "Setting volume db to %f.", vol); set_volume = vol; if (hardware_mixer && alsa_mix_handle) { - if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, 0) != 0) { - debug(1, "Can't set playback volume accurately to %f dB.", vol); - if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, -1) != 0) - if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, 1) != 0) - die("Failed to set playback dB volume"); + if (has_softvol) { + if (ctl && elem_id) { + snd_ctl_elem_value_t *value; + long raw; + + if (snd_ctl_convert_from_dB(ctl, elem_id, (long) vol, &raw, 0) < 0) + debug(1, "Failed converting dB gain to raw volume value for the " + "software volume control."); + + snd_ctl_elem_value_alloca(&value); + snd_ctl_elem_value_set_id(value, elem_id); + snd_ctl_elem_value_set_integer(value, 0, raw); + snd_ctl_elem_value_set_integer(value, 1, raw); + if (snd_ctl_elem_write(ctl, value) < 0) + debug(1, "Failed to set playback dB volume for the software volume " + "control."); + } + } else { + if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, 0) != 0) { + debug(1, "Can't set playback volume accurately to %f dB.", vol); + if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, -1) != 0) + if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, 1) != 0) + die("Failed to set playback dB volume"); + } } } pthread_mutex_unlock(&alsa_mutex); From ab979d59674d02961055caeb39a0cdf60d8cb81c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 7 May 2016 08:01:05 +0000 Subject: [PATCH 25/90] Fix warning about redefined function. --- audio_alsa.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 85b86fcc..9d25ddd5 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -289,10 +289,10 @@ static int init(int argc, char **argv) { audio_alsa.parameters = ¶meters; // likewise the parameters stuff if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) { // Raspberry Pi does this - debug(1, "Lowest dB value is a mute."); - //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, "Lowest dB value is a mute -- try minimum volume +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 minimum volume + 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); From 58f3758ad6ae166c1b2e95d04da3ea2a4aeabb4f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 7 May 2016 08:01:46 +0000 Subject: [PATCH 26/90] Fix warning about redefined function. --- rtsp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rtsp.c b/rtsp.c index af0f8f78..976effe1 100644 --- a/rtsp.c +++ b/rtsp.c @@ -143,8 +143,6 @@ typedef struct { rtsp_message *carrier; } metadata_package; -void ask_other_rtsp_conversation_threads_to_stop(pthread_t except_this_thread); - void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items) { pthread_mutex_init(&the_queue->pc_queue_lock, NULL); @@ -246,6 +244,8 @@ int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) { #endif +void ask_other_rtsp_conversation_threads_to_stop(pthread_t except_this_thread); + // determine if we are the currently playing thread static inline int rtsp_playing(void) { if (pthread_mutex_trylock(&play_lock)) { From f01e86f5e98a2efc27c576fc911493812be3e957 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 7 May 2016 09:41:51 +0100 Subject: [PATCH 27/90] Fix a diagnostic that actually caused the connection to be dropped occasinally. --- rtsp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rtsp.c b/rtsp.c index 976effe1..c41f7540 100644 --- a/rtsp.c +++ b/rtsp.c @@ -498,11 +498,13 @@ rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) { goto shutdown; } nread = read(conn->fd, buf + inbuf, buflen - inbuf); + /* if (!nread) { debug(2, "RTSP connection closed."); reply = rtsp_read_request_response_shutdown_requested; goto shutdown; } + */ if (nread < 0) { if (errno == EINTR) continue; From ecb13597796d1754581b22b6f51a8f2b5ed67256 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 7 May 2016 09:44:09 +0100 Subject: [PATCH 28/90] Bump to reflect bug fix. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index ebb42c57..7ad6f0b2 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.8.3.4], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.8.3.5], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 429b04f13ca5befdc2b9417c867605a9dfdad4ca Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 7 May 2016 16:30:11 +0100 Subject: [PATCH 29/90] Reinstate end of conversation detection. --- rtsp.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rtsp.c b/rtsp.c index c41f7540..a6e4992a 100644 --- a/rtsp.c +++ b/rtsp.c @@ -498,13 +498,14 @@ rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) { goto shutdown; } nread = read(conn->fd, buf + inbuf, buflen - inbuf); - /* - if (!nread) { + + if (nread==0) { + // a blocking read that returns zero means eof -- implies connection closed debug(2, "RTSP connection closed."); reply = rtsp_read_request_response_shutdown_requested; goto shutdown; } - */ + if (nread < 0) { if (errno == EINTR) continue; From 2239efe8ca1aa020ac47b2a1503b5eb74395051d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 8 May 2016 13:17:47 +0100 Subject: [PATCH 30/90] Check for zero length or NULL play buffers and skip them if they happen. --- player.c | 96 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 75 insertions(+), 21 deletions(-) diff --git a/player.c b/player.c index 1a2cf1a6..0d07f1b9 100644 --- a/player.c +++ b/player.c @@ -653,7 +653,7 @@ static abuf_t *buffer_get_frame(void) { int64_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) { + 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; @@ -668,6 +668,10 @@ static abuf_t *buffer_get_frame(void) { int64_t fs = filler_size; if (fs > (max_dac_delay - dac_delay)) fs = max_dac_delay - dac_delay; + if (fs<0) { + debug(2,"frame size (fs) < 0!"); + fs=0; + } 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, @@ -678,24 +682,32 @@ static abuf_t *buffer_get_frame(void) { } signed short *silence; // fs will be truncated here - 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)); - config.output->play(silence, fs); - free(silence); - have_sent_prefiller_silence = 1; - if (ab_buffering == 0) { - // not the time of the playing of the first frame - 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 + if (fs==0) + debug(2,"Zero length silence buffer needed, duh."); + else { + silence = malloc(FRAME_BYTES(fs)); + if (silence==NULL) + debug(1,"Failed to allocate %d byte silence buffer.",fs); + else { + 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)); + config.output->play(silence, fs); + free(silence); + have_sent_prefiller_silence = 1; + } } } } } + if (ab_buffering == 0) { + // not the time of the playing of the first frame + 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 + } } } } @@ -1000,7 +1012,11 @@ static void *player_thread_func(void *arg) { signed short *inbuf, *outbuf, *silence; outbuf = malloc(OUTFRAME_BYTES(frame_size)); + if (outbuf==NULL) + debug(1,"Failed to allocate memory for an output buffer."); silence = malloc(OUTFRAME_BYTES(frame_size)); + if (silence==NULL) + debug(1,"Failed to allocate memory for a silence buffer."); memset(silence, 0, OUTFRAME_BYTES(frame_size)); late_packet_message_sent = 0; first_packet_timestamp = 0; @@ -1021,7 +1037,14 @@ static void *player_thread_func(void *arg) { // debug(1,"Player has a supplied silent frame."); last_seqno_read = (SUCCESSOR(last_seqno_read) & 0xffff); // manage the packet out of sequence minder - config.output->play(inbuf, frame_size); + if (inbuf==NULL) + debug(1,"NULL inbuf to play -- skipping it."); + else { + if (frame_size==0) + debug(1,"empty frame to play -- skipping it (1)."); + else + 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. @@ -1163,7 +1186,14 @@ static void *player_thread_func(void *arg) { // 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); + if (inbuf==NULL) + debug(1,"NULL inbuf to play -- skipping it."); + else { + if (frame_size==0) + debug(1,"empty frame to play -- skipping it (2)."); + else + config.output->play(inbuf, frame_size); + } } else { #ifdef HAVE_LIBSOXR switch (config.packet_stuffing) { @@ -1195,8 +1225,16 @@ static void *player_thread_func(void *arg) { debug(1,"Silence!"); } */ - - config.output->play(outbuf, play_samples); + + + if (outbuf==NULL) + debug(1,"NULL outbuf to play -- skipping it."); + else { + if (play_samples==0) + debug(1,"play_samples==0 skipping it (1)."); + else + config.output->play(outbuf, play_samples); + } } // check for loss of sync @@ -1227,10 +1265,26 @@ static void *player_thread_func(void *arg) { // if there is no delay procedure, or it's not working or not allowed, there can be no synchronising if (fix_volume == 0x10000) - config.output->play(inbuf, frame_size); + + if (inbuf==NULL) + debug(1,"NULL inbuf to play -- skipping it."); + else { + if (frame_size==0) + debug(1,"empty frame to play -- skipping it (3)."); + else + config.output->play(inbuf, frame_size); + } else { play_samples = stuff_buffer_basic(inbuf, outbuf, 0); // no stuffing, but volume adjustment - config.output->play(outbuf, frame_size); + + if (outbuf==NULL) + debug(1,"NULL outbuf to play -- skipping it."); + else { + if (frame_size==0) + debug(1,"empty frame to play -- skipping it (4)."); + else + config.output->play(outbuf, frame_size); + } } } From f5ee4c163539e92e108967943cada219b5ec9529 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 8 May 2016 14:45:13 +0100 Subject: [PATCH 31/90] Update RELEASENOTES.md --- RELEASENOTES.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b44caab2..69f47f66 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,8 +1,19 @@ +Version 2.8.3.5 – Development Version +---- +**Enhancements** + +Experimental support for a softvol plugin, thanks to the work of [Jörg Krause](https://github.com/joerg-krause) -- see [#293](https://github.com/mikebrady/shairport-sync/pull/293). + +**Bug fix** + +Add checks for empty or NULL audio buffers that seem to be causing assertion violations and subsequent abmormal program termination. + Version 2.8.3.4 – Development Version ---- + **Bug Fix** -The networking subsystem has been modified to always use the same IP number during a session. Background: the computer Shairport Sync is running on can have many IP numbers active at the same time – various IPv6 numbers and also various IPv4 numbers. During a play session, when Shairport Sync has to create connections back to the source, it would use an automatically-assigned IP number for itself, but that number might not be same as the the number used earlier in the session. From now on, it will always use the same IP number it used when the connection was first established. +The networking subsystem has been modified to always use the same IP number during a session. Background: the computer Shairport Sync is running on can have many IP numbers active at the same time – various IPv6 numbers and also various IPv4 numbers. During a play session, when Shairport Sync has to create connections back to the source, it would use an automatically-assigned IP number for itself, but that number might not be same as the the number used earlier in the session. From now on, it will always use the same IP number it used when the connection was first established. Thannks to [ejurgensen](https://github.com/ejurgensen) for help with this. Changed the `mono` setting for a `playback_mode` setting with two possible values: `stereo` (default) and `mono`. From 98607e55f4c07a86e47010e1f5a46673c913a9cd Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 8 May 2016 14:47:55 +0100 Subject: [PATCH 32/90] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 69f47f66..66d025f8 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,6 +1,6 @@ Version 2.8.3.5 – Development Version ---- -**Enhancements** +**Enhancement** Experimental support for a softvol plugin, thanks to the work of [Jörg Krause](https://github.com/joerg-krause) -- see [#293](https://github.com/mikebrady/shairport-sync/pull/293). From b2474c7b05f41f48e3463fe56fd3644d0606f609 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 9 May 2016 11:00:23 +0100 Subject: [PATCH 33/90] Add some debug information to help understand some of the startup dynamics --- player.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/player.c b/player.c index 0d07f1b9..5bfe720b 100644 --- a/player.c +++ b/player.c @@ -43,6 +43,7 @@ #include #include #include +#include #include "config.h" @@ -669,7 +670,7 @@ static abuf_t *buffer_get_frame(void) { if (fs > (max_dac_delay - dac_delay)) fs = max_dac_delay - dac_delay; if (fs<0) { - debug(2,"frame size (fs) < 0!"); + debug(2,"frame size (fs) < 0 with max_dac_delay of %lld and dac_delay of %ld",max_dac_delay, dac_delay); fs=0; } if ((exact_frame_gap <= fs) || (exact_frame_gap <= frame_size * 2)) { @@ -681,10 +682,10 @@ static abuf_t *buffer_get_frame(void) { ab_buffering = 0; } signed short *silence; - // fs will be truncated here - if (fs==0) - debug(2,"Zero length silence buffer needed, duh."); - else { + //if (fs==0) + // debug(2,"Zero length silence buffer needed with gross_frame_gap of %lld and dac_delay of %lld.",gross_frame_gap,dac_delay); + // the fs (number of frames of silence to play) can be zero in the DAC doesn't start ouotputting frames for a while -- it could get loaded up but not start responding for many milliseconds. + if (fs!=0) { silence = malloc(FRAME_BYTES(fs)); if (silence==NULL) debug(1,"Failed to allocate %d byte silence buffer.",fs); From 02651562a44c17879513d049aad02d0606f6d4ed Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 9 May 2016 11:28:37 +0000 Subject: [PATCH 34/90] Only try to set volume at play if there's a mixer. --- audio_alsa.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 5975b360..f7462e55 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -667,9 +667,10 @@ static void play(short buf[], int samples) { if (alsa_handle == NULL) { pthread_mutex_lock(&alsa_mutex); ret = open_alsa_device(); - open_mixer(); + if (hardware_mixer) + open_mixer(); pthread_mutex_unlock(&alsa_mutex); - if ((ret == 0) && (audio_alsa.volume)) + if ((hardware_mixer) && (ret == 0) && (audio_alsa.volume)) audio_alsa.volume(set_volume); } if (ret == 0) { From f230e3e543507cc7e11a90967ecb116097601845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Krause?= Date: Mon, 9 May 2016 15:07:21 +0200 Subject: [PATCH 35/90] Fix vol2attn This fixes calculation of the scaled attenuation if `max_dB` is different from zero. Closes: https://github.com/mikebrady/shairport-sync/issues/290 --- common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.c b/common.c index a86fd8e2..6ca577e9 100644 --- a/common.c +++ b/common.c @@ -437,7 +437,7 @@ double vol2attn(double vol, long max_db, long min_db) { #define order 3 - double vol_setting = max_db; + double vol_setting = 0; if ((vol <= 0.0) && (vol >= -30.0)) { long range_db = max_db - min_db; // this will be a positive nunmber From 30386ae08ca36e72e3bdd90a056ec7897454e1b6 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 11 May 2016 09:08:10 +0000 Subject: [PATCH 36/90] Add some information to message if the listening port (normally port 5000) is already in use. --- rtsp.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/rtsp.c b/rtsp.c index a6e4992a..ac707db1 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1819,6 +1819,7 @@ void rtsp_listen_loop(void) { } for (p = info; p; p = p->ai_next) { + ret = 0; int fd = socket(p->ai_family, p->ai_socktype, IPPROTO_TCP); int yes = 1; @@ -1847,7 +1848,14 @@ void rtsp_listen_loop(void) { // report its availability. do not complain. if (ret) { - debug(1, "Failed to bind to address %s.", format_address(p->ai_addr)); + char *family; +#ifdef AF_INET6 + if (p->ai_family == AF_INET6) { + family = "IPv6"; + } else +#endif + family = "IPv4"; + debug(1, "Unable to listen on %s port %d. The error is: \"%s\".", family, config.port,strerror(errno)); continue; } @@ -1860,7 +1868,7 @@ void rtsp_listen_loop(void) { freeaddrinfo(info); if (!nsock) - die("could not bind any listen sockets!"); + die("Could not listen on port %d -- program terminating. Is another Shairport Sync running?",config.port); int maxfd = -1; fd_set fds; From 51ad2cd385c2156416e0466dd7c56ab716950d56 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 11 May 2016 09:18:02 +0000 Subject: [PATCH 37/90] Alter the wording of an error message a tiny bit. --- rtsp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtsp.c b/rtsp.c index ac707db1..4b2a75e9 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1868,7 +1868,7 @@ void rtsp_listen_loop(void) { freeaddrinfo(info); if (!nsock) - die("Could not listen on port %d -- program terminating. Is another Shairport Sync running?",config.port); + die("Could not establish a service on port %d -- program terminating. Is another Shairport Sync running?",config.port); int maxfd = -1; fd_set fds; From 284dfb94bf3269fa82bc9d27357b7d6a3508ef88 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 13 May 2016 16:32:13 +0000 Subject: [PATCH 38/90] Add scope_id to information used to bind an outgoing IPv6 port. Makes it work with link-local addresses. --- rtp.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/rtp.c b/rtp.c index 811b4e6b..0f8dae93 100644 --- a/rtp.c +++ b/rtp.c @@ -60,6 +60,7 @@ static int running = 0; static char client_ip_string[INET6_ADDRSTRLEN]; // the ip string pointing to the client static char self_ip_string[INET6_ADDRSTRLEN]; // the ip string being used by this program -- it could be one of many, so we need to know it +static uint32_t self_scope_id; // if it's an ipv6 connection, this will be its scope static short connection_ip_family; // AF_INET / AF_INET6 static uint32_t client_active_remote; // used when you want to control the client... @@ -544,7 +545,7 @@ void *rtp_timing_receiver(void *arg) { return NULL; } -static int bind_port(int ip_family,const char *self_ip_address, int *sock) { +static int bind_port(int ip_family,const char *self_ip_address,uint32_t scope_id,int *sock) { // look for a port in the range, if any was specified. int desired_port = config.udp_port_base; int ret; @@ -568,6 +569,7 @@ static int bind_port(int ip_family,const char *self_ip_address, int *sock) { sa6->sin6_family = AF_INET6; sa6->sin6_port = ntohs(desired_port); inet_pton(AF_INET6,self_ip_address,&(sa6->sin6_addr)); + sa6->sin6_scope_id=scope_id; ret = bind(local_socket,(struct sockaddr*)sa6, sizeof(struct sockaddr_in6)); } #endif @@ -630,6 +632,7 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t sa6 = (struct sockaddr_in6 *)local; self_addr = &(sa6->sin6_addr); self_port = ntohs(sa6->sin6_port); + self_scope_id = sa6->sin6_scope_id; } #endif if (connection_ip_family == AF_INET) { @@ -689,9 +692,9 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t // now, we open three sockets -- one for the audio stream, one for the timing and one for the // control - *lsport = bind_port(connection_ip_family,self_ip_string,&audio_socket); - *lcport = bind_port(connection_ip_family,self_ip_string,&control_socket); - *ltport = bind_port(connection_ip_family,self_ip_string,&timing_socket); + *lsport = bind_port(connection_ip_family,self_ip_string,self_scope_id,&audio_socket); + *lcport = bind_port(connection_ip_family,self_ip_string,self_scope_id,&control_socket); + *ltport = bind_port(connection_ip_family,self_ip_string,self_scope_id,&timing_socket); debug(2, "listening for audio, control and timing on ports %d, %d, %d.", *lsport, *lcport, *ltport); From 04914a7ddb0193849265ffe6ae0b0e59859df851 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 16 May 2016 13:52:23 +0100 Subject: [PATCH 39/90] Reflect bug fix in ipv6 link-local connections --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 7ad6f0b2..5d6815b5 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.8.3.5], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.8.3.6], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 0eaf97ab0e56a28b86c9ad21269e4c61a70ce7ee Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 16 May 2016 13:53:58 +0100 Subject: [PATCH 40/90] Update RELEASENOTES.md --- RELEASENOTES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 66d025f8..c0dcfad6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,9 @@ +Version 2.8.3.6 – Development Version +---- +**Bug fix** + +An IPv6 link-local connection issue was fixed. A bug in the networking software would not allow an IPv6 link-local connection to be made from a client if Shairport Sync was running on a device with more than one network interface. The solution was to take account of the `config_id` information. + Version 2.8.3.5 – Development Version ---- **Enhancement** From 78f5153b1137341f50da991cab52b4b8f61ee34a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Krause?= Date: Tue, 10 May 2016 13:01:12 +0200 Subject: [PATCH 41/90] Make statistics output more parsable --- player.c | 115 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 93 insertions(+), 22 deletions(-) diff --git a/player.c b/player.c index 5bfe720b..e6e69c7a 100644 --- a/player.c +++ b/player.c @@ -1026,6 +1026,42 @@ static void *player_thread_func(void *arg) { // as a null operand, so we'll use it like that too int sync_error_out_of_bounds = 0; // number of times in a row that there's been a serious sync error + if (config.statistics_requested) { + if ((config.output->delay)) { + if (config.no_sync==0) { + inform("sync error in frames, " + "net correction in ppm, " + "corrections in ppm, " + "total packets, " + "missing packets, " + "late packets, " + "too late packets, " + "resend requests, " + "min DAC queue size, " + "min buffer occupancy, " + "max buffer occupancy"); + } else { + inform("sync error in frames, " + "total packets, " + "missing packets, " + "late packets, " + "too late packets, " + "resend requests, " + "min DAC queue size, " + "min buffer occupancy, " + "max buffer occupancy"); + } + } else { + inform("total packets, " + "missing packets, " + "late packets, " + "too late packets, " + "resend requests, " + "min buffer occupancy, " + "max buffer occupancy"); + } + } + uint64_t tens_of_seconds = 0; while (!please_stop) { abuf_t *inframe = buffer_get_frame(); @@ -1354,31 +1390,66 @@ static void *player_thread_func(void *arg) { if (at_least_one_frame_seen) { if ((config.output->delay)) { if (config.no_sync==0) { - inform("Sync error: %.1f (frames); net correction: %.1f (ppm); corrections: %.1f " - "(ppm); total packets %d; missing packets %llu; late packets %llu; too late packets %llu; " - "resend requests %llu; min DAC queue size %lli, min and max buffer occupancy " - "%d and %d.", - moving_average_sync_error, moving_average_correction * 1000000 / 352, - moving_average_insertions_plus_deletions * 1000000 / 352, play_number, missing_packets, - late_packets, too_late_packets, resend_requests, minimum_dac_queue_size, - minimum_buffer_occupancy, maximum_buffer_occupancy); + inform("%*.1f," /* Sync error inf frames */ + "%*.1f," /* net correction in ppm */ + "%*.1f," /* corrections in ppm */ + "%*d," /* total packets */ + "%*llu," /* missing packets */ + "%*llu," /* late packets */ + "%*llu," /* too late packets */ + "%*llu," /* resend requests */ + "%*lli," /* min DAC queue size */ + "%*d," /* min buffer occupancy */ + "%*d", /* max buffer occupancy */ + 10, moving_average_sync_error, + 10, moving_average_correction * 1000000 / 352, + 10, moving_average_insertions_plus_deletions * 1000000 / 352, + 12, play_number, + 7, missing_packets, + 7, late_packets, + 7, too_late_packets, + 7, resend_requests, + 7, minimum_dac_queue_size, + 5, minimum_buffer_occupancy, + 5, maximum_buffer_occupancy); } else { - inform("Synchronisation disabled. Sync error: %.1f (frames); total packets %d; " - "missing packets %llu; late packets %llu; too late packets %llu; " - "resend requests %llu; min DAC queue size %lli, min and max buffer occupancy " - "%d and %d.", - moving_average_sync_error, play_number, missing_packets, - late_packets, too_late_packets, resend_requests, minimum_dac_queue_size, - minimum_buffer_occupancy, maximum_buffer_occupancy); + inform("%*.1f," /* Sync error inf frames */ + "%*d," /* total packets */ + "%*llu," /* missing packets */ + "%*llu," /* late packets */ + "%*llu," /* too late packets */ + "%*llu," /* resend requests */ + "%*lli," /* min DAC queue size */ + "%*d," /* min buffer occupancy */ + "%*d", /* max buffer occupancy */ + 10, moving_average_sync_error, + 12, play_number, + 7, missing_packets, + 7, late_packets, + 7, too_late_packets, + 7, resend_requests, + 7, minimum_dac_queue_size, + 5, minimum_buffer_occupancy, + 5, maximum_buffer_occupancy); } } else { - inform("Synchronisation disabled. Total packets %d; missing packets %llu; late packets %llu; too late packets %llu; " - "resend requests %llu; min and max buffer occupancy " - "%d and %d.", - play_number, missing_packets, - late_packets, too_late_packets, resend_requests, - minimum_buffer_occupancy, maximum_buffer_occupancy); - } + inform("%*.1f," /* Sync error inf frames */ + "%*d," /* total packets */ + "%*llu," /* missing packets */ + "%*llu," /* late packets */ + "%*llu," /* too late packets */ + "%*llu," /* resend requests */ + "%*d," /* min buffer occupancy */ + "%*d", /* max buffer occupancy */ + 10, moving_average_sync_error, + 12, play_number, + 7, missing_packets, + 7, late_packets, + 7, too_late_packets, + 7, resend_requests, + 5, minimum_buffer_occupancy, + 5, maximum_buffer_occupancy); + } } else { inform("No frames received in the last sampling interval."); } From dcd7f5098c65fd6dc795d9561e715d4546cb3eae Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 18 May 2016 11:06:31 +0100 Subject: [PATCH 42/90] Remove references to older Raspbian --- LIBSOXR.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/LIBSOXR.md b/LIBSOXR.md index 4883b9b2..709e3f8a 100644 --- a/LIBSOXR.md +++ b/LIBSOXR.md @@ -1,5 +1,6 @@ -The Raspbian image at the time of writing is `2015-01-31-raspbian`. It does not include `libsoxr`, nor is `libsoxr` available as a package via `apt-get`. -Libsoxr is, however, easy to compile, and works well with Shairport Sync on the Raspberry Pi. Here are very brief instructions to download, compile and install it on the Raspberry Pi, so that it can be used with Shairport Sync. + +The Raspbian image at the time of writing is the `May 2016` version, with the release date of `2016-05-10`. It does not include `libsoxr`, but it is available as a package via `apt-get`. +For distributions that do not have `libsoxr` available as a package, it is easy to compile. Here are very brief instructions to download, compile and install. * Install `cmake`. This is used in the building of libsoxr: ``` From ebb59c98bf911590e4aa9e81fd490bd0370ff1df Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 18 May 2016 11:07:33 +0100 Subject: [PATCH 43/90] Update LIBSOXR.md --- LIBSOXR.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/LIBSOXR.md b/LIBSOXR.md index 709e3f8a..af10e968 100644 --- a/LIBSOXR.md +++ b/LIBSOXR.md @@ -1,6 +1,7 @@ The Raspbian image at the time of writing is the `May 2016` version, with the release date of `2016-05-10`. It does not include `libsoxr`, but it is available as a package via `apt-get`. -For distributions that do not have `libsoxr` available as a package, it is easy to compile. Here are very brief instructions to download, compile and install. + +`libsoxr` is vary easy to compile. Here are very brief instructions to download, compile and install it: * Install `cmake`. This is used in the building of libsoxr: ``` From 2d38d5c364446ba7104bfba329fc78ed29698fb8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 18 May 2016 11:08:16 +0100 Subject: [PATCH 44/90] Update LIBSOXR.md --- LIBSOXR.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LIBSOXR.md b/LIBSOXR.md index af10e968..2c703ab6 100644 --- a/LIBSOXR.md +++ b/LIBSOXR.md @@ -1,7 +1,7 @@ The Raspbian image at the time of writing is the `May 2016` version, with the release date of `2016-05-10`. It does not include `libsoxr`, but it is available as a package via `apt-get`. -`libsoxr` is vary easy to compile. Here are very brief instructions to download, compile and install it: +Alternatively, `libsoxr` is very easy to compile. Here are very brief instructions to download, compile and install it: * Install `cmake`. This is used in the building of libsoxr: ``` From 49d27c35f2d1098f55af50797f2eefd718709444 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 20 May 2016 15:39:18 +0100 Subject: [PATCH 45/90] Change Zeroconf strings to match those of a First Generation AirPort Express running firmware 7.6.4 --- mdns.h | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/mdns.h b/mdns.h index a15b77fa..a591a3b7 100644 --- a/mdns.h +++ b/mdns.h @@ -15,19 +15,29 @@ typedef struct { void (*mdns_unregister)(void); } mdns_backend; -// 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" +// #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 \ + "sf=0x4", "fv=76400.10", "am=AirPort4,107", "vs=105.1", "tp=TCP,UDP", "vn=65537", \ + "ss=16", "sr=44100", "da=true", "sv=false", "et=0,1", "ek=1", "cn=0,1", "ch=2", "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", METADATA_EXPRESSION, "ss=16", "sr=44100", "vn=3", \ +// "txtvers=1", config.password ? "pw=true" : "pw=false" + +#define MDNS_RECORD_WITHOUT_METADATA \ + "sf=0x4", "fv=76400.10", "am=AirPort4,107", "vs=105.1", "tp=TCP,UDP", "vn=65537", \ + "ss=16", "sr=44100", "da=true", "sv=false", "et=0,1", "ek=1", "cn=0,1", "ch=2", "txtvers=1", config.password ? "pw=true" : "pw=false" #endif // _MDNS_H + + + From 04eeaa837e1df68942daebc37b94af900fbf8ae2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 20 May 2016 15:41:19 +0100 Subject: [PATCH 46/90] Change password authentication to the same as an AirPort Express running 7.6.4. Only look for authentication once per session, to suit AllStream/Streambels --- rtsp.c | 69 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/rtsp.c b/rtsp.c index 4b2a75e9..ec4eb7c5 100644 --- a/rtsp.c +++ b/rtsp.c @@ -88,6 +88,7 @@ static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER; typedef struct { int fd; + int authorized; // set is a password is required and has been supplied stream_cfg stream; SOCKADDR remote,local; int stop; @@ -388,10 +389,10 @@ static char *msg_get_header(rtsp_message *msg, char *name) { return NULL; } -static void msg_print_debug_headers(rtsp_message *msg) { +static void debug_print_msg_headers(int level, rtsp_message *msg) { int i; for (i = 0; i < msg->nheaders; i++) { - debug(1, " Type: \"%s\", content: \"%s\"", msg->name[i], msg->value[i]); + debug(level, " Type: \"%s\", content: \"%s\"", msg->name[i], msg->value[i]); } } @@ -501,7 +502,7 @@ rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) { if (nread==0) { // a blocking read that returns zero means eof -- implies connection closed - debug(2, "RTSP connection closed."); + debug(3, "RTSP connection closed."); reply = rtsp_read_request_response_shutdown_requested; goto shutdown; } @@ -606,7 +607,7 @@ static void msg_write_response(int fd, rtsp_message *resp) { int i, n; n = snprintf(p, pktfree, "RTSP/1.0 %d %s\r\n", resp->respcode, - resp->respcode == 200 ? "OK" : "Error"); + resp->respcode == 200 ? "OK" : "Unauthorized"); // debug(1, "sending response: %s", pkt); pktfree -= n; p += n; @@ -1279,7 +1280,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, // if (!req->contentlength) // debug(1, "received empty SET_PARAMETER request."); - // msg_print_debug_headers(req); + // debug_print_msg_headers(1,req); char *ct = msg_get_header(req, "Content-Type"); @@ -1658,7 +1659,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { int i; unsigned char buf[33]; for (i = 0; i < 16; i++) - sprintf((char *)buf + 2 * i, "%02X", digest_urp[i]); + sprintf((char *)buf + 2 * i, "%02x", digest_urp[i]); #ifdef HAVE_LIBSSL MD5_Init(&ctx); @@ -1667,7 +1668,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { MD5_Update(&ctx, *nonce, strlen(*nonce)); MD5_Update(&ctx, ":", 1); for (i = 0; i < 16; i++) - sprintf((char *)buf + 2 * i, "%02X", digest_mu[i]); + sprintf((char *)buf + 2 * i, "%02x", digest_mu[i]); MD5_Update(&ctx, buf, 32); MD5_Final(digest_total, &ctx); #endif @@ -1679,23 +1680,23 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { 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]); + 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]); + sprintf((char *)buf + 2 * i, "%02x", digest_total[i]); if (!strcmp(response, (const char *)buf)) return 0; - warn("auth failed"); + warn("Password authorization failed."); authenticate: resp->respcode = 401; int hdrlen = strlen(*nonce) + 40; char *authhdr = malloc(hdrlen); - snprintf(authhdr, hdrlen, "Digest realm=\"taco\", nonce=\"%s\"", *nonce); + snprintf(authhdr, hdrlen, "Digest realm=\"raop\", nonce=\"%s\"", *nonce); msg_add_header(resp, "WWW-Authenticate", authhdr); free(authhdr); return 1; @@ -1718,6 +1719,8 @@ static void *rtsp_conversation_thread_func(void *pconn) { do { reply = rtsp_read_request(conn, &req); if (reply == rtsp_read_request_response_ok) { + debug(3,"RTSP Packet received of type \"%s\":",req->method), + debug_print_msg_headers(3,req); resp = msg_init(); resp->respcode = 400; @@ -1725,29 +1728,26 @@ static void *rtsp_conversation_thread_func(void *pconn) { hdr = msg_get_header(req, "CSeq"); if (hdr) msg_add_header(resp, "CSeq", hdr); - msg_add_header(resp, "Audio-Jack-Status", "connected; type=analog"); - - if (rtsp_auth(&auth_nonce, req, resp)) - goto respond; - - struct method_handler *mh; - int method_selected = 0; - 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); - method_selected = 1; - mh->handler(conn, req, resp); - // debug(1,"RTSP Response:"); - // msg_print_debug_headers(resp); - break; - } - } - if (method_selected == 0) - debug(1, "Unrecognised and unhandled rtsp request \"%s\".", - req->method); - - respond: +// msg_add_header(resp, "Audio-Jack-Status", "connected; type=analog"); + msg_add_header(resp, "Server", "AirTunes/105.1"); + + if ((conn->authorized==1) || (rtsp_auth(&auth_nonce, req, resp))==0) { + conn->authorized=1; // it must have been authorized or didn't need a password + struct method_handler *mh; + int method_selected = 0; + for (mh = method_handlers; mh->method; mh++) { + if (!strcmp(mh->method, req->method)) { + method_selected = 1; + mh->handler(conn, req, resp); + break; + } + } + if (method_selected == 0) + debug(1, "Unrecognised and unhandled rtsp request \"%s\".", + req->method); + } + debug(3,"RTSP Response:"); + debug_print_msg_headers(3,resp); msg_write_response(conn->fd, resp); msg_free(req); msg_free(resp); @@ -1969,6 +1969,7 @@ void rtsp_listen_loop(void) { conn->thread = rtsp_conversation_thread; conn->stop = 0; + conn->authorized = 0; conn->running = 1; track_thread(conn); } From cd9a1b58381057f137452b393240bbc2818c2de9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 20 May 2016 15:41:54 +0100 Subject: [PATCH 47/90] Reintroduce a message to indicate successful startup. --- shairport.c | 1 + 1 file changed, 1 insertion(+) diff --git a/shairport.c b/shairport.c index 8be3a35e..150849ea 100644 --- a/shairport.c +++ b/shairport.c @@ -1024,6 +1024,7 @@ int main(int argc, char **argv) { #ifdef CONFIG_METADATA metadata_init(); // create the metadata pipe if necessary #endif + daemon_log(LOG_INFO,"Successful Startup"); rtsp_listen_loop(); // should not reach this... From 2ad0387456fb5c4c2deabff6b1be59321f52a9d4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 20 May 2016 15:42:25 +0100 Subject: [PATCH 48/90] Minor bug fixes and AllStream compatability. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 5d6815b5..f6bfd382 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.8.3.6], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.8.3.7], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From e337bf6cccc23d4e04ba7b98fcfdc26f0d6f3b4b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 20 May 2016 15:50:06 +0100 Subject: [PATCH 49/90] Update RELEASENOTES.md --- RELEASENOTES.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c0dcfad6..929a1972 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,11 @@ +Version 2.8.3.7 – Development Version +---- +**Enhancement** + +Shairport Sync now works with AllStream/Streambels on Android with password proection. (As with all Android clients, you should set the `drift` to something large, like 500 or 1000, as the timetkeeping of thes clients isn't as accurate as that of iTunes, etc.) + +The text strings advertising the capabilities of Shairport Sync over Bonjour/Zeroconf/Avahi have been changed and now more closely match those of an AirPort Express Base Station (First Generation). + Version 2.8.3.6 – Development Version ---- **Bug fix** From 3996553913b0ff19463cbe229f973a9b9a7ce7a9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 20 May 2016 15:53:11 +0100 Subject: [PATCH 50/90] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 929a1972..88ee8f28 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,7 +2,7 @@ Version 2.8.3.7 – Development Version ---- **Enhancement** -Shairport Sync now works with AllStream/Streambels on Android with password proection. (As with all Android clients, you should set the `drift` to something large, like 500 or 1000, as the timetkeeping of thes clients isn't as accurate as that of iTunes, etc.) +Shairport Sync now works with AllStream/Streambels on Android with password protection. (As with all Android clients, you should set the `drift` to something large, like 500 or 1,000, as the timekeeping of these clients isn't as accurate as that of iTunes, etc.) The text strings advertising the capabilities of Shairport Sync over Bonjour/Zeroconf/Avahi have been changed and now more closely match those of an AirPort Express Base Station (First Generation). From e4642cbdb70e6a3fb2a19785a8f9a59f27a7ff3b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 20 May 2016 15:54:04 +0100 Subject: [PATCH 51/90] Update RELEASENOTES.md --- RELEASENOTES.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 88ee8f28..7a1e5dcd 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,10 +1,10 @@ Version 2.8.3.7 – Development Version ---- -**Enhancement** +**Enhancements** -Shairport Sync now works with AllStream/Streambels on Android with password protection. (As with all Android clients, you should set the `drift` to something large, like 500 or 1,000, as the timekeeping of these clients isn't as accurate as that of iTunes, etc.) +* Shairport Sync now works with AllStream/Streambels on Android with password protection. (As with all Android clients, you should set the `drift` to something large, like 500 or 1,000, as the timekeeping of these clients isn't as accurate as that of iTunes, etc.) -The text strings advertising the capabilities of Shairport Sync over Bonjour/Zeroconf/Avahi have been changed and now more closely match those of an AirPort Express Base Station (First Generation). +* The text strings advertising the capabilities of Shairport Sync over Bonjour/Zeroconf/Avahi have been changed and now more closely match those of an AirPort Express Base Station (First Generation). Version 2.8.3.6 – Development Version ---- From 006b86c2d28f106f7278e5face6ee7a96ecb3d14 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 20 May 2016 16:24:38 +0100 Subject: [PATCH 52/90] AllConnect, not AllStream --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7a1e5dcd..6a60c0a3 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,7 +2,7 @@ Version 2.8.3.7 – Development Version ---- **Enhancements** -* Shairport Sync now works with AllStream/Streambels on Android with password protection. (As with all Android clients, you should set the `drift` to something large, like 500 or 1,000, as the timekeeping of these clients isn't as accurate as that of iTunes, etc.) +* Shairport Sync now works with AllConnect/Streambels on Android with password protection. (As with all Android clients, you should set the `drift` to something large, like 500 or 1,000, as the timekeeping of these clients isn't as accurate as that of iTunes, etc.) * The text strings advertising the capabilities of Shairport Sync over Bonjour/Zeroconf/Avahi have been changed and now more closely match those of an AirPort Express Base Station (First Generation). From 16b6f5d7111fec96cc0eaadc694299e7dfa1092e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 21 May 2016 13:04:26 +0100 Subject: [PATCH 53/90] Don't replace an existing service file --- Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index f576001d..f9fd8976 100644 --- a/Makefile.am +++ b/Makefile.am @@ -62,5 +62,5 @@ if INSTALL_SYSTEMV endif if INSTALL_SYSTEMD [ -e $(DESTDIR)$(systemdsystemunitdir) ] || mkdir -p $(DESTDIR)$(systemdsystemunitdir) - cp scripts/shairport-sync.service $(DESTDIR)$(systemdsystemunitdir) + [ -f $(DESTDIR)$(systemdsystemunitdir)/shairport-sync.service ] || cp scripts/shairport-sync.service $(DESTDIR)$(systemdsystemunitdir) endif From 46fac92f6c2877cc043023c886d61b7b9d314df5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 21 May 2016 13:05:15 +0100 Subject: [PATCH 54/90] Add a utility for string substitution, rename apname to service_name --- common.c | 30 ++++++++++++++++++++++++++++++ common.h | 5 ++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/common.c b/common.c index 6ca577e9..f8a8e2a4 100644 --- a/common.c +++ b/common.c @@ -537,3 +537,33 @@ ssize_t non_blocking_write(int fd, const void *buf, size_t count) { // return write(fd,buf,count); } +/* from http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722 */ + +char *str_replace ( const char *string, const char *substr, const char *replacement ){ + char *tok = NULL; + char *newstr = NULL; + char *oldstr = NULL; + char *head = NULL; + + /* if either substr or replacement is NULL, duplicate string a let caller handle it */ + if ( substr == NULL || replacement == NULL ) return strdup (string); + newstr = strdup (string); + head = newstr; + while ( (tok = strstr ( head, substr ))){ + oldstr = newstr; + newstr = malloc ( strlen ( oldstr ) - strlen ( substr ) + strlen ( replacement ) + 1 ); + /*failed to alloc mem, free old string and return NULL */ + if ( newstr == NULL ){ + free (oldstr); + return NULL; + } + memcpy ( newstr, oldstr, tok - oldstr ); + memcpy ( newstr + (tok - oldstr), replacement, strlen ( replacement ) ); + memcpy ( newstr + (tok - oldstr) + strlen( replacement ), tok + strlen ( substr ), strlen ( oldstr ) - strlen ( substr ) - ( tok - oldstr ) ); + memset ( newstr + strlen ( oldstr ) - strlen ( substr ) + strlen ( replacement ) , 0, 1 ); + /* move back head right after the last replacement */ + head = newstr + (tok - oldstr) + strlen( replacement ); + free (oldstr); + } + return newstr; +} diff --git a/common.h b/common.h index 6e5331d5..dc44af4c 100644 --- a/common.h +++ b/common.h @@ -51,7 +51,7 @@ enum playback_mode_type { typedef struct { config_t *cfg; char *password; - char *apname; + char *service_name; // the name for the shairport service, e.g. "Shairport Sync Version %v running on host %h" #ifdef CONFIG_METADATA int metadata_enabled; char *metadata_pipename; @@ -110,6 +110,9 @@ 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 +/* from http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722 */ +char *str_replace ( const char *string, const char *substr, const char *replacement ); + int debuglev; void die(char *format, ...); void warn(char *format, ...); From 8896bd10268b6a41b709e6491c541055ab7a3a81 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 21 May 2016 13:05:31 +0100 Subject: [PATCH 55/90] rename apname to service_name --- mdns.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mdns.c b/mdns.c index f4491fd1..fa5bfb91 100644 --- a/mdns.c +++ b/mdns.c @@ -57,15 +57,15 @@ static mdns_backend *mdns_backends[] = { NULL}; void mdns_register(void) { - char *mdns_apname = alloca(strlen(config.apname) + 14); - char *p = mdns_apname; + char *mdns_service_name = alloca(strlen(config.service_name) + 14); + char *p = mdns_service_name; int i; for (i = 0; i < 6; i++) { sprintf(p, "%02X", config.hw_addr[i]); p += 2; } *p++ = '@'; - strcpy(p, config.apname); + strcpy(p, config.service_name); mdns_backend **b = NULL; @@ -73,7 +73,7 @@ void mdns_register(void) { 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); + int error = (*b)->mdns_register(mdns_service_name, config.port); if (error >= 0) { config.mdns = *b; } @@ -84,7 +84,7 @@ void mdns_register(void) { warn("%s mDNS backend not found"); } else { for (b = mdns_backends; *b; b++) { - int error = (*b)->mdns_register(mdns_apname, config.port); + int error = (*b)->mdns_register(mdns_service_name, config.port); if (error >= 0) { config.mdns = *b; break; From 5f79ca7598287f15cca9e554ac4ccb7737b1a547 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 21 May 2016 13:06:15 +0100 Subject: [PATCH 56/90] Add explanation of substitutions for service name --- scripts/shairport-sync.conf | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 2f185669..4dd57505 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -4,7 +4,13 @@ // General Settings general = { -// name = "Shairport Sync Player"; // This is the name the service will advertise to iTunes. The default is "Shairport Sync on " +// name = "Shairport Sync on %H"; // This is the name the service will advertise to iTunes. +// The default is "" -- the first letter is capitalised (ASCII only.) +// You can use %h for the hostname, +// %H for the Hostname (i.e. with first letter capitalised (ASCII only)), +// %v for the version number, e.g. 2.8.4 and +// %V for the full version string, e.g. 2.8.4-OpenSSL-Avahi-ALSA-soxr-metadata +// Overall length can not exceed 50 characters. Example: "Shairport Sync %v on %H". // password = "secret"; // leave this commented out if you don't want to require a password // 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. From c434f0733583c646354493f597a0549e146f6120 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 21 May 2016 13:08:01 +0100 Subject: [PATCH 57/90] Add ability to use substitution strings in the service name. Make the default service name the capitalised hostname --- shairport.c | 61 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/shairport.c b/shairport.c index 150849ea..b79788e9 100644 --- a/shairport.c +++ b/shairport.c @@ -2,6 +2,7 @@ * Shairport, an Apple Airplay receiver * Copyright (c) James Laird 2013 * All rights reserved. + * Modifications (c) Mike Brady 2014--2016 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -109,10 +110,10 @@ char* get_version_string() { if (version_string) { 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"); @@ -234,6 +235,8 @@ void usage(char *progname) { } int parse_options(int argc, char **argv) { + // there are potential memory leaks here -- it's called a second time, previously allocated strings will dangle. + char *raw_service_name = NULL; /* Used to pick up the service name before possibly expanding it */ char *stuffing = NULL; /* used for picking up the stuffing option */ signed char c; /* used for argument parsing */ int i = 0; /* used for tracking options */ @@ -248,7 +251,7 @@ int parse_options(int argc, char **argv) { {"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}, + {"name", 'a', POPT_ARG_STRING, &raw_service_name, 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}, @@ -295,9 +298,9 @@ int parse_options(int argc, char **argv) { } config_setting_t *setting; - const char *str; - int value; - + const char *str=0; + int value=0; + debug(1,"Looking for the configuration file \"%s\".",config.configfile); config_init(&config_file_stuff); @@ -312,8 +315,9 @@ int parse_options(int argc, char **argv) { // 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; + if (config_lookup_string(config.cfg, "general.name", &str)) { + raw_service_name = (char *)str; + } /* Get the Daemonize setting. */ if (config_lookup_string(config.cfg, "general.daemonize", &str)) { @@ -600,6 +604,27 @@ int parse_options(int argc, char **argv) { if (tdebuglev!=0) debuglev = tdebuglev; + +/* if the Service Name wasn't specified, do it now */ + + if (raw_service_name==NULL) + raw_service_name = strdup("%H"); + +// now, do the substitutions in the service name + char hostname[100]; + gethostname(hostname, 100); + char *i1 = str_replace(raw_service_name,"%h",hostname); + if ((hostname[0]>='a') && (hostname[0]<='z')) + hostname[0] = hostname[0]-0x20; // convert a lowercase first letter into a capital letter + char *i2 = str_replace(i1,"%H",hostname); + char *i3 = str_replace(i2,"%v",PACKAGE_VERSION); + char *vs = get_version_string(); + config.service_name = str_replace(i3,"%V",vs); + free(i1); + free(i2); + free(i3); + free(vs); + return optind + 1; } @@ -723,10 +748,10 @@ int main(int argc, char **argv) { 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); + //char hostname[100]; + //gethostname(hostname, 100); + //config.service_name = malloc(20 + 100); + //snprintf(config.service_name, 20 + 100, "Shairport Sync on %s", hostname); set_requested_connection_state_to_output(1); // we expect to be able to connect to the output device config.audio_backend_buffer_desired_length = 6615; // 0.15 seconds. config.udp_port_base = 6001; @@ -823,8 +848,10 @@ int main(int argc, char **argv) { 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)"); + if (strlen(config.service_name) > 50) { + warn("Supplied name too long (max 50 characters)"); + config.service_name[50] = '\0'; //truncate it and carry on... + } /* here, daemonise with libdaemon */ @@ -965,7 +992,7 @@ int main(int argc, char **argv) { debug(1, "rtsp listening port is %d.", config.port); debug(1, "udp base port is %d.", config.udp_port_base); debug(1, "udp port range is %d.", config.udp_port_range); - debug(1, "Shairport Sync player name is \"%s\".", config.apname); + debug(1, "Shairport Sync player name is \"%s\".", config.service_name); debug(1, "Audio Output name is \"%s\".", config.output_name); debug(1, "on-start action is \"%s\".", config.cmd_start); debug(1, "on-stop action is \"%s\".", config.cmd_stop); @@ -1010,14 +1037,14 @@ int main(int argc, char **argv) { #ifdef HAVE_LIBSSL MD5_CTX ctx; MD5_Init(&ctx); - MD5_Update(&ctx, config.apname, strlen(config.apname)); + MD5_Update(&ctx, config.service_name, strlen(config.service_name)); 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_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); md5_finish(&tctx, ap_md5); #endif memcpy(config.hw_addr, ap_md5, sizeof(config.hw_addr)); From 3b0e87111a41599088aa83c2a76b03e71352d7d6 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 21 May 2016 13:09:01 +0100 Subject: [PATCH 58/90] Bump to coincide with name substitution stuff, default service name and avoiding overwriting service file --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index f6bfd382..60b5ad58 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.8.3.7], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.8.3.8], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 629c844e3ad397619edb683175006940e0670717 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 21 May 2016 13:20:59 +0100 Subject: [PATCH 59/90] Update RELEASENOTES.md --- RELEASENOTES.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6a60c0a3..9d5040cd 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,13 @@ +Version 2.8.3.8 – Development Version +---- +**Enhancements** + +* Substitutions can now be made in the service name, i.e. the name that appears in iTunes, etc. The following substitutions can be used in the service name you specify: `%h` for the hostname, `%H` for the hostname with the first letter capitalised, `%v` for the version number and `%V` for the full version string. Maximum length is 50 characters. + +* The new default service name is simply the hostname, with its first character capitalised. + +* An existing `shairport-sync.service` file will not be overwritten by `sudo make install`. + Version 2.8.3.7 – Development Version ---- **Enhancements** From dae0bccdd9cc2c24592ad6f0d49084a67eb4afad Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 21 May 2016 13:24:57 +0100 Subject: [PATCH 60/90] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9d5040cd..73945e15 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,4 +1,4 @@ -Version 2.8.3.8 – Development Version +Version 2.8.3.9 – Development Version ---- **Enhancements** From b6e5c2fe14a0385eee99d48bf40b7106834ac7e2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 21 May 2016 13:25:23 +0100 Subject: [PATCH 61/90] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 60b5ad58..32b1cda7 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.8.3.8], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.8.3.9], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From d0b09eb26fc207b93c2d62e419bc143588fe0f26 Mon Sep 17 00:00:00 2001 From: vru1 Date: Sat, 21 May 2016 19:56:26 +0200 Subject: [PATCH 62/90] Update README.md Fixed typos. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index af63b91f..2a2965da 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ Debian, Ubuntu and Raspbian users can get the basics with: - `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 in many recent distributions, including Jessie and Raspioan Jessie; if not, instructions for how to build it from source for Raspian/Debian Wheezy are available at [LIBSOXR.md](https://github.com/mikebrady/shairport-sync/blob/master/LIBSOXR.md). +- `apt-get install libsoxr-dev` if you want support for libsoxr-based resampling. This library is in many recent distributions, including Jessie and Raspbian Jessie; if not, instructions for how to build it from source for Rasbpian/Debian Wheezy are available at [LIBSOXR.md](https://github.com/mikebrady/shairport-sync/blob/master/LIBSOXR.md). Download Shairport Sync: From ba2f1a67af15045cbd69f578f3683377fda4a349 Mon Sep 17 00:00:00 2001 From: vru1 Date: Sat, 21 May 2016 22:21:43 +0200 Subject: [PATCH 63/90] Update README.md If xmltoman is not installed, ./configure complains: ... checking for xmltoman... no configure: WARNING: >>xmltoman not found - not rebuilding man pages ... After xmltoman is installed, no more complains: ... checking for xmltoman... xmltoman ... Afterwards when launching make, the man pages are processed ok: ... xmltoman shairport-sync.7.xml > shairport-sync.7 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a2965da..69638ee3 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ Many Linux distributions have Avahi and OpenSSL already in place, so normally it Debian, Ubuntu and Raspbian users can get the basics with: -- `apt-get install build-essential git` – these may already be installed. +- `apt-get install build-essential git xmltoman` – these may already be installed. - `apt-get install autoconf automake 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. From 90799d45687fe6df5911008b7013af4da7c77887 Mon Sep 17 00:00:00 2001 From: vru1 Date: Sat, 21 May 2016 22:56:25 +0200 Subject: [PATCH 64/90] Update shairport-sync.spec - Fixed Version: from 2.8.2 to 2.8.3. - Fixed Release: special characters were reported as non-compliant by checkinstall - Replaced all occurrences of character Horizontal Tab, ASCII Dec #9, that was used everywhere between a colon and tag value, with a Blank Space ASCII Dec #32. Horizontal Tab caused problems when creating a .deb package, tag values were reported as non-compliant by checkinstall. - Fixed Requires: popt If a .deb package was created with Requires: popt, the .deb package could not be installed due to a missing Package popt: venca@cubox-i:~/shairport-sync$ sudo dpkg -i shairport-sync_2.8.3-1_armhf.deb Selecting previously unselected package shairport-sync. (Reading database ... 52746 files and directories currently installed.) Preparing to unpack shairport-sync_2.8.3-1_armhf.deb ... Unpacking shairport-sync (2.8.3-1) ... dpkg: dependency problems prevent configuration of shairport-sync: shairport-sync depends on popt; however: Package popt is not installed. dpkg: error processing package shairport-sync (--install): dependency problems - leaving unconfigured Processing triggers for man-db (2.7.0.2-5) ... Errors were encountered while processing: shairport-sync After Requires: popt was changed to Requires: libpopt-dev and a new .deb package was created, the new .deb package could be installed. --- shairport-sync.spec | 52 ++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/shairport-sync.spec b/shairport-sync.spec index 23c10d8e..b1d30575 100644 --- a/shairport-sync.spec +++ b/shairport-sync.spec @@ -1,29 +1,29 @@ -Name: shairport-sync -Version: 2.8.2 -Release: 1%{?dist} -Summary: AirTunes emulator. Shairport Sync adds multi-room capability with Audio Synchronisation +Name: shairport-sync +Version: 2.8.3 +Release: 1 +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 +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 -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 +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: libpopt-dev +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.) @@ -45,8 +45,8 @@ 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 + -d %{_localstatedir}/%{name} -m -g %{name} -s /sbin/nologin \ + -G audio %{name} >/dev/null %files %config /etc/shairport-sync.conf From 12419dcfafaa12b5643eb04090ee9701fff81e12 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 22 May 2016 12:22:08 +0100 Subject: [PATCH 65/90] Restore metadata feed, duh. --- configure.ac | 2 +- mdns.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 32b1cda7..dc9d7236 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.8.3.9], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.8.3.10], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) diff --git a/mdns.h b/mdns.h index a591a3b7..4c1cf247 100644 --- a/mdns.h +++ b/mdns.h @@ -25,7 +25,7 @@ typedef struct { #define MDNS_RECORD_WITH_METADATA \ "sf=0x4", "fv=76400.10", "am=AirPort4,107", "vs=105.1", "tp=TCP,UDP", "vn=65537", \ - "ss=16", "sr=44100", "da=true", "sv=false", "et=0,1", "ek=1", "cn=0,1", "ch=2", "txtvers=1", config.password ? "pw=true" : "pw=false" + METADATA_EXPRESSION, "ss=16", "sr=44100", "da=true", "sv=false", "et=0,1", "ek=1", "cn=0,1", "ch=2", "txtvers=1", config.password ? "pw=true" : "pw=false" #endif From d657360c5fdc341a5ad06744974e40843b6b89f8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 22 May 2016 12:28:02 +0100 Subject: [PATCH 66/90] Update RELEASENOTES.md --- RELEASENOTES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 73945e15..2984e40b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,9 @@ +Version 2.8.3.10 – Development Version +---- +**Bug Fix** + +* Restored metadata feed lost in 2.8.3.9. + Version 2.8.3.9 – Development Version ---- **Enhancements** From 5f6f4a2636e3d754b1677adb62375e3932e483ad Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 23 May 2016 11:58:22 +0100 Subject: [PATCH 67/90] replace the nonblocking write code --- common.c | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/common.c b/common.c index f8a8e2a4..6800de6f 100644 --- a/common.c +++ b/common.c @@ -514,26 +514,35 @@ uint64_t get_absolute_time_in_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 + void *ibuf = buf; + size_t bytes_remaining = count; + int rc = 0; struct pollfd ufds[1]; - ssize_t reply; - do { - ufds[0].fd = fd; + while ((bytes_remaining>0) && (rc==0)) { + // check that we can do some writing + 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; - + rc = poll(ufds, 1, 5000); + if (rc < 0) { + debug(1, "error waiting for pipe to become ready for writing..."); + } else if (rc == 0) { + debug(1, "timeout waiting for pipe to become ready for writing"); + rc = -2; + } else { //rc > 0, implying it might be ready + size_t bytes_written = write(fd,ibuf,bytes_remaining); + if (bytes_written==-1) { + if (!((errno == EAGAIN) || (errno == EWOULDBLOCK))) + rc = -1; + } else { + ibuf += bytes_written; + bytes_remaining -= bytes_written; + } + } + } + if (rc==0) + return bytes_remaining; + else + return rc; // return write(fd,buf,count); } From f36352dc4ece27fda24ef736183b8ff5729b8c96 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 23 May 2016 14:03:38 +0100 Subject: [PATCH 68/90] Add setting for the time to wait for the metadata pipe to come ready, otherwise give up. Default 5,000 milliseconds. --- common.c | 6 +++--- common.h | 4 ++-- configure.ac | 2 +- scripts/shairport-sync.conf | 1 + shairport.c | 10 ++++++++++ 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/common.c b/common.c index 6800de6f..d7c32f2f 100644 --- a/common.c +++ b/common.c @@ -514,7 +514,7 @@ uint64_t get_absolute_time_in_fp() { } ssize_t non_blocking_write(int fd, const void *buf, size_t count) { - void *ibuf = buf; + void *ibuf = (void *)buf; size_t bytes_remaining = count; int rc = 0; struct pollfd ufds[1]; @@ -522,11 +522,11 @@ ssize_t non_blocking_write(int fd, const void *buf, size_t count) { // check that we can do some writing ufds[0].fd = fd; ufds[0].events = POLLOUT; - rc = poll(ufds, 1, 5000); + rc = poll(ufds, 1, config.metadata_pipe_timeout); if (rc < 0) { debug(1, "error waiting for pipe to become ready for writing..."); } else if (rc == 0) { - debug(1, "timeout waiting for pipe to become ready for writing"); + warn("timeout waiting for pipe to become ready for writing"); rc = -2; } else { //rc > 0, implying it might be ready size_t bytes_written = write(fd,ibuf,bytes_remaining); diff --git a/common.h b/common.h index dc44af4c..5924be79 100644 --- a/common.h +++ b/common.h @@ -54,6 +54,7 @@ typedef struct { char *service_name; // the name for the shairport service, e.g. "Shairport Sync Version %v running on host %h" #ifdef CONFIG_METADATA int metadata_enabled; + int metadata_pipe_timeout; // in milliseconds char *metadata_pipename; char *metadata_sockaddr; int metadata_sockport; @@ -97,9 +98,8 @@ typedef struct { char *regtype; // The regtype is the service type followed by the protocol, separated by a dot, by default “_raop._tcp.”. long audio_backend_buffer_desired_length; // this will be the desired number of frames in the // audio backend buffer -- the DAC buffer for ALSA - long audio_backend_latency_offset; // this will be the offset to compensate for any fixed latency + long audio_backend_latency_offset; // this will be the offset to compensate for any fixed latency there might be in the audio path uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's native range. - // there might be in the audio } shairport_cfg; // true if Shairport Sync is supposed to be sending output to the output device, false otherwise diff --git a/configure.ac b/configure.ac index dc9d7236..d52a032f 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.8.3.10], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.8.3.11], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 4dd57505..ec0ce541 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -34,6 +34,7 @@ metadata = // 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"; +// pipe_timeout = 5000; // wait for this number of milliseconds for a blocked pipe to unblock before giving up // socket_address = "226.0.0.1"; // if set to a host name or IP address, UDP packets containing metadata will be sent to this address. May be a multicast address. "socket-port" must be non-zero and "enabled" must be set to yes" // socket_port = "5555"; // if socket_address is set, the port to send UDP packets to // socket_msglength = "65000"; // the maximum packet size for any UDP metadata. This will be clipped to be between 500 or 65000. The default is 500. diff --git a/shairport.c b/shairport.c index b79788e9..8c80bfec 100644 --- a/shairport.c +++ b/shairport.c @@ -490,6 +490,15 @@ int parse_options(int argc, char **argv) { if (config_lookup_int(config.cfg, "metadata.socket_msglength", &value)) { config.metadata_sockmsglength = value < 500 ? 500 : value > 65000 ? 65000 : value; } + if (config_lookup_int(config.cfg, "metadata.pipe_timeout", &value)) { + + if ((value < 1) || (value > 150000)) + die("Invalid timeout range \"%sd\". It should be in the range 1 to 150,000 milliseconds.", + value); + else + config.metadata_pipe_timeout = value; + } + #endif if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_play_begins", &str)) { @@ -754,6 +763,7 @@ int main(int argc, char **argv) { //snprintf(config.service_name, 20 + 100, "Shairport Sync on %s", hostname); set_requested_connection_state_to_output(1); // we expect to be able to connect to the output device config.audio_backend_buffer_desired_length = 6615; // 0.15 seconds. + config.metadata_pipe_timeout = 5000; //milliseconds config.udp_port_base = 6001; config.udp_port_range = 100; From b269b94fc24e07eaef96ad404102cd5eed86655f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 23 May 2016 14:07:31 +0100 Subject: [PATCH 69/90] Update RELEASENOTES.md --- RELEASENOTES.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 2984e40b..377af1f4 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,13 @@ +Version 2.8.3.11 – Development Version +---- +**Enhancement** + +It is now possible to set the amount of time Shiarport Sync will wait for the metadata pipe to become ready to write to. The setting is `pipe_timeout` in the `metadata` section. Default is 5,000 milliseconds. + +**Bug Fix** + +Fix some problems with the non-blocking write function used to write to the metadata pipe. + Version 2.8.3.10 – Development Version ---- **Bug Fix** From defba470396743a8d5acb3f9706cb2e6ac27e5af Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 23 May 2016 14:11:19 +0100 Subject: [PATCH 70/90] Update RELEASENOTES.md --- RELEASENOTES.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 377af1f4..3927b474 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,13 +1,13 @@ Version 2.8.3.11 – Development Version ---- -**Enhancement** - -It is now possible to set the amount of time Shiarport Sync will wait for the metadata pipe to become ready to write to. The setting is `pipe_timeout` in the `metadata` section. Default is 5,000 milliseconds. - **Bug Fix** Fix some problems with the non-blocking write function used to write to the metadata pipe. +**Enhancement** + +It is now possible to set the amount of time to wait for the metadata pipe to become ready for writing. The setting is called `pipe_timeout` in the `metadata` section. Default is 5,000 milliseconds. + Version 2.8.3.10 – Development Version ---- **Bug Fix** From 36a76f8c69f939b25418e7ebdb1d6afe5c95e1ab Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 23 May 2016 14:27:22 +0100 Subject: [PATCH 71/90] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 3927b474..ce41fa00 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,7 +2,7 @@ Version 2.8.3.11 – Development Version ---- **Bug Fix** -Fix some problems with the non-blocking write function used to write to the metadata pipe. +Fixed some problems with the non-blocking write function used to write to the metadata pipe. **Enhancement** From 2e28df289f9e2f9a9dd3e229b4bb5e625458c8fd Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 23 May 2016 14:57:42 +0100 Subject: [PATCH 72/90] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ce41fa00..933214e2 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -12,7 +12,7 @@ Version 2.8.3.10 – Development Version ---- **Bug Fix** -* Restored metadata feed lost in 2.8.3.9. +* Restored metadata feed lost in 2.8.3.7. Version 2.8.3.9 – Development Version ---- From b20ea5fea36186ef2ea72e154267e7490f690d37 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 23 May 2016 15:58:16 +0100 Subject: [PATCH 73/90] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 933214e2..1fd51e5f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -53,7 +53,7 @@ Version 2.8.3.4 – Development Version **Bug Fix** -The networking subsystem has been modified to always use the same IP number during a session. Background: the computer Shairport Sync is running on can have many IP numbers active at the same time – various IPv6 numbers and also various IPv4 numbers. During a play session, when Shairport Sync has to create connections back to the source, it would use an automatically-assigned IP number for itself, but that number might not be same as the the number used earlier in the session. From now on, it will always use the same IP number it used when the connection was first established. Thannks to [ejurgensen](https://github.com/ejurgensen) for help with this. +The networking subsystem has been modified to always use the same IP number during a session. Background: the computer Shairport Sync is running on can have many IP numbers active at the same time – various IPv6 numbers and also various IPv4 numbers. During a play session, when Shairport Sync has to create connections back to the source, it would use an automatically-assigned IP number for itself, but that number might not be same as the the number used earlier in the session. From now on, it will always use the same IP number it used when the connection was first established. Thanks to [ejurgensen](https://github.com/ejurgensen) for help with this. Changed the `mono` setting for a `playback_mode` setting with two possible values: `stereo` (default) and `mono`. From 870de08db54cf6f2f768683329a84614d7f99334 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 23 May 2016 16:43:02 +0100 Subject: [PATCH 74/90] Add a little extra diagnostic to nno-blocking write --- common.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common.c b/common.c index d7c32f2f..b4d95839 100644 --- a/common.c +++ b/common.c @@ -524,13 +524,15 @@ ssize_t non_blocking_write(int fd, const void *buf, size_t count) { ufds[0].events = POLLOUT; rc = poll(ufds, 1, config.metadata_pipe_timeout); if (rc < 0) { - debug(1, "error waiting for pipe to become ready for writing..."); + debug(1, "non-blocking write error waiting for pipe to become ready for writing..."); } else if (rc == 0) { - warn("timeout waiting for pipe to become ready for writing"); + warn("non-blocking write timeout waiting for pipe to become ready for writing"); rc = -2; } else { //rc > 0, implying it might be ready size_t bytes_written = write(fd,ibuf,bytes_remaining); if (bytes_written==-1) { + if (errno==EINTR) + debug(1,"non-blocking write interrupted by a signal interrupt"); if (!((errno == EAGAIN) || (errno == EWOULDBLOCK))) rc = -1; } else { From 8ae176933ef6baabc3b092c1cee34072c5455de6 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 23 May 2016 18:22:08 +0100 Subject: [PATCH 75/90] Check non-blocking write returns correctly, sheesh. --- rtsp.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/rtsp.c b/rtsp.c index ec4eb7c5..604b9989 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1119,13 +1119,17 @@ void metadata_process(uint32_t type, uint32_t code, char *data, "%x%x%u", type, code, length); ret = non_blocking_write(fd, thestring, strlen(thestring)); - if (ret < 1) + if (ret < 0) { + debug(1,"metadata_process error %d exit 1",ret); return; + } if ((data != NULL) && (length > 0)) { snprintf(thestring, 1024, "\n\n"); ret = non_blocking_write(fd, thestring, strlen(thestring)); - if (ret < 1) // no reader + if (ret < 0) { + debug(1,"metadata_process error %d exit 2",ret); 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. @@ -1146,20 +1150,26 @@ void metadata_process(uint32_t type, uint32_t code, char *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) + if (ret < 0) { + debug(1,"metadata_process error %d exit 3",ret); return; + } remaining_data += towrite_count; remaining_count -= towrite_count; } snprintf(thestring, 1024, ""); ret = non_blocking_write(fd, thestring, strlen(thestring)); - if (ret < 1) // no reader + if (ret < 0) { + debug(1,"metadata_process error %d exit 4",ret); return; + } } snprintf(thestring, 1024, "\n"); ret = non_blocking_write(fd, thestring, strlen(thestring)); - if (ret < 1) // no reader + if (ret < 0) { + debug(1,"metadata_process error %d exit 5",ret); return; + } } void *metadata_thread_function(void *ignore) { From 28efe16f7330fe4f7dc14215d4d5a1cc02f53da2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Krause?= Date: Mon, 23 May 2016 21:58:18 +0200 Subject: [PATCH 76/90] Fix build without metadata support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit f36352dc4ece27fda24ef736183b8ff5729b8c96 introduced the parameter pipe_timeout for the metadata config. If shairport-sync is now configured without metadata support, the build fails with: ``` shairport.c: In function ‘main’: shairport.c:766:9: error: ‘shairport_cfg {aka struct }’ has no member named ‘metadata_pipe_timeout’ config.metadata_pipe_timeout = 5000; //milliseconds ``` Fix this by opting-out the affected code with CONFIG_METADATA. --- common.c | 2 ++ shairport.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/common.c b/common.c index b4d95839..8a200d32 100644 --- a/common.c +++ b/common.c @@ -513,6 +513,7 @@ uint64_t get_absolute_time_in_fp() { return time_now_fp; } +#ifdef CONFIG_METADATA ssize_t non_blocking_write(int fd, const void *buf, size_t count) { void *ibuf = (void *)buf; size_t bytes_remaining = count; @@ -547,6 +548,7 @@ ssize_t non_blocking_write(int fd, const void *buf, size_t count) { return rc; // return write(fd,buf,count); } +#endif /* from http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722 */ diff --git a/shairport.c b/shairport.c index 8c80bfec..58c1dc54 100644 --- a/shairport.c +++ b/shairport.c @@ -763,7 +763,9 @@ int main(int argc, char **argv) { //snprintf(config.service_name, 20 + 100, "Shairport Sync on %s", hostname); set_requested_connection_state_to_output(1); // we expect to be able to connect to the output device config.audio_backend_buffer_desired_length = 6615; // 0.15 seconds. +#ifdef CONFIG_METADATA config.metadata_pipe_timeout = 5000; //milliseconds +#endif config.udp_port_base = 6001; config.udp_port_range = 100; From e5c6ea8b295524ec439d6c10f6f1729f505b1831 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 23 May 2016 21:10:15 +0100 Subject: [PATCH 77/90] Add some diagnostics to non_blocking_write/3. --- common.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/common.c b/common.c index b4d95839..366b9618 100644 --- a/common.c +++ b/common.c @@ -531,10 +531,8 @@ ssize_t non_blocking_write(int fd, const void *buf, size_t count) { } else { //rc > 0, implying it might be ready size_t bytes_written = write(fd,ibuf,bytes_remaining); if (bytes_written==-1) { - if (errno==EINTR) - debug(1,"non-blocking write interrupted by a signal interrupt"); - if (!((errno == EAGAIN) || (errno == EWOULDBLOCK))) - rc = -1; + debug(1,"Error %d in non_blocking_write: \"%s\".",errno,strerror(errno)); + rc = -1; } else { ibuf += bytes_written; bytes_remaining -= bytes_written; @@ -542,7 +540,7 @@ ssize_t non_blocking_write(int fd, const void *buf, size_t count) { } } if (rc==0) - return bytes_remaining; + return count-bytes_remaining; // this is just to mimic a normal write/3. else return rc; // return write(fd,buf,count); From a1b975106a582bc02370345e6f2a70243ad58cd8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 26 May 2016 08:28:06 +0100 Subject: [PATCH 78/90] Quieten some debug messages. --- common.c | 6 +++--- rtsp.c | 21 ++++++++++----------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/common.c b/common.c index 1208f603..0dc8a0b7 100644 --- a/common.c +++ b/common.c @@ -525,14 +525,14 @@ ssize_t non_blocking_write(int fd, const void *buf, size_t count) { ufds[0].events = POLLOUT; rc = poll(ufds, 1, config.metadata_pipe_timeout); if (rc < 0) { - debug(1, "non-blocking write error waiting for pipe to become ready for writing..."); + // debug(1, "non-blocking write error waiting for pipe to become ready for writing..."); } else if (rc == 0) { - warn("non-blocking write timeout waiting for pipe to become ready for writing"); + // warn("non-blocking write timeout waiting for pipe to become ready for writing"); rc = -2; } else { //rc > 0, implying it might be ready size_t bytes_written = write(fd,ibuf,bytes_remaining); if (bytes_written==-1) { - debug(1,"Error %d in non_blocking_write: \"%s\".",errno,strerror(errno)); + // debug(1,"Error %d in non_blocking_write: \"%s\".",errno,strerror(errno)); rc = -1; } else { ibuf += bytes_written; diff --git a/rtsp.c b/rtsp.c index 604b9989..e78456f7 100644 --- a/rtsp.c +++ b/rtsp.c @@ -888,8 +888,8 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, #ifdef CONFIG_METADATA if (!strncmp(cp, "progress: ", 10)) { char *progress = cp + 10; - debug(2, "progress: \"%s\"\n", - progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per second + //debug(2, "progress: \"%s\"\n", + // progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per second send_ssnc_metadata('prgr', strdup(progress), strlen(progress), 1); } else #endif @@ -1092,8 +1092,7 @@ static void metadata_close(void) { 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); + // debug(2, "Process metadata with type %x, code %x and length %u.", type, code, length); int ret; if (metadata_sock >= 0 && length < config.metadata_sockmsglength - 8) { @@ -1120,14 +1119,14 @@ void metadata_process(uint32_t type, uint32_t code, char *data, code, length); ret = non_blocking_write(fd, thestring, strlen(thestring)); if (ret < 0) { - debug(1,"metadata_process error %d exit 1",ret); + // debug(1,"metadata_process error %d exit 1",ret); return; } if ((data != NULL) && (length > 0)) { snprintf(thestring, 1024, "\n\n"); ret = non_blocking_write(fd, thestring, strlen(thestring)); if (ret < 0) { - debug(1,"metadata_process error %d exit 2",ret); + // debug(1,"metadata_process error %d exit 2",ret); return; } // here, we write the data in base64 form using our nice base64 encoder @@ -1151,7 +1150,7 @@ void metadata_process(uint32_t type, uint32_t code, char *data, // %d.",remaining_count,ret,outbuf_size); ret = non_blocking_write(fd, outbuf, outbuf_size); if (ret < 0) { - debug(1,"metadata_process error %d exit 3",ret); + // debug(1,"metadata_process error %d exit 3",ret); return; } remaining_data += towrite_count; @@ -1160,14 +1159,14 @@ void metadata_process(uint32_t type, uint32_t code, char *data, snprintf(thestring, 1024, ""); ret = non_blocking_write(fd, thestring, strlen(thestring)); if (ret < 0) { - debug(1,"metadata_process error %d exit 4",ret); + // debug(1,"metadata_process error %d exit 4",ret); return; } } snprintf(thestring, 1024, "\n"); ret = non_blocking_write(fd, thestring, strlen(thestring)); if (ret < 0) { - debug(1,"metadata_process error %d exit 5",ret); + // debug(1,"metadata_process error %d exit 5",ret); return; } } @@ -1295,7 +1294,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, char *ct = msg_get_header(req, "Content-Type"); if (ct) { - debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct); + // debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct); #ifdef CONFIG_METADATA // It seems that the rtptime of the message is used as a kind of an ID that @@ -1369,7 +1368,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, } else #endif if (!strncmp(ct, "text/parameters", 15)) { - debug(2, "received parameters in SET_PARAMETER request."); + // debug(2, "received parameters in SET_PARAMETER request."); handle_set_parameter_parameter(conn, req, resp); // this could be volume or progress } else { From a0b9b74623927a28933497154258ae6dab05f45f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 26 May 2016 17:57:26 +0100 Subject: [PATCH 79/90] Candidate 2.8.4 release --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index d52a032f..fdcba4df 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.8.3.11], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.8.3.12], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From e4d49e27cd41830232a000cd2c5d1075a9bcb3d2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 26 May 2016 18:00:21 +0100 Subject: [PATCH 80/90] Update RELEASENOTES.md --- RELEASENOTES.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1fd51e5f..5736ce6e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,11 @@ +Version 2.8.3.12 – Development Version +---- +This is a candidate for stable release 2.8.4 + +**Changes** + +Some debug messages have been quietened. + Version 2.8.3.11 – Development Version ---- **Bug Fix** From 0a328ab305d199da2b6fabf472dd45367c2d176f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 27 May 2016 19:15:28 +0100 Subject: [PATCH 81/90] Update man and html man entries to reflect new features. --- man/shairport-sync.7 | 72 ++++++++++++++++++---- man/shairport-sync.7.xml | 129 +++++++++++++++++++++++++++++---------- man/shairport-sync.html | 114 ++++++++++++++++++++++++++-------- 3 files changed, 246 insertions(+), 69 deletions(-) diff --git a/man/shairport-sync.7 b/man/shairport-sync.7 index b261ec60..85202518 100644 --- a/man/shairport-sync.7 +++ b/man/shairport-sync.7 @@ -6,8 +6,6 @@ shairport-sync \- Synchronised Audio Player for iTunes / AirPlay shairport-sync -D\fB -shairport-sync -h\fB - shairport-sync -k\fB shairport-sync -h\fB @@ -49,7 +47,7 @@ Settings are organised into groups, for example, there is a "general" group of s \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 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. +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 specified. 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. @@ -61,7 +59,11 @@ The configuration file is processed using the \fIlibconfig\f1 library -- see \fB 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 ". +Use this \fIservice_name\f1 to identify this player in iTunes, etc. + +The following substitutions are allowed: \fB%h\f1 for the computer's hostname, \fB%H\f1 for the computer's hostname with the first letter capitalised (ASCII only), \fB%v\f1 for the Shairport Sync version number, e.g. "2.8.4" and \fB%V\f1 for the Shairport Sync version string, e.g. "2.8.4-OpenSSL-Avahi-ALSA-soxr-metadata". + +The default is "%H", which is replaced by the hostname with the first letter capitalised. .TP \fBpassword=\f1\fI"password"\f1\fB;\f1 Require the password \fIpassword\f1 to connect to the service. If you leave this setting commented out, no password is needed. @@ -100,7 +102,21 @@ Use this to specify how much debugging information should be output or logged. " Set this \fIchoice\f1 to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. This might be useful if you want to set the volume on the output device, independently of the setting at the source. The default is "no". .TP \fBvolume_range_db=\f1\fIdBvalue\f1\fB;\f1 -Set this \fIdBvalue\f1 to use just a portion of the full range of attenuation offered by a mixer. For example, if a mixer has a minimum volume of -80 dB and a maximum of +20 dB, you might wish to use only 60 dB of the 100 dB available. This might be because the sound becomes inaudible at the lowest setting and unbearably loud at the highest setting -- indeed, many domestic HiFi systmes have a volume copntrol range of just 60 to 80dB. Another potential use might be where the range specified by the mixer does not match the capabilities of the device. For example, the Raspberry Pi's DAC that feeds the built-in audio jack claims a range of 106 dB but has a useful range of only about 35dB. The setting allows you to specify the maximum range from highest to lowest. The range suggested for the Raspberry Pi's built-in audio DAC, which feeds the headphone jack, is 35. Using it in this case gives the volume control a much more useful range of settings. If you omit this setting, the full "native" range of the mixer is used. +Use this \fIdBvalue\f1 to reduce or increase the attenuation range, in decibels, between the minimum and maximum volume. + +For example, if a mixer has a minimum volume of -80 dB and a maximum of +20 dB, you might wish to use only 60 dB of the 100 dB available. This might be because the sound becomes inaudible at the lowest setting and unbearably loud at the highest setting -- indeed, many domestic HiFi systems have a volume control range of just 60 to 80dB. + +Another potential use might be where the range specified by the mixer does not match the capabilities of the device. For example, the Raspberry Pi's DAC that feeds the built-in audio jack claims a range of 106 dB but has a useful range of only about 35dB. The setting allows you to specify the maximum range from highest to lowest. The range suggested for the Raspberry Pi's built-in audio DAC, which feeds the headphone jack, is 35. Using it in this case gives the volume control a much more useful range of settings. + +As a third example, you can actually extend the range provided by a mixer. Many cheaper DACs have hardware mixers that offer a restricted attenuation range. If you specify a volume range greater than the range of the mixer, software attenuation and hardware attenuation will be combined to give the specified range. + +If you omit this setting, the full "native" range of the mixer is used. +.TP +\fBregtype=\f1\fI"regTypeString"\f1\fB;\f1 +Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp". +.TP +\fBplayback_mode=\f1\fI"mode"\f1\fB;\f1 +The \fImode\f1 can be "stereo" or "mono". Default is "stereo". .TP \fB"ALSA" SETTINGS\f1 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: @@ -122,6 +138,15 @@ Set this \fIoffset\f1, in frames, to compensate for a fixed delay in the audio b \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 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 +\fBdisable_synchronization=\f1\fI"no"\f1\fB;\f1 +This is an advanced setting and is for debugging only. Set to "yes" to disable synchronization. Default is "no". If you use it to disable synchronisation, then soner or later you'll experience audio glitches due to audio buffer overflow or underflow. +.TP +\fBperiod_size=\f1\fInumber\f1\fB;\f1 +Use this optional advanced setting to set the alsa period size near to this value. +.TP +\fBbuffer_size=\f1\fInumber\f1\fB;\f1 +Use this optional advanced setting to set the alsa buffer size near to this value. +.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. @@ -182,8 +207,17 @@ Set the \fIchoice\f1 to "yes" to enable shairport-sync to look for cover art fro \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 +\fBsocket_address=\f1\fI"hostnameOrIP"\f1\fB;\f1 +If \fIhostnameOrIP\f1 is set to a host name or and IP address, UDP packets containing metadata will be sent to this address. May be a multicast address. "socket-port" must be non-zero and "enabled" must be set to "yes". +.TP +\fBsocket_port=\f1\fIport\f1\fB;\f1 +If \fBsocket_address\f1 is set, use \fIport\f1 to specify the port to send UDP packets to. Must not be zero. +.TP +\fBsocket_msglength=\f1\fI65000\f1\fB;\f1 +The maximum packet size for any UDP metadata. This must be between 500 or 65000. The default is 500. +.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. @@ -207,9 +241,9 @@ Latency is the exact time from a sound signal's original timestamp until that si Shairport Sync now sets latencies automatically using information supplied by the source, typically either 88,200 or 99,577 frames. -The following relates to the old scheme of using fixed latencies, which ignores the latency information supplied by the source. There are four default latency settings. 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. +The following relates to the old scheme of using fixed latencies, which ignored the latency information supplied by the source. There are four default latency settings. 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. -Note: If you are thinking of changing latencies to compensate for a delay in the audio output device, then instead of changing these individual latencies, consider using the \fBaudio_backend_latency_offset\f1 setting in the \fBalsa\f1 group (or the appropriate other group if you're not outputing through the alsa backend). +Note: If you are thinking of changing latencies to compensate for a delay in the audio output device, then instead of changing these individual latencies, use the \fBaudio_backend_latency_offset\f1 setting in the \fBalsa\f1 group (or the appropriate other group if you're not outputing through the alsa backend). .TP \fBitunes=\f1\fIlatency\f1\fB;\f1 This is the \fIlatency\f1, in frames, used for iTunes 10 or later. Default is 99,400. @@ -232,10 +266,14 @@ The command line for shairport-sync can take two kinds of options: regular \fBpr These options are used by shairport-sync itself. .TP \fB-a \f1\fIservice name\f1\fB | --name=\f1\fIservice name\f1 -Use this \fIservice name\f1 to identify this player in iTunes, etc. The default name is "Shairport Sync on ". +Use this \fIservice name\f1 to identify this player in iTunes, etc. The following substitutions are allowed: \fB%h\f1 for the computer's hostname, \fB%H\f1 for the computer's hostname with the first letter capitalised (ASCII only), \fB%v\f1 for the Shairport Sync version number, e.g. "2.8.4" and \fB%V\f1 for the Shairport Sync version string, e.g. "2.8.4-OpenSSL-Avahi-ALSA-soxr-metadata". + +The default is "%H", which is replaced by the hostname with the first letter capitalised. .TP \fB-A | --AirPlayLatency=\f1\fIlatency\f1 Use this \fIlatency\f1, in frames, for audio streamed from an AirPlay device. The default is 88,200 frames, where there are 44,100 frames to the second. + +Please note that this feature is deprecated and will be removed in a future version of Shairport Sync. .TP \fB-B \f1\fIprogram\f1\fB | --on-start=\f1\fIprogram\f1 Execute \fIprogram\f1 when playback is about to begin. Specify the full path to the program, e.g. \fI/usr/bin/logger\f1. Executable scripts can be used, but they must have \fI#!/bin/sh\f1 (or whatever is appropriate) in the headline. @@ -247,6 +285,8 @@ Read configuration settings from \fIfilename\f1. The default is to read them fro .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). + +Please note that this feature is deprecated and will be removed in a future version of Shairport Sync. .TP \fB-d | --daemon\f1 Instruct shairport-sync to demonise itself. It will write its Process ID (PID) to a file, usually at \fI/var/run/shairport-sync.pid\f1, which is used by the \fB-k\f1, \fB-D\f1 and \fB-R\f1 options to locate the daemon at a later time. @@ -258,6 +298,8 @@ If you want shairport-sync to wait until the command has completed before contin .TP \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. + +Please note that this feature is deprecated and will be removed in a future version of Shairport Sync. .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. @@ -269,12 +311,16 @@ Print brief help message and exit. .TP \fB-i | --iTunesLatency=\f1\fIlatency\f1 Use this \fIlatency\f1, in frames, for audio streamed from an iTunes source, where iTunes is Version 10 or later. The default is 99,400 frames, where there are 44,100 frames to the second. If the source is iTunes but is earler than Version 10, the \fIdefault latency\f1 is used (see the \fB-L\f1 option). Some third party programs masquerade as older versions of iTunes. + +Please note that this feature is deprecated and will be removed in a future version of Shairport Sync. .TP \fB-k | --kill\f1 Kill the shairport-sync daemon and exit. (Requires that the daemon has written its PID to an agreed file -- see the \fB-d\f1 option). .TP \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. + +Please note that this feature is deprecated and will be removed in a future version of Shairport Sync. .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 \fBhttps://github.com/mikebrady/shairport-sync-metadata-reader\f1 for a sample metadata reader. @@ -293,6 +339,8 @@ Require the password \fIsecret\f1 to be able to connect and stream to the servic .TP \fB-R | --reconnectToOutput\f1 Reconnect the shairport-sync daemon to the output device and exit. It may take a few seconds to synchronise. (Requires that the daemon has written its PID to an agreed file -- see the \fB-d\f1 option). + +Please note that this feature is deprecated and will be removed in a future version of Shairport Sync. .TP \fB-r \f1\fIthreshold\f1\fB | --resync=\f1\fIthreshold\f1 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 \fB0\f1 to disable resynchronisation. @@ -320,9 +368,9 @@ Print debug information. Repeat up to three times to get more detail. \fB-w | --wait-cmd\f1 Wait for commands specified using \fB-B\f1 or \fB-E\f1 to complete before continuing execution. .SH AUDIO BACKEND OPTIONS -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 \fB--\f1 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. +These options are passed to the chosen audio backend. The audio backend options are preceded by a \fB--\f1 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 \fB-d\f1 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 \fB-t\f1 option; (b) you must specify where the mixer is to be found using the \fB-m\f1 option and finally (c) you must specify the name of the level control you are using with the \fB-c\f1 option. +In the ALSA backend, audio is sent to an output device which you can specify using the \fB-d\f1 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 in software. To use a hardware level control on a mixer on the sound card, specify the name of the mixer control with the \fB-c\f1 option. If the mixer is not associated with the output device, then you need to specify where the mixer is to be found with the \fB-m\f1 option. .TP \fB-c \f1\fIcontrolname\f1 Use the level control called \fIcontrolname\f1 on the hardware mixer for controlling volume. This is needed if the mixer type is specified, using the \fB-t\f1 option, to be \fBhardware\f1. There is no default. @@ -336,7 +384,7 @@ Use the specified hardware \fImixer\f1 for volume control. Use this to specify w \fB-t \f1\fIdevicetype\f1 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: +Here is a slightly contrived 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-c PCM\f1 diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml index 3738eb9a..0ba76f56 100644 --- a/man/shairport-sync.7.xml +++ b/man/shairport-sync.7.xml @@ -3,8 +3,8 @@