2
0
philippe44 5 жил өмнө
parent
commit
9b5bdd8cb4
17 өөрчлөгдсөн 5996 нэмэгдсэн , 0 устгасан
  1. 5 0
      CMakeLists.txt
  2. 68 0
      Kconfig.projbuild
  3. 115 0
      buffer.c
  4. 8 0
      component.mk
  5. 296 0
      decode.c
  6. 12 0
      esp32.c
  7. 837 0
      main.c
  8. 450 0
      output.c
  9. 154 0
      output_dac.c
  10. 365 0
      output_pack.c
  11. 438 0
      pcm.c
  12. 148 0
      scan.c
  13. 976 0
      slimproto.c
  14. 185 0
      slimproto.h
  15. 786 0
      squeezelite.h
  16. 590 0
      stream.c
  17. 563 0
      utils.c

+ 5 - 0
CMakeLists.txt

@@ -0,0 +1,5 @@
+cmake_minimum_required(VERSION 3.5)
+set(COMPONENT_SRCS "scan.c")
+set(COMPONENT_ADD_INCLUDEDIRS ".")
+
+register_component()

+ 68 - 0
Kconfig.projbuild

@@ -0,0 +1,68 @@
+menu "Example Configuration"
+
+    config WIFI_SSID
+        string "WiFi SSID"
+        default "myssid"
+        help
+            SSID (network name) for the example to connect to.
+
+    config WIFI_PASSWORD
+        string "WiFi Password"
+        default "mypassword"
+        help
+            WiFi password (WPA or WPA2) for the example to use.
+
+    choice SCAN_METHOD
+        prompt "scan method"
+        default WIFI_FAST_SCAN
+        help
+            scan method for the esp32 to use
+
+        config WIFI_FAST_SCAN
+            bool "fast"
+        config WIFI_ALL_CHANNEL_SCAN
+            bool "all"
+    endchoice
+
+    choice SORT_METHOD
+        prompt "sort method"
+        default WIFI_CONNECT_AP_BY_SIGNAL
+        help
+            sort method for the esp32 to use
+
+        config WIFI_CONNECT_AP_BY_SIGNAL
+            bool "rssi"
+        config WIFI_CONNECT_AP_BY_SECURITY
+            bool "authmode"
+    endchoice
+
+    config FAST_SCAN_THRESHOLD
+        bool "fast scan threshold"
+        default y
+        help
+            wifi fast scan threshold
+
+    config FAST_SCAN_MINIMUM_SIGNAL
+        int "fast scan minimum rssi"
+        depends on FAST_SCAN_THRESHOLD
+        range -127 0
+        default -127
+        help
+            rssi is use to measure the signal
+
+    choice FAST_SCAN_WEAKEST_AUTHMODE
+        prompt "fast scan weakest authmode"
+        depends on FAST_SCAN_THRESHOLD
+        default EXAMPLE_OPEN
+
+        config EXAMPLE_OPEN
+            bool "open"
+        config EXAMPLE_WEP
+            bool "wep"
+        config EXAMPLE_WPA
+            bool "wpa"
+        config EXAMPLE_WPA2
+            bool "wpa2"
+    endchoice
+
+endmenu

+ 115 - 0
buffer.c

@@ -0,0 +1,115 @@
+/* 
+ *  Squeezelite - lightweight headless squeezebox emulator
+ *
+ *  (c) Adrian Smith 2012-2015, triode1@btinternet.com
+ *      Ralph Irving 2015-2017, ralph_irving@hotmail.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+// fifo bufffers 
+
+#define _GNU_SOURCE
+
+#include "squeezelite.h"
+
+// _* called with muxtex locked
+
+inline unsigned _buf_used(struct buffer *buf) {
+	return buf->writep >= buf->readp ? buf->writep - buf->readp : buf->size - (buf->readp - buf->writep);
+}
+
+unsigned _buf_space(struct buffer *buf) {
+	return buf->size - _buf_used(buf) - 1; // reduce by one as full same as empty otherwise
+}
+
+unsigned _buf_cont_read(struct buffer *buf) {
+	return buf->writep >= buf->readp ? buf->writep - buf->readp : buf->wrap - buf->readp;
+}
+
+unsigned _buf_cont_write(struct buffer *buf) {
+	return buf->writep >= buf->readp ? buf->wrap - buf->writep : buf->readp - buf->writep;
+}
+
+void _buf_inc_readp(struct buffer *buf, unsigned by) {
+	buf->readp += by;
+	if (buf->readp >= buf->wrap) {
+		buf->readp -= buf->size;
+	}
+}
+
+void _buf_inc_writep(struct buffer *buf, unsigned by) {
+	buf->writep += by;
+	if (buf->writep >= buf->wrap) {
+		buf->writep -= buf->size;
+	}
+}
+
+void buf_flush(struct buffer *buf) {
+	mutex_lock(buf->mutex);
+	buf->readp  = buf->buf;
+	buf->writep = buf->buf;
+	mutex_unlock(buf->mutex);
+}
+
+// adjust buffer to multiple of mod bytes so reading in multiple always wraps on frame boundary
+void buf_adjust(struct buffer *buf, size_t mod) {
+	size_t size;
+	mutex_lock(buf->mutex);
+	size = ((unsigned)(buf->base_size / mod)) * mod;
+	buf->readp  = buf->buf;
+	buf->writep = buf->buf;
+	buf->wrap   = buf->buf + size;
+	buf->size   = size;
+	mutex_unlock(buf->mutex);
+}
+
+// called with mutex locked to resize, does not retain contents, reverts to original size if fails
+void _buf_resize(struct buffer *buf, size_t size) {
+	free(buf->buf);
+	buf->buf = malloc(size);
+	if (!buf->buf) {
+		size    = buf->size;
+		buf->buf= malloc(size);
+		if (!buf->buf) {
+			size = 0;
+		}
+	}
+	buf->readp  = buf->buf;
+	buf->writep = buf->buf;
+	buf->wrap   = buf->buf + size;
+	buf->size   = size;
+	buf->base_size = size;
+}
+
+void buf_init(struct buffer *buf, size_t size) {
+	buf->buf    = malloc(size);
+	buf->readp  = buf->buf;
+	buf->writep = buf->buf;
+	buf->wrap   = buf->buf + size;
+	buf->size   = size;
+	buf->base_size = size;
+	mutex_create_p(buf->mutex);
+}
+
+void buf_destroy(struct buffer *buf) {
+	if (buf->buf) {
+		free(buf->buf);
+		buf->buf = NULL;
+		buf->size = 0;
+		buf->base_size = 0;
+		mutex_destroy(buf->mutex);
+	}
+}

+ 8 - 0
component.mk

@@ -0,0 +1,8 @@
+#
+# "main" pseudo-component makefile.
+#
+# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
+CFLAGS += -DPOSIX -DLINKALL -DLOOPBACK -DDACAUDIO
+
+
+

+ 296 - 0
decode.c

@@ -0,0 +1,296 @@
+/* 
+ *  Squeezelite - lightweight headless squeezebox emulator
+ *
+ *  (c) Adrian Smith 2012-2015, triode1@btinternet.com
+ *      Ralph Irving 2015-2017, ralph_irving@hotmail.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+// decode thread
+
+#include "squeezelite.h"
+
+log_level loglevel;
+
+extern struct buffer *streambuf;
+extern struct buffer *outputbuf;
+extern struct streamstate stream;
+extern struct outputstate output;
+extern struct processstate process;
+
+struct decodestate decode;
+struct codec *codecs[MAX_CODECS];
+struct codec *codec;
+static bool running = true;
+
+#define LOCK_S   mutex_lock(streambuf->mutex)
+#define UNLOCK_S mutex_unlock(streambuf->mutex)
+#define LOCK_O   mutex_lock(outputbuf->mutex)
+#define UNLOCK_O mutex_unlock(outputbuf->mutex)
+#define LOCK_D   mutex_lock(decode.mutex);
+#define UNLOCK_D mutex_unlock(decode.mutex);
+
+#if PROCESS
+#define IF_DIRECT(x)    if (decode.direct) { x }
+#define IF_PROCESS(x)   if (!decode.direct) { x }
+#define MAY_PROCESS(x)  { x }
+#else
+#define IF_DIRECT(x)    { x }
+#define IF_PROCESS(x)
+#define MAY_PROCESS(x)
+#endif
+
+static void *decode_thread() {
+
+	while (running) {
+		size_t bytes, space, min_space;
+		bool toend;
+		bool ran = false;
+
+		LOCK_S;
+		bytes = _buf_used(streambuf);
+		toend = (stream.state <= DISCONNECT);
+		UNLOCK_S;
+		LOCK_O;
+		space = _buf_space(outputbuf);
+		UNLOCK_O;
+
+		LOCK_D;
+
+		if (decode.state == DECODE_RUNNING && codec) {
+		
+			LOG_SDEBUG("streambuf bytes: %u outputbuf space: %u", bytes, space);
+
+			IF_DIRECT(
+				min_space = codec->min_space;
+			);
+			IF_PROCESS(
+				min_space = process.max_out_frames * BYTES_PER_FRAME;
+			);
+			
+			if (space > min_space && (bytes > codec->min_read_bytes || toend)) {
+				
+				decode.state = codec->decode();
+
+				IF_PROCESS(
+					if (process.in_frames) {
+						process_samples();
+					}
+
+					if (decode.state == DECODE_COMPLETE) {
+						process_drain();
+					}
+				);
+
+				if (decode.state != DECODE_RUNNING) {
+
+					LOG_INFO("decode %s", decode.state == DECODE_COMPLETE ? "complete" : "error");
+
+					LOCK_O;
+					if (output.fade_mode) _checkfade(false);
+					UNLOCK_O;
+
+					wake_controller();
+				}
+
+				ran = true;
+			}
+		}
+		
+		UNLOCK_D;
+
+		if (!ran) {
+			usleep(100000);
+		}
+	}
+
+	return 0;
+}
+
+static void sort_codecs(int pry, struct codec* ptr) {
+	static int priority[MAX_CODECS];
+	int i, tpry;
+	struct codec* tptr;
+
+	for (i = 0; i < MAX_CODECS; i++) {
+		if (!codecs[i]) {
+			codecs[i] = ptr;
+			priority[i] = pry;
+			return;
+		}
+		if (pry < priority[i]) {
+			tptr = codecs[i];
+			codecs[i] = ptr;
+			ptr = tptr;
+			tpry = priority[i];
+			priority[i] = pry;
+			pry = tpry;
+		}
+	}
+}
+
+static thread_type thread;
+
+void decode_init(log_level level, const char *include_codecs, const char *exclude_codecs) {
+	int i;
+	char* order_codecs = NULL;
+
+	loglevel = level;
+
+	LOG_INFO("init decode");
+
+	// register codecs
+	// dsf,dff,alc,wma,wmap,wmal,aac,spt,ogg,ogf,flc,aif,pcm,mp3
+	i = 0;
+/*	
+#if DSD
+	if (!strstr(exclude_codecs, "dsd")	&& (!include_codecs || (order_codecs = strstr(include_codecs, "dsd"))))
+		sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_dsd());
+#endif
+#if FFMPEG
+	if (!strstr(exclude_codecs, "alac") && (!include_codecs || (order_codecs = strstr(include_codecs, "alac"))))
+		sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_ff("alc"));
+	if (!strstr(exclude_codecs, "wma")	&& (!include_codecs || (order_codecs = strstr(include_codecs, "wma"))))
+		sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_ff("wma"));
+#endif
+#ifndef NO_FAAD
+	if (!strstr(exclude_codecs, "aac")	&& (!include_codecs || (order_codecs = strstr(include_codecs, "aac"))))
+		sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_faad());
+#endif
+	if (!strstr(exclude_codecs, "ogg")	&& (!include_codecs || (order_codecs = strstr(include_codecs, "ogg"))))
+		sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_vorbis());
+	if (!strstr(exclude_codecs, "flac") && (!include_codecs || (order_codecs = strstr(include_codecs, "flac"))))
+		sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_flac());
+*/	
+	if (!strstr(exclude_codecs, "pcm")	&& (!include_codecs || (order_codecs = strstr(include_codecs, "pcm"))))
+		sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_pcm());
+/*
+	// try mad then mpg for mp3 unless command line option passed
+	if (!(strstr(exclude_codecs, "mp3") || strstr(exclude_codecs, "mad")) &&
+		(!include_codecs || (order_codecs = strstr(include_codecs, "mp3")) || (order_codecs = strstr(include_codecs, "mad"))))
+		sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_mad());
+	else if (!(strstr(exclude_codecs, "mp3") || strstr(exclude_codecs, "mpg")) &&
+		(!include_codecs || (order_codecs = strstr(include_codecs, "mp3")) || (order_codecs = strstr(include_codecs, "mpg"))))
+		sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_mpg());
+*/		
+
+	LOG_DEBUG("include codecs: %s exclude codecs: %s", include_codecs ? include_codecs : "", exclude_codecs);
+
+	mutex_create(decode.mutex);
+
+#if LINUX || OSX || FREEBSD || POSIX
+	pthread_attr_t attr;
+	pthread_attr_init(&attr);
+#ifdef PTHREAD_STACK_MIN
+	pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + DECODE_THREAD_STACK_SIZE);
+#endif
+	pthread_create(&thread, &attr, decode_thread, NULL);
+	pthread_attr_destroy(&attr);
+#endif
+#if WIN
+	thread = CreateThread(NULL, DECODE_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&decode_thread, NULL, 0, NULL);
+#endif
+
+	decode.new_stream = true;
+	decode.state = DECODE_STOPPED;
+
+	MAY_PROCESS(
+		decode.direct = true;
+		decode.process = false;
+	);
+}
+
+void decode_close(void) {
+	LOG_INFO("close decode");
+	LOCK_D;
+	if (codec) {
+		codec->close();
+		codec = NULL;
+	}
+	running = false;
+	UNLOCK_D;
+#if LINUX || OSX || FREEBSD
+	pthread_join(thread, NULL);
+#endif
+	mutex_destroy(decode.mutex);
+}
+
+void decode_flush(void) {
+	LOG_INFO("decode flush");
+	LOCK_D;
+	decode.state = DECODE_STOPPED;
+	IF_PROCESS(
+		process_flush();
+	);
+	UNLOCK_D;
+}
+
+unsigned decode_newstream(unsigned sample_rate, unsigned supported_rates[]) {
+
+	// called with O locked to get sample rate for potentially processed output stream
+	// release O mutex during process_newstream as it can take some time
+
+	MAY_PROCESS(
+		if (decode.process) {
+			UNLOCK_O;
+			sample_rate = process_newstream(&decode.direct, sample_rate, supported_rates);
+			LOCK_O;
+		}
+	);
+
+	return sample_rate;
+}
+
+void codec_open(u8_t format, u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t endianness) {
+	int i;
+
+	LOG_INFO("codec open: '%c'", format);
+
+	LOCK_D;
+
+	decode.new_stream = true;
+	decode.state = DECODE_STOPPED;
+
+	MAY_PROCESS(
+		decode.direct = true; // potentially changed within codec when processing enabled
+	);
+
+	// find the required codec
+	for (i = 0; i < MAX_CODECS; ++i) {
+
+		if (codecs[i] && codecs[i]->id == format) {
+
+			if (codec && codec != codecs[i]) {
+				LOG_INFO("closing codec: '%c'", codec->id);
+				codec->close();
+			}
+			
+			codec = codecs[i];
+			
+			codec->open(sample_size, sample_rate, channels, endianness);
+
+			decode.state = DECODE_READY;
+
+			UNLOCK_D;
+			return;
+		}
+	}
+
+	UNLOCK_D;
+
+	LOG_ERROR("codec not found");
+}
+

+ 12 - 0
esp32.c

@@ -0,0 +1,12 @@
+#include <signal.h>
+
+#include "esp_system.h" 
+#include "squeezelite.h"
+
+void get_mac(u8_t mac[]) {
+    esp_read_mac(mac, ESP_MAC_WIFI_STA);
+}
+
+_sig_func_ptr signal(int sig, _sig_func_ptr func) {
+	return NULL;
+}

+ 837 - 0
main.c

@@ -0,0 +1,837 @@
+/* 
+ *  Squeezelite - lightweight headless squeezebox emulator
+ *
+ *  (c) Adrian Smith 2012-2015, triode1@btinternet.com
+ *      Ralph Irving 2015-2017, ralph_irving@hotmail.com
+ *  
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Additions (c) Paul Hermann, 2015-2017 under the same license terms
+ *   -Control of Raspberry pi GPIO for amplifier power
+ *   -Launch script on power status change from LMS
+ */
+
+#include "squeezelite.h"
+
+#include <signal.h>
+
+#define TITLE "Squeezelite " VERSION ", Copyright 2012-2015 Adrian Smith, 2015-2019 Ralph Irving."
+
+#define CODECS_BASE "flac,pcm,mp3,ogg"
+#if NO_FAAD
+#define CODECS_AAC  ""
+#else
+#define CODECS_AAC  ",aac"
+#endif
+#if FFMPEG
+#define CODECS_FF   ",wma,alac"
+#else
+#define CODECS_FF   ""
+#endif
+#if DSD
+#define CODECS_DSD  ",dsd"
+#else
+#define CODECS_DSD  ""
+#endif
+#define CODECS_MP3  " (mad,mpg for specific mp3 codec)"
+
+#define CODECS CODECS_BASE CODECS_AAC CODECS_FF CODECS_DSD CODECS_MP3
+
+static void usage(const char *argv0) {
+	printf(TITLE " See -t for license terms\n"
+		   "Usage: %s [options]\n"
+		   "  -s <server>[:<port>]\tConnect to specified server, otherwise uses autodiscovery to find server\n"
+		   "  -o <output device>\tSpecify output device, default \"default\", - = output to stdout\n"
+		   "  -l \t\t\tList output devices\n"
+#if ALSA
+		   "  -a <b>:<p>:<f>:<m>\tSpecify ALSA params to open output device, b = buffer time in ms or size in bytes, p = period count or size in bytes, f sample format (16|24|24_3|32), m = use mmap (0|1)\n"
+#endif
+#if PORTAUDIO
+#if PA18API
+		   "  -a <frames>:<buffers>\tSpecify output target 4 byte frames per buffer, number of buffers\n"
+#elif OSX && !defined(OSXPPC)
+		   "  -a <l>:<r>\t\tSpecify Portaudio params to open output device, l = target latency in ms, r = allow OSX to resample (0|1)\n"
+#elif WIN
+		   "  -a <l>:<e>\t\tSpecify Portaudio params to open output device, l = target latency in ms, e = use exclusive mode for WASAPI (0|1)\n"
+#else
+		   "  -a <l>\t\tSpecify Portaudio params to open output device, l = target latency in ms\n"
+#endif
+#endif
+		   "  -a <f>\t\tSpecify sample format (16|24|32) of output file when using -o - to output samples to stdout (interleaved little endian only)\n"
+		   "  -b <stream>:<output>\tSpecify internal Stream and Output buffer sizes in Kbytes\n"
+		   "  -c <codec1>,<codec2>\tRestrict codecs to those specified, otherwise load all available codecs; known codecs: " CODECS "\n"
+		   "  \t\t\tCodecs reported to LMS in order listed, allowing codec priority refinement.\n"
+		   "  -C <timeout>\t\tClose output device when idle after timeout seconds, default is to keep it open while player is 'on'\n"
+#if !IR
+		   "  -d <log>=<level>\tSet logging level, logs: all|slimproto|stream|decode|output, level: info|debug|sdebug\n"
+#else
+		   "  -d <log>=<level>\tSet logging level, logs: all|slimproto|stream|decode|output|ir, level: info|debug|sdebug\n"
+#endif
+#if defined(GPIO) && defined(RPI)
+		   "  -G <Rpi GPIO#>:<H/L>\tSpecify the BCM GPIO# to use for Amp Power Relay and if the output should be Active High or Low\n"
+#endif
+		   "  -e <codec1>,<codec2>\tExplicitly exclude native support of one or more codecs; known codecs: " CODECS "\n"
+		   "  -f <logfile>\t\tWrite debug to logfile\n"
+#if IR
+		   "  -i [<filename>]\tEnable lirc remote control support (lirc config file ~/.lircrc used if filename not specified)\n"
+#endif
+		   "  -m <mac addr>\t\tSet mac address, format: ab:cd:ef:12:34:56\n"
+		   "  -M <modelname>\tSet the squeezelite player model name sent to the server (default: " MODEL_NAME_STRING ")\n"
+		   "  -n <name>\t\tSet the player name\n"
+		   "  -N <filename>\t\tStore player name in filename to allow server defined name changes to be shared between servers (not supported with -n)\n"
+		   "  -W\t\t\tRead wave and aiff format from header, ignore server parameters\n"
+#if ALSA
+		   "  -p <priority>\t\tSet real time priority of output thread (1-99)\n"
+#endif
+#if LINUX || FREEBSD || SUN
+		   "  -P <filename>\t\tStore the process id (PID) in filename\n"
+#endif
+		   "  -r <rates>[:<delay>]\tSample rates supported, allows output to be off when squeezelite is started; rates = <maxrate>|<minrate>-<maxrate>|<rate1>,<rate2>,<rate3>; delay = optional delay switching rates in ms\n"
+#if GPIO
+			"  -S <Power Script>\tAbsolute path to script to launch on power commands from LMS\n"
+#endif
+#if RESAMPLE
+		   "  -R -u [params]\tResample, params = <recipe>:<flags>:<attenuation>:<precision>:<passband_end>:<stopband_start>:<phase_response>,\n" 
+		   "  \t\t\t recipe = (v|h|m|l|q)(L|I|M)(s) [E|X], E = exception - resample only if native rate not supported, X = async - resample to max rate for device, otherwise to max sync rate\n"
+		   "  \t\t\t flags = num in hex,\n"
+		   "  \t\t\t attenuation = attenuation in dB to apply (default is -1db if not explicitly set),\n"
+		   "  \t\t\t precision = number of bits precision (NB. HQ = 20. VHQ = 28),\n"
+		   "  \t\t\t passband_end = number in percent (0dB pt. bandwidth to preserve. nyquist = 100%%),\n"
+		   "  \t\t\t stopband_start = number in percent (Aliasing/imaging control. > passband_end),\n"
+		   "  \t\t\t phase_response = 0-100 (0 = minimum / 50 = linear / 100 = maximum)\n"
+#endif
+#if DSD
+#if ALSA
+		   "  -D [delay][:format]\tOutput device supports DSD, delay = optional delay switching between PCM and DSD in ms\n"
+		   "  \t\t\t format = dop (default if not specified), u8, u16le, u16be, u32le or u32be.\n"
+#else
+		   "  -D [delay]\t\tOutput device supports DSD over PCM (DoP), delay = optional delay switching between PCM and DoP in ms\n"
+#endif
+#endif
+#if VISEXPORT
+		   "  -v \t\t\tVisualiser support\n"
+#endif
+# if ALSA
+		   "  -O <mixer device>\tSpecify mixer device, defaults to 'output device'\n"
+		   "  -L \t\t\tList volume controls for output device\n"
+		   "  -U <control>\t\tUnmute ALSA control and set to full volume (not supported with -V)\n"
+		   "  -V <control>\t\tUse ALSA control for volume adjustment, otherwise use software volume adjustment\n"
+		   "  -X \t\t\tUse linear volume adjustments instead of in terms of dB (only for hardware volume control)\n"
+#endif
+#if LINUX || FREEBSD || SUN
+		   "  -z \t\t\tDaemonize\n"
+#endif
+#if RESAMPLE
+		   "  -Z <rate>\t\tReport rate to server in helo as the maximum sample rate we can support\n"
+#endif
+		   "  -t \t\t\tLicense terms\n"
+		   "  -? \t\t\tDisplay this help text\n"
+		   "\n"
+		   "Build options:"
+#if SUN
+		   " SOLARIS"
+#elif LINUX
+		   " LINUX"
+#endif
+#if WIN
+		   " WIN"
+#endif
+#if OSX
+		   " OSX"
+#endif
+#if OSXPPC
+		   "PPC"
+#endif
+#if FREEBSD
+		   " FREEBSD"
+#endif
+#if ALSA
+		   " ALSA"
+#endif
+#if PORTAUDIO
+		   " PORTAUDIO"
+#if PA18API
+		   "18"
+#endif
+#endif
+#if EVENTFD
+		   " EVENTFD"
+#endif
+#if SELFPIPE
+		   " SELFPIPE"
+#endif		   
+#if LOOPBACK
+		   " LOOPBACK"
+#endif
+#if WINEVENT
+		   " WINEVENT"
+#endif
+#if RESAMPLE_MP
+		   " RESAMPLE_MP"
+#else
+#if RESAMPLE
+		   " RESAMPLE"
+#endif
+#endif
+#if FFMPEG
+		   " FFMPEG"
+#endif
+#if NO_FAAD
+		   " NO_FAAD"
+#endif
+#if VISEXPORT
+		   " VISEXPORT"
+#endif
+#if IR
+		   " IR"
+#endif
+#if GPIO
+		   " GPIO"
+#endif
+#if RPI
+		   " RPI"
+#endif
+#if DSD
+		   " DSD"
+#endif
+#if USE_SSL
+                   " SSL"
+#endif
+#if LINKALL
+		   " LINKALL"
+#endif
+#if STATUSHACK
+		   " STATUSHACK"
+#endif
+		   "\n\n",
+		   argv0);
+}
+
+static void license(void) {
+	printf(TITLE "\n\n"
+		   "This program is free software: you can redistribute it and/or modify\n"
+		   "it under the terms of the GNU General Public License as published by\n"
+		   "the Free Software Foundation, either version 3 of the License, or\n"
+		   "(at your option) any later version.\n\n"
+		   "This program is distributed in the hope that it will be useful,\n"
+		   "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+		   "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
+		   "GNU General Public License for more details.\n\n"
+		   "You should have received a copy of the GNU General Public License\n"
+		   "along with this program.  If not, see <http://www.gnu.org/licenses/>.\n"
+#if DSD		   
+		   "\nContains dsd2pcm library Copyright 2009, 2011 Sebastian Gesemann which\n"
+		   "is subject to its own license.\n"
+		   "\nContains the Daphile Project full dsd patch Copyright 2013-2017 Daphile,\n"
+		   "which is subject to its own license.\n"
+#endif
+		   "\nOption to allow server side upsampling for PCM streams (-W) from\n"
+		   "squeezelite-R2 (c) Marco Curti 2015, marcoc1712@gmail.com.\n"
+#if GPIO
+		   "\nAdditions (c) Paul Hermann, 2015, 2017 under the same license terms\n"
+		   "- Launch a script on power status change\n"
+		   "- Control of Raspberry pi GPIO for amplifier power\n"
+#endif
+#if RPI
+		   "\nContains wiringpi GPIO Interface library Copyright (c) 2012-2017\n"
+		   "Gordon Henderson, which is subject to its own license.\n"
+#endif
+#if FFMPEG
+		   "\nThis software uses libraries from the FFmpeg project under\n"
+		   "the LGPLv2.1 and its source can be downloaded from\n"
+		   "<https://sourceforge.net/projects/lmsclients/files/source/>\n"
+#endif
+		   "\n"
+		   );
+}
+
+static void sighandler(int signum) {
+	slimproto_stop();
+
+	// remove ourselves in case above does not work, second SIGINT will cause non gracefull shutdown
+	signal(signum, SIG_DFL);
+}
+
+int main(int argc, char **argv) {
+	char *server = NULL;
+	char *output_device = "default";
+	char *include_codecs = NULL;
+	char *exclude_codecs = "";
+	char *name = NULL;
+	char *namefile = NULL;
+	char *modelname = NULL;
+	extern bool pcm_check_header;
+	extern bool user_rates;
+	char *logfile = NULL;
+	u8_t mac[6];
+	unsigned stream_buf_size = STREAMBUF_SIZE;
+	unsigned output_buf_size = 0; // set later
+	unsigned rates[MAX_SUPPORTED_SAMPLERATES] = { 0 };
+	unsigned rate_delay = 0;
+	char *resample = NULL;
+	char *output_params = NULL;
+	unsigned idle = 0;
+#if LINUX || FREEBSD || SUN
+	bool daemonize = false;
+	char *pidfile = NULL;
+	FILE *pidfp = NULL;
+#endif
+#if ALSA
+	unsigned rt_priority = OUTPUT_RT_PRIORITY;
+	char *mixer_device = output_device;
+	char *output_mixer = NULL;
+	bool output_mixer_unmute = false;
+	bool linear_volume = false;
+#endif
+#if DSD
+	unsigned dsd_delay = 0;
+	dsd_format dsd_outfmt = PCM;
+#endif
+#if VISEXPORT
+	bool visexport = false;
+#endif
+#if IR
+	char *lircrc = NULL;
+#endif
+	
+	log_level log_output = lWARN;
+	log_level log_stream = lWARN;
+	log_level log_decode = lWARN;
+	log_level log_slimproto = lWARN;
+#if IR
+	log_level log_ir     = lWARN;
+#endif
+
+	int maxSampleRate = 0;
+
+	char *optarg = NULL;
+	int optind = 1;
+	int i;
+
+#define MAXCMDLINE 512
+	char cmdline[MAXCMDLINE] = "";
+
+	get_mac(mac);
+
+	for (i = 0; i < argc && (strlen(argv[i]) + strlen(cmdline) + 2 < MAXCMDLINE); i++) {
+		strcat(cmdline, argv[i]);
+		strcat(cmdline, " ");
+	}
+
+	while (optind < argc && strlen(argv[optind]) >= 2 && argv[optind][0] == '-') {
+		char *opt = argv[optind] + 1;
+		if (strstr("oabcCdefmMnNpPrs"
+#if ALSA
+				   "UVO"
+#endif
+/* 
+ * only allow '-Z <rate>' override of maxSampleRate 
+ * reported by client if built with the capability to resample!
+ */
+#if RESAMPLE
+				   "Z"
+#endif
+				   , opt) && optind < argc - 1) {
+			optarg = argv[optind + 1];
+			optind += 2;
+		} else if (strstr("ltz?W"
+#if ALSA
+						  "LX"
+#endif
+#if RESAMPLE
+						  "uR"
+#endif
+#if DSD
+						  "D"
+#endif
+#if VISEXPORT
+						  "v"
+#endif
+#if IR
+						  "i"
+#endif
+#if defined(GPIO) && defined(RPI)
+						  "G"
+#endif
+#if GPIO
+						  "S"
+#endif
+
+						  , opt)) {
+			optarg = NULL;
+			optind += 1;
+		} else {
+			fprintf(stderr, "\nOption error: -%s\n\n", opt);
+			usage(argv[0]);
+			exit(1);
+		}
+
+		switch (opt[0]) {
+		case 'o':
+			output_device = optarg;
+#if ALSA
+			mixer_device = optarg;
+#endif
+			break;
+		case 'a':
+			output_params = optarg;
+			break;
+		case 'b': 
+			{
+				char *s = next_param(optarg, ':');
+				char *o = next_param(NULL, ':');
+				if (s) stream_buf_size = atoi(s) * 1024;
+				if (o) output_buf_size = atoi(o) * 1024;
+			}
+			break;
+		case 'c':
+			include_codecs = optarg;
+			break;
+		case 'C':
+			if (atoi(optarg) > 0) {
+				idle = atoi(optarg) * 1000;
+			}
+			break;
+		case 'e':
+			exclude_codecs = optarg;
+			break;
+		case 'd':
+			{
+				char *l = strtok(optarg, "=");
+				char *v = strtok(NULL, "=");
+				log_level new = lWARN;
+				if (l && v) {
+					if (!strcmp(v, "info"))   new = lINFO;
+					if (!strcmp(v, "debug"))  new = lDEBUG;
+					if (!strcmp(v, "sdebug")) new = lSDEBUG;
+					if (!strcmp(l, "all") || !strcmp(l, "slimproto")) log_slimproto = new;
+					if (!strcmp(l, "all") || !strcmp(l, "stream"))    log_stream = new;
+					if (!strcmp(l, "all") || !strcmp(l, "decode"))    log_decode = new;
+					if (!strcmp(l, "all") || !strcmp(l, "output"))    log_output = new;
+#if IR
+					if (!strcmp(l, "all") || !strcmp(l, "ir"))        log_ir     = new;
+#endif
+				} else {
+					fprintf(stderr, "\nDebug settings error: -d %s\n\n", optarg);
+					usage(argv[0]);
+					exit(1);
+				}
+			}
+			break;
+		case 'f':
+			logfile = optarg;
+			break;
+		case 'm':
+			{
+				int byte = 0;
+				char *tmp;
+				if (!strncmp(optarg, "00:04:20", 8)) {
+					LOG_ERROR("ignoring mac address from hardware player range 00:04:20:**:**:**");
+				} else {
+					char *t = strtok(optarg, ":");
+					while (t && byte < 6) {
+						mac[byte++] = (u8_t)strtoul(t, &tmp, 16);
+						t = strtok(NULL, ":");
+					}
+				}
+			}
+			break;
+		case 'M':
+			modelname = optarg;
+			break;
+		case 'r':
+			{ 
+				char *rstr = next_param(optarg, ':');
+				char *dstr = next_param(NULL, ':');
+				if (rstr && strstr(rstr, ",")) {
+					// parse sample rates and sort them
+					char *r = next_param(rstr, ',');
+					unsigned tmp[MAX_SUPPORTED_SAMPLERATES] = { 0 };
+					int i, j;
+					int last = 999999;
+					for (i = 0; r && i < MAX_SUPPORTED_SAMPLERATES; ++i) { 
+						tmp[i] = atoi(r);
+						r = next_param(NULL, ',');
+					}
+					for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) {
+						int largest = 0;
+						for (j = 0; j < MAX_SUPPORTED_SAMPLERATES; ++j) {
+							if (tmp[j] > largest && tmp[j] < last) {
+								largest = tmp[j];
+							}
+						}
+						rates[i] = last = largest;
+					}
+				} else if (rstr) {
+					// optstr is <min>-<max> or <max>, extract rates from test rates within this range
+					unsigned ref[] TEST_RATES;
+					char *str1 = next_param(rstr, '-');
+					char *str2 = next_param(NULL, '-');
+					unsigned max = str2 ? atoi(str2) : (str1 ? atoi(str1) : ref[0]);
+					unsigned min = str1 && str2 ? atoi(str1) : 0;
+					unsigned tmp;
+					int i, j;
+					if (max < min) { tmp = max; max = min; min = tmp; }
+					rates[0] = max;
+					for (i = 0, j = 1; i < MAX_SUPPORTED_SAMPLERATES; ++i) {
+						if (ref[i] < rates[j-1] && ref[i] >= min) {
+							rates[j++] = ref[i];
+						}
+					}
+				}
+				if (dstr) {
+					rate_delay = atoi(dstr);
+				}
+				if (rates[0]) {
+					user_rates = true;
+				}
+			}
+			break;
+		case 's':
+			server = optarg;
+			break;
+		case 'n':
+			name = optarg;
+			break;
+		case 'N':
+			namefile = optarg;
+			break;
+		case 'W':
+			pcm_check_header = true;
+			break;
+#if ALSA
+		case 'p':
+			rt_priority = atoi(optarg);
+			if (rt_priority > 99 || rt_priority < 1) {
+				fprintf(stderr, "\nError: invalid priority: %s\n\n", optarg);
+				usage(argv[0]);
+				exit(1);
+			}
+			break;
+#endif
+#if LINUX || FREEBSD || SUN
+		case 'P':
+			pidfile = optarg;
+			break;
+#endif
+#ifndef DACAUDIO
+		case 'l':
+			list_devices();
+			exit(0);
+			break;
+#endif			
+#if RESAMPLE
+		case 'u':
+		case 'R':
+			if (optind < argc && argv[optind] && argv[optind][0] != '-') {
+				resample = argv[optind++];
+			} else {
+				resample = "";
+			}
+			break;
+		case 'Z':
+			maxSampleRate = atoi(optarg);
+			break;
+#endif
+#if DSD
+		case 'D':
+			dsd_outfmt = DOP;
+			if (optind < argc && argv[optind] && argv[optind][0] != '-') {
+				char *dstr = next_param(argv[optind++], ':');
+				char *fstr = next_param(NULL, ':');
+				dsd_delay = dstr ? atoi(dstr) : 0;
+				if (fstr) {
+					if (!strcmp(fstr, "dop")) dsd_outfmt = DOP; 
+					if (!strcmp(fstr, "u8")) dsd_outfmt = DSD_U8; 
+					if (!strcmp(fstr, "u16le")) dsd_outfmt = DSD_U16_LE; 
+					if (!strcmp(fstr, "u32le")) dsd_outfmt = DSD_U32_LE; 
+					if (!strcmp(fstr, "u16be")) dsd_outfmt = DSD_U16_BE; 
+					if (!strcmp(fstr, "u32be")) dsd_outfmt = DSD_U32_BE;
+					if (!strcmp(fstr, "dop24")) dsd_outfmt = DOP_S24_LE;
+					if (!strcmp(fstr, "dop24_3")) dsd_outfmt = DOP_S24_3LE;
+				}
+			}
+			break;
+#endif
+#if VISEXPORT
+		case 'v':
+			visexport = true;
+			break;
+#endif
+#if ALSA
+		case 'O':
+			mixer_device = optarg;
+			break;
+		case 'L':
+			list_mixers(mixer_device);
+			exit(0);
+			break;
+		case 'X':
+			linear_volume = true;
+			break;
+		case 'U':
+			output_mixer_unmute = true;
+		case 'V':
+			if (output_mixer) {
+				fprintf(stderr, "-U and -V option should not be used at same time\n");
+				exit(1);
+			}
+			output_mixer = optarg;
+			break;
+#endif
+#if IR
+		case 'i':
+			if (optind < argc && argv[optind] && argv[optind][0] != '-') {
+				lircrc = argv[optind++];
+			} else {
+				lircrc = "~/.lircrc"; // liblirc_client will expand ~/
+			}
+			break;
+#endif
+#if defined(GPIO) && defined(RPI)
+		case 'G':
+			if (power_script != NULL){
+				fprintf(stderr, "-G and -S options cannot be used together \n\n" );
+				usage(argv[0]);
+				exit(1);
+			}
+			if (optind < argc && argv[optind] && argv[optind][0] != '-') {
+				char *gp = next_param(argv[optind++], ':');
+				char *go = next_param (NULL, ':');
+				gpio_pin = atoi(gp);
+				if (go != NULL){
+					if ((strcmp(go, "H")==0)|(strcmp(go, "h")==0)){
+						gpio_active_low=false;
+					}else if((strcmp(go, "L")==0)|(strcmp(go, "l")==0)){
+						gpio_active_low=true;
+					}else{
+						fprintf(stderr,"Must set output to be active High or Low i.e. -G18:H or -G18:L\n");
+						usage(argv[0]);
+						exit(1);
+					}
+				}else{
+					fprintf(stderr,"-G Option Error\n");
+					usage(argv[0]);
+					exit(1);
+				}
+				gpio_active = true;
+				relay(0);
+
+			} else {
+				fprintf(stderr, "Error in GPIO Pin assignment.\n");
+				usage(argv[0]);
+				exit(1);
+			}
+			break;
+#endif
+#if GPIO
+		case 'S':
+			if (gpio_active){
+				fprintf(stderr, "-G and -S options cannot be used together \n\n" );
+				usage(argv[0]);
+				exit(1);
+			}
+			if (optind < argc && argv[optind] && argv[optind][0] != '-') {
+				power_script = argv[optind++];
+				if( access( power_script, R_OK|X_OK ) == -1 ) {
+				    // file doesn't exist
+					fprintf(stderr, "Script %s, not found\n\n", argv[optind-1]);
+					usage(argv[0]);
+					exit(1);
+				}
+			} else {
+				fprintf(stderr, "No Script Name Given.\n\n");
+				usage(argv[0]);
+				exit(1);
+			}
+			relay_script(0);
+			break;
+#endif
+#if LINUX || FREEBSD || SUN
+		case 'z':
+			daemonize = true;
+#if SUN
+			init_daemonize();
+#endif /* SUN */
+			break;
+#endif
+		case 't':
+			license();
+			exit(0);
+		case '?':
+			usage(argv[0]);
+			exit(0);
+		default:
+			fprintf(stderr, "Arg error: %s\n", argv[optind]);
+			break;
+		}
+	}
+
+	// warn if command line includes something which isn't parsed
+	if (optind < argc) {
+		fprintf(stderr, "\nError: command line argument error\n\n");
+		usage(argv[0]);
+		exit(1);
+	}
+
+	signal(SIGINT, sighandler);
+	signal(SIGTERM, sighandler);
+#if defined(SIGQUIT)
+	signal(SIGQUIT, sighandler);
+#endif
+#if defined(SIGHUP)
+	signal(SIGHUP, sighandler);
+#endif
+
+#if USE_SSL && !LINKALL
+	ssl_loaded = load_ssl_symbols();
+#endif	
+
+	// set the output buffer size if not specified on the command line, take account of resampling
+	if (!output_buf_size) {
+		output_buf_size = OUTPUTBUF_SIZE;
+		if (resample) {
+			unsigned scale = 8;
+			if (rates[0]) {
+				scale = rates[0] / 44100;
+				if (scale > 8) scale = 8;
+				if (scale < 1) scale = 1;
+			}
+			output_buf_size *= scale;
+		}
+	}
+
+	if (logfile) {
+		if (!freopen(logfile, "a", stderr)) {
+			fprintf(stderr, "error opening logfile %s: %s\n", logfile, strerror(errno));
+		} else {
+			if (log_output >= lINFO || log_stream >= lINFO || log_decode >= lINFO || log_slimproto >= lINFO) {
+				fprintf(stderr, "\n%s\n", cmdline);
+			}
+		}
+	}
+
+#if LINUX || FREEBSD || SUN
+	if (pidfile) {
+		if (!(pidfp = fopen(pidfile, "w")) ) {
+			fprintf(stderr, "Error opening pidfile %s: %s\n", pidfile, strerror(errno));
+			exit(1);
+		}
+		pidfile = realpath(pidfile, NULL); // daemonize will change cwd
+	}
+
+	if (daemonize) {
+		if (daemon(0, logfile ? 1 : 0)) {
+			fprintf(stderr, "error daemonizing: %s\n", strerror(errno));
+		}
+	}
+
+	if (pidfp) {
+		fprintf(pidfp, "%d\n", (int) getpid());
+		fclose(pidfp);
+	}
+#endif
+
+#if WIN
+	winsock_init();
+#endif
+
+	stream_init(log_stream, stream_buf_size);
+
+#if DACAUDIO
+	output_init_dac(log_output, output_buf_size, output_params, rates, rate_delay, idle);
+#else
+	if (!strcmp(output_device, "-")) {
+		output_init_stdout(log_output, output_buf_size, output_params, rates, rate_delay);
+	} else {
+#if ALSA
+		output_init_alsa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, rt_priority, idle, mixer_device, output_mixer,
+						 output_mixer_unmute, linear_volume);
+#endif
+#if PORTAUDIO
+		output_init_pa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, idle);
+#endif
+	}
+#endif	
+
+#if DSD
+	dsd_init(dsd_outfmt, dsd_delay);
+#endif
+
+#if VISEXPORT
+	if (visexport) {
+		output_vis_init(log_output, mac);
+	}
+#endif
+
+	decode_init(log_decode, include_codecs, exclude_codecs);
+
+#if RESAMPLE
+	if (resample) {
+		process_init(resample);
+	}
+#endif
+
+#if IR
+	if (lircrc) {
+		ir_init(log_ir, lircrc);
+	}
+#endif
+
+	if (name && namefile) {
+		fprintf(stderr, "-n and -N option should not be used at same time\n");
+		exit(1);
+	}
+
+	slimproto(log_slimproto, server, mac, name, namefile, modelname, maxSampleRate);
+
+	decode_close();
+	stream_close();
+
+#if DACAUDIO
+	output_close_dac();	
+#else
+	if (!strcmp(output_device, "-")) {
+		output_close_stdout();
+	} else {
+#if ALSA
+		output_close_alsa();
+#endif
+#if PORTAUDIO
+		output_close_pa();
+#endif
+	}
+#endif	
+
+#if IR
+	ir_close();
+#endif
+
+#if WIN
+	winsock_close();
+#endif
+
+#if LINUX || FREEBSD || SUN
+	if (pidfile) {
+		unlink(pidfile);
+		free(pidfile);
+	}
+#endif
+
+#if USE_SSL && !LINKALL
+	free_ssl_symbols();
+#endif	
+
+	exit(0);
+}

+ 450 - 0
output.c

@@ -0,0 +1,450 @@
+/* 
+ *  Squeezelite - lightweight headless squeezebox emulator
+ *
+ *  (c) Adrian Smith 2012-2015, triode1@btinternet.com
+ *      Ralph Irving 2015-2017, ralph_irving@hotmail.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+// Common output function
+
+#include "squeezelite.h"
+
+static log_level loglevel;
+
+struct outputstate output;
+
+static struct buffer buf;
+
+struct buffer *outputbuf = &buf;
+
+u8_t *silencebuf;
+#if DSD
+u8_t *silencebuf_dsd;
+#endif
+
+bool user_rates = false;
+
+#define LOCK   mutex_lock(outputbuf->mutex)
+#define UNLOCK mutex_unlock(outputbuf->mutex)
+
+// functions starting _* are called with mutex locked
+
+frames_t _output_frames(frames_t avail) {
+
+	frames_t frames, size;
+	bool silence;
+	
+	s32_t cross_gain_in = 0, cross_gain_out = 0; s32_t *cross_ptr = NULL;
+	
+	s32_t gainL = output.current_replay_gain ? gain(output.gainL, output.current_replay_gain) : output.gainL;
+	s32_t gainR = output.current_replay_gain ? gain(output.gainR, output.current_replay_gain) : output.gainR;
+
+	if (output.invert) { gainL = -gainL; gainR = -gainR; }
+
+	frames = _buf_used(outputbuf) / BYTES_PER_FRAME;
+	silence = false;
+
+	// start when threshold met
+	if (output.state == OUTPUT_BUFFER && frames > output.threshold * output.next_sample_rate / 100 && frames > output.start_frames) {
+		output.state = OUTPUT_RUNNING;
+		LOG_INFO("start buffer frames: %u", frames);
+		wake_controller();
+	}
+	
+	// skip ahead - consume outputbuf but play nothing
+	if (output.state == OUTPUT_SKIP_FRAMES) {
+		if (frames > 0) {
+			frames_t skip = min(frames, output.skip_frames);
+			LOG_INFO("skip %u of %u frames", skip, output.skip_frames);
+			frames -= skip;
+			output.frames_played += skip;
+			while (skip > 0) {
+				frames_t cont_frames = min(skip, _buf_cont_read(outputbuf) / BYTES_PER_FRAME);
+				skip -= cont_frames;
+				_buf_inc_readp(outputbuf, cont_frames * BYTES_PER_FRAME);
+			}
+		}
+		output.state = OUTPUT_RUNNING;
+	}
+	
+	// pause frames - play silence for required frames
+	if (output.state == OUTPUT_PAUSE_FRAMES) {
+		LOG_INFO("pause %u frames", output.pause_frames);
+		if (output.pause_frames == 0) {
+			output.state = OUTPUT_RUNNING;
+		} else {
+			silence = true;
+			frames = min(avail, output.pause_frames);
+			frames = min(frames, MAX_SILENCE_FRAMES);
+			output.pause_frames -= frames;
+		}
+	}
+	
+	// start at - play silence until jiffies reached
+	if (output.state == OUTPUT_START_AT) {
+		u32_t now = gettime_ms();
+		if (now >= output.start_at || output.start_at > now + 10000) {
+			output.state = OUTPUT_RUNNING;
+		} else {
+			u32_t delta_frames = (output.start_at - now) * output.current_sample_rate / 1000;
+			silence = true;
+			frames = min(avail, delta_frames);
+			frames = min(frames, MAX_SILENCE_FRAMES);
+		}
+	}
+	
+	// play silence if buffering or no frames
+	if (output.state <= OUTPUT_BUFFER || frames == 0) {
+		silence = true;
+		frames = min(avail, MAX_SILENCE_FRAMES);
+	}
+
+	LOG_SDEBUG("avail: %d frames: %d silence: %d", avail, frames, silence);
+	frames = min(frames, avail);
+	size = frames;
+	
+	while (size > 0) {
+		frames_t out_frames;
+		frames_t cont_frames = _buf_cont_read(outputbuf) / BYTES_PER_FRAME;
+		int wrote;
+		
+		if (output.track_start && !silence) {
+			if (output.track_start == outputbuf->readp) {
+				unsigned delay = 0;
+				if (output.current_sample_rate != output.next_sample_rate) {
+					delay = output.rate_delay;
+				}
+				IF_DSD(
+				   if (output.outfmt != output.next_fmt) {
+					   delay = output.dsd_delay;
+				   }
+				)
+				frames -= size;
+				// add silence delay in two halves, before and after track start on rate or pcm-dop change
+				if (delay) {
+					output.state = OUTPUT_PAUSE_FRAMES;
+					if (!output.delay_active) {
+						output.pause_frames = output.current_sample_rate * delay / 2000;
+						output.delay_active = true;  // first delay - don't process track start
+						break;
+					} else {
+						output.pause_frames = output.next_sample_rate * delay / 2000;
+						output.delay_active = false; // second delay - process track start
+					}
+				}
+				LOG_INFO("track start sample rate: %u replay_gain: %u", output.next_sample_rate, output.next_replay_gain);
+				output.frames_played = 0;
+				output.track_started = true;
+				output.track_start_time = gettime_ms();
+				output.current_sample_rate = output.next_sample_rate;
+				IF_DSD(
+				   output.outfmt = output.next_fmt;
+				)
+				if (output.fade == FADE_INACTIVE || output.fade_mode != FADE_CROSSFADE) {
+					output.current_replay_gain = output.next_replay_gain;
+				}
+				output.track_start = NULL;
+				break;
+			} else if (output.track_start > outputbuf->readp) {
+				// reduce cont_frames so we find the next track start at beginning of next chunk
+				cont_frames = min(cont_frames, (output.track_start - outputbuf->readp) / BYTES_PER_FRAME);
+			}
+		}
+
+		IF_DSD(
+			if (output.outfmt != PCM) {
+				gainL = gainR = FIXED_ONE;
+			}
+		)
+		
+		if (output.fade && !silence) {
+			if (output.fade == FADE_DUE) {
+				if (output.fade_start == outputbuf->readp) {
+					LOG_INFO("fade start reached");
+					output.fade = FADE_ACTIVE;
+				} else if (output.fade_start > outputbuf->readp) {
+					cont_frames = min(cont_frames, (output.fade_start - outputbuf->readp) / BYTES_PER_FRAME);
+				}
+			}
+			if (output.fade == FADE_ACTIVE) {
+				// find position within fade
+				frames_t cur_f = outputbuf->readp >= output.fade_start ? (outputbuf->readp - output.fade_start) / BYTES_PER_FRAME : 
+					(outputbuf->readp + outputbuf->size - output.fade_start) / BYTES_PER_FRAME;
+				frames_t dur_f = output.fade_end >= output.fade_start ? (output.fade_end - output.fade_start) / BYTES_PER_FRAME :
+					(output.fade_end + outputbuf->size - output.fade_start) / BYTES_PER_FRAME;
+				if (cur_f >= dur_f) {
+					if (output.fade_mode == FADE_INOUT && output.fade_dir == FADE_DOWN) {
+						LOG_INFO("fade down complete, starting fade up");
+						output.fade_dir = FADE_UP;
+						output.fade_start = outputbuf->readp;
+						output.fade_end = outputbuf->readp + dur_f * BYTES_PER_FRAME;
+						if (output.fade_end >= outputbuf->wrap) {
+							output.fade_end -= outputbuf->size;
+						}
+						cur_f = 0;
+					} else if (output.fade_mode == FADE_CROSSFADE) {
+						LOG_INFO("crossfade complete");
+						if (_buf_used(outputbuf) >= dur_f * BYTES_PER_FRAME) {
+							_buf_inc_readp(outputbuf, dur_f * BYTES_PER_FRAME);
+							LOG_INFO("skipped crossfaded start");
+						} else {
+							LOG_WARN("unable to skip crossfaded start");
+						}
+						output.fade = FADE_INACTIVE;
+						output.current_replay_gain = output.next_replay_gain;
+					} else {
+						LOG_INFO("fade complete");
+						output.fade = FADE_INACTIVE;
+					}
+				}
+				// if fade in progress set fade gain, ensure cont_frames reduced so we get to end of fade at start of chunk
+				if (output.fade) {
+					if (output.fade_end > outputbuf->readp) {
+						cont_frames = min(cont_frames, (output.fade_end - outputbuf->readp) / BYTES_PER_FRAME);
+					}
+					if (output.fade_dir == FADE_UP || output.fade_dir == FADE_DOWN) {
+						// fade in, in-out, out handled via altering standard gain
+						s32_t fade_gain;
+						if (output.fade_dir == FADE_DOWN) {
+							cur_f = dur_f - cur_f;
+						}
+						fade_gain = to_gain((float)cur_f / (float)dur_f);
+						gainL = gain(gainL, fade_gain);
+						gainR = gain(gainR, fade_gain);
+						if (output.invert) { gainL = -gainL; gainR = -gainR; }
+					}
+					if (output.fade_dir == FADE_CROSS) {
+						// cross fade requires special treatment - performed later based on these values
+						// support different replay gain for old and new track by retaining old value until crossfade completes
+						if (_buf_used(outputbuf) / BYTES_PER_FRAME > dur_f + size) { 
+							cross_gain_in  = to_gain((float)cur_f / (float)dur_f);
+							cross_gain_out = FIXED_ONE - cross_gain_in;
+							if (output.current_replay_gain) {
+								cross_gain_out = gain(cross_gain_out, output.current_replay_gain);
+							}
+							if (output.next_replay_gain) {
+								cross_gain_in = gain(cross_gain_in, output.next_replay_gain);
+							}
+							gainL = output.gainL;
+							gainR = output.gainR;
+							if (output.invert) { gainL = -gainL; gainR = -gainR; }
+							cross_ptr = (s32_t *)(output.fade_end + cur_f * BYTES_PER_FRAME);
+						} else {
+							LOG_INFO("unable to continue crossfade - too few samples");
+							output.fade = FADE_INACTIVE;
+						}
+					}
+				}
+			}
+		}
+		
+		out_frames = !silence ? min(size, cont_frames) : size;
+
+		wrote = output.write_cb(out_frames, silence, gainL, gainR, cross_gain_in, cross_gain_out, &cross_ptr);
+
+		if (wrote <= 0) {
+			frames -= size;
+			break;
+		} else {
+			out_frames = (frames_t)wrote;
+		}
+
+		size -= out_frames;
+
+		_vis_export(outputbuf, &output, out_frames, silence);
+
+		if (!silence) {
+			_buf_inc_readp(outputbuf, out_frames * BYTES_PER_FRAME);
+			output.frames_played += out_frames;
+		}
+	}
+			
+	LOG_SDEBUG("wrote %u frames", frames);
+
+	return frames;
+}
+
+void _checkfade(bool start) {
+	frames_t bytes;
+
+	LOG_INFO("fade mode: %u duration: %u %s", output.fade_mode, output.fade_secs, start ? "track-start" : "track-end");
+
+	bytes = output.next_sample_rate * BYTES_PER_FRAME * output.fade_secs;
+	if (output.fade_mode == FADE_INOUT) {
+		/* align on a frame boundary */
+		bytes = ((bytes / 2) / BYTES_PER_FRAME) * BYTES_PER_FRAME;
+	}
+
+	if (start && (output.fade_mode == FADE_IN || (output.fade_mode == FADE_INOUT && _buf_used(outputbuf) == 0))) {
+		bytes = min(bytes, outputbuf->size - BYTES_PER_FRAME); // shorter than full buffer otherwise start and end align
+		LOG_INFO("fade IN: %u frames", bytes / BYTES_PER_FRAME);
+		output.fade = FADE_DUE;
+		output.fade_dir = FADE_UP;
+		output.fade_start = outputbuf->writep;
+		output.fade_end = output.fade_start + bytes;
+		if (output.fade_end >= outputbuf->wrap) {
+			output.fade_end -= outputbuf->size;
+		}
+	}
+
+	if (!start && (output.fade_mode == FADE_OUT || output.fade_mode == FADE_INOUT)) {
+		bytes = min(_buf_used(outputbuf), bytes);
+		LOG_INFO("fade %s: %u frames", output.fade_mode == FADE_INOUT ? "IN-OUT" : "OUT", bytes / BYTES_PER_FRAME);
+		output.fade = FADE_DUE;
+		output.fade_dir = FADE_DOWN;
+		output.fade_start = outputbuf->writep - bytes;
+		if (output.fade_start < outputbuf->buf) {
+			output.fade_start += outputbuf->size;
+		}
+		output.fade_end = outputbuf->writep;
+	}
+
+	if (start && output.fade_mode == FADE_CROSSFADE) {
+		if (_buf_used(outputbuf) != 0) {
+			if (output.next_sample_rate != output.current_sample_rate) {
+				LOG_INFO("crossfade disabled as sample rates differ");
+				return;
+			}
+			bytes = min(bytes, _buf_used(outputbuf));               // max of current remaining samples from previous track
+			bytes = min(bytes, (frames_t)(outputbuf->size * 0.9));  // max of 90% of outputbuf as we consume additional buffer during crossfade
+			LOG_INFO("CROSSFADE: %u frames", bytes / BYTES_PER_FRAME);
+			output.fade = FADE_DUE;
+			output.fade_dir = FADE_CROSS;
+			output.fade_start = outputbuf->writep - bytes;
+			if (output.fade_start < outputbuf->buf) {
+				output.fade_start += outputbuf->size;
+			}
+			output.fade_end = outputbuf->writep;
+			output.track_start = output.fade_start;
+		} else if (outputbuf->size == OUTPUTBUF_SIZE && outputbuf->readp == outputbuf->buf) {
+			// if default setting used and nothing in buffer attempt to resize to provide full crossfade support
+			LOG_INFO("resize outputbuf for crossfade");
+			_buf_resize(outputbuf, OUTPUTBUF_SIZE_CROSSFADE);
+#if LINUX || FREEBSD
+			touch_memory(outputbuf->buf, outputbuf->size);
+#endif			
+		}
+	}
+}
+
+void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[], unsigned idle) {
+	unsigned i;
+
+	loglevel = level;
+
+	output_buf_size = output_buf_size - (output_buf_size % BYTES_PER_FRAME);
+	LOG_DEBUG("outputbuf size: %u", output_buf_size);
+
+	buf_init(outputbuf, output_buf_size);
+	if (!outputbuf->buf) {
+		LOG_ERROR("unable to malloc output buffer");
+		exit(0);
+	}
+
+	silencebuf = malloc(MAX_SILENCE_FRAMES * BYTES_PER_FRAME);
+	if (!silencebuf) {
+		LOG_ERROR("unable to malloc silence buffer");
+		exit(0);
+	}
+	memset(silencebuf, 0, MAX_SILENCE_FRAMES * BYTES_PER_FRAME);
+
+	IF_DSD(
+		silencebuf_dsd = malloc(MAX_SILENCE_FRAMES * BYTES_PER_FRAME);
+		if (!silencebuf_dsd) {
+			LOG_ERROR("unable to malloc silence dsd buffer");
+			exit(0);
+		}
+		dsd_silence_frames((u32_t *)silencebuf_dsd, MAX_SILENCE_FRAMES);
+	)
+
+	LOG_DEBUG("idle timeout: %u", idle);
+
+	output.state = idle ? OUTPUT_OFF: OUTPUT_STOPPED;
+	output.device = device;
+	output.fade = FADE_INACTIVE;
+	output.invert = false;
+	output.error_opening = false;
+	output.idle_to = (u32_t) idle;
+
+	/* Skip test_open for stdout, set default sample rates */
+	if ( output.device[0] == '-' ) {
+		for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) {
+			output.supported_rates[i] = rates[i];
+		}
+	}
+#ifndef DACAUDIO	
+	else {
+		if (!test_open(output.device, output.supported_rates, user_rates)) {
+			LOG_ERROR("unable to open output device: %s", output.device);
+			exit(0);
+		}
+	}
+#endif	
+
+	if (user_rates) {
+		for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) {
+			output.supported_rates[i] = rates[i];
+		}
+	}
+
+	// set initial sample rate, preferring 44100
+	for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) {
+		if (output.supported_rates[i] == 44100) {
+			output.default_sample_rate = 44100;
+			break;
+		}
+	}
+	if (!output.default_sample_rate) {
+		output.default_sample_rate = output.supported_rates[0];
+	}
+	
+	output.current_sample_rate = output.default_sample_rate;
+
+	if (loglevel >= lINFO) {
+		char rates_buf[10 * MAX_SUPPORTED_SAMPLERATES] = "";
+		for (i = 0; output.supported_rates[i]; ++i) {
+			char s[10];
+			sprintf(s, "%d ", output.supported_rates[i]);
+			strcat(rates_buf, s);
+		}
+		LOG_INFO("supported rates: %s", rates_buf);
+	}
+}
+
+void output_close_common(void) {
+	buf_destroy(outputbuf);
+	free(silencebuf);
+	IF_DSD(
+		free(silencebuf_dsd);
+	)
+}
+
+void output_flush(void) {
+	LOG_INFO("flush output buffer");
+	buf_flush(outputbuf);
+	LOCK;
+	output.fade = FADE_INACTIVE;
+	if (output.state != OUTPUT_OFF) {
+		output.state = OUTPUT_STOPPED;
+		if (output.error_opening) {
+			output.current_sample_rate = output.default_sample_rate;
+		}
+		output.delay_active = false;
+	}
+	output.frames_played = 0;
+	UNLOCK;
+}

+ 154 - 0
output_dac.c

@@ -0,0 +1,154 @@
+
+#include "squeezelite.h"
+
+#include <signal.h>
+
+static log_level loglevel;
+
+static bool running = true;
+
+extern struct outputstate output;
+extern struct buffer *outputbuf;
+
+#define LOCK   mutex_lock(outputbuf->mutex)
+#define UNLOCK mutex_unlock(outputbuf->mutex)
+
+#define FRAME_BLOCK MAX_SILENCE_FRAMES
+
+extern u8_t *silencebuf;
+
+// buffer to hold output data so we can block on writing outside of output lock, allocated on init
+static u8_t *buf;
+static unsigned buffill;
+static int bytes_per_frame;
+static thread_type thread;
+
+static int _dac_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR,
+								s32_t cross_gain_in, s32_t cross_gain_out, s32_t **cross_ptr);
+static void *output_thread();
+
+void set_volume(unsigned left, unsigned right) {}
+
+void output_init_dac(log_level level, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) {
+	loglevel = level;
+
+	LOG_INFO("init output DAC");
+
+	buf = malloc(FRAME_BLOCK * BYTES_PER_FRAME);
+	if (!buf) {
+		LOG_ERROR("unable to malloc buf");
+		return;
+	}
+	buffill = 0;
+
+	memset(&output, 0, sizeof(output));
+
+	output.format = S32_LE;
+	output.start_frames = FRAME_BLOCK * 2;
+	output.write_cb = &_dac_write_frames;
+	output.rate_delay = rate_delay;
+
+	if (params) {
+		if (!strcmp(params, "32"))	output.format = S32_LE;
+		if (!strcmp(params, "24")) output.format = S24_3LE;
+		if (!strcmp(params, "16")) output.format = S16_LE;
+	}
+
+	// ensure output rate is specified to avoid test open
+	if (!rates[0]) {
+		rates[0] = 44100;
+	}
+
+	output_init_common(level, "-", output_buf_size, rates, 0);
+
+#if LINUX || OSX || FREEBSD || POSIX
+	pthread_attr_t attr;
+	pthread_attr_init(&attr);
+#ifdef PTHREAD_STACK_MIN
+	pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + OUTPUT_THREAD_STACK_SIZE);
+#endif
+	pthread_create(&thread, &attr, output_thread, NULL);
+	pthread_attr_destroy(&attr);
+#endif
+#if WIN
+	thread = CreateThread(NULL, OUTPUT_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&output_thread, NULL, 0, NULL);
+#endif
+}
+
+void output_close_dac(void) {
+	LOG_INFO("close output");
+
+	LOCK;
+	running = false;
+	UNLOCK;
+
+	free(buf);
+
+	output_close_common();
+}
+
+static int _dac_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR,
+								s32_t cross_gain_in, s32_t cross_gain_out, s32_t **cross_ptr) {
+
+	u8_t *obuf;
+
+	if (!silence) {
+		
+		if (output.fade == FADE_ACTIVE && output.fade_dir == FADE_CROSS && *cross_ptr) {
+			_apply_cross(outputbuf, out_frames, cross_gain_in, cross_gain_out, cross_ptr);
+		}
+
+		obuf = outputbuf->readp;
+
+	} else {
+
+		obuf = silencebuf;
+	}
+
+	_scale_and_pack_frames(buf + buffill * bytes_per_frame, (s32_t *)(void *)obuf, out_frames, gainL, gainR, output.format);
+
+	buffill += out_frames;
+
+	return (int)out_frames;
+}
+
+static void *output_thread() {
+
+	LOCK;
+
+	switch (output.format) {
+	case S32_LE:
+		bytes_per_frame = 4 * 2; break;
+	case S24_3LE:
+		bytes_per_frame = 3 * 2; break;
+	case S16_LE:
+		bytes_per_frame = 2 * 2; break;
+	default:
+		bytes_per_frame = 4 * 2; break;
+		break;
+	}
+
+	UNLOCK;
+
+	while (running) {
+
+		LOCK;
+
+		output.device_frames = 0;
+		output.updated = gettime_ms();
+		output.frames_played_dmp = output.frames_played;
+
+		_output_frames(FRAME_BLOCK);
+
+		UNLOCK;
+
+		if (buffill) {
+			//fwrite(buf, bytes_per_frame, buffill, stdout);
+			buffill = 0;
+		}
+
+	}
+
+	return 0;
+}
+

+ 365 - 0
output_pack.c

@@ -0,0 +1,365 @@
+/* 
+ *  Squeezelite - lightweight headless squeezebox emulator
+ *
+ *  (c) Adrian Smith 2012-2015, triode1@btinternet.com
+ *      Ralph Irving 2015-2017, ralph_irving@hotmail.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+// Scale and pack functions
+
+#include "squeezelite.h"
+
+#define MAX_SCALESAMPLE 0x7fffffffffffLL
+#define MIN_SCALESAMPLE -MAX_SCALESAMPLE
+
+// inlining these on windows prevents them being linkable...
+#if !WIN
+inline 
+#endif
+s32_t gain(s32_t gain, s32_t sample) {
+	s64_t res = (s64_t)gain * (s64_t)sample;
+	if (res > MAX_SCALESAMPLE) res = MAX_SCALESAMPLE;
+	if (res < MIN_SCALESAMPLE) res = MIN_SCALESAMPLE;
+	return (s32_t) (res >> 16);
+}
+#if !WIN
+inline
+#endif
+s32_t to_gain(float f) {
+	return (s32_t)(f * 65536.0F);
+}
+
+void _scale_and_pack_frames(void *outputptr, s32_t *inputptr, frames_t cnt, s32_t gainL, s32_t gainR, output_format format) {
+	switch(format) {
+#if DSD
+	case U32_LE:
+		{
+#if SL_LITTLE_ENDIAN
+			memcpy(outputptr, inputptr, cnt * BYTES_PER_FRAME);
+#else
+			u32_t *optr = (u32_t *)(void *)outputptr;
+			while (cnt--) {
+				s32_t lsample = *(inputptr++);
+				s32_t rsample = *(inputptr++);
+				*(optr++) = 
+					(lsample & 0xff000000) >> 24 | (lsample & 0x00ff0000) >> 8 |
+					(lsample & 0x0000ff00) << 8  | (lsample & 0x000000ff) << 24;
+				*(optr++) = 
+					(rsample & 0xff000000) >> 24 | (rsample & 0x00ff0000) >> 8 |
+					(rsample & 0x0000ff00) << 8  | (rsample & 0x000000ff) << 24;
+			}
+#endif
+		}
+		break;
+	case U32_BE:
+		{
+#if SL_LITTLE_ENDIAN
+			u32_t *optr = (u32_t *)(void *)outputptr;
+			while (cnt--) {
+				s32_t lsample = *(inputptr++);
+				s32_t rsample = *(inputptr++);
+				*(optr++) = 
+					(lsample & 0xff000000) >> 24 | (lsample & 0x00ff0000) >> 8 |
+					(lsample & 0x0000ff00) << 8  | (lsample & 0x000000ff) << 24;
+				*(optr++) = 
+					(rsample & 0xff000000) >> 24 | (rsample & 0x00ff0000) >> 8 |
+					(rsample & 0x0000ff00) << 8  | (rsample & 0x000000ff) << 24;
+			}
+#else
+			memcpy(outputptr, inputptr, cnt * BYTES_PER_FRAME);
+#endif
+		}
+		break;
+	case U16_LE:
+		{
+			u32_t *optr = (u32_t *)(void *)outputptr;
+#if SL_LITTLE_ENDIAN
+			while (cnt--) {
+				*(optr++) = (*(inputptr) >> 16 & 0x0000ffff) | (*(inputptr + 1) & 0xffff0000);
+				inputptr += 2;
+			}
+#else
+			while (cnt--) {
+				s32_t lsample = *(inputptr++);
+				s32_t rsample = *(inputptr++);
+				*(optr++) = 
+					(lsample & 0x00ff0000) << 8 | (lsample & 0xff000000) >> 8 |
+					(rsample & 0x00ff0000) >> 8 | (rsample & 0xff000000) >> 24;
+			}
+#endif
+		}
+		break;
+	case U16_BE:
+		{
+			u32_t *optr = (u32_t *)(void *)outputptr;
+#if SL_LITTLE_ENDIAN
+			while (cnt--) {
+				s32_t lsample = *(inputptr++);
+				s32_t rsample = *(inputptr++);
+				*(optr++) = 
+					(lsample & 0xff000000) >> 24 | (lsample & 0x00ff0000) >> 8 |
+					(rsample & 0xff000000) >> 8 | (rsample & 0x00ff0000) << 8;
+			}
+#else
+			while (cnt--) {
+				*(optr++) = (*(inputptr) & 0xffff0000) | (*(inputptr + 1) >> 16 & 0x0000ffff);
+				inputptr += 2;
+			}
+#endif
+		}
+		break;
+	case U8:
+		{
+			u16_t *optr = (u16_t *)(void *)outputptr;
+#if SL_LITTLE_ENDIAN
+			while (cnt--) {
+				*(optr++) = (u16_t)((*(inputptr) >> 24 & 0x000000ff) | (*(inputptr + 1) >> 16 & 0x0000ff00));
+				inputptr += 2;
+			}
+#else
+			while (cnt--) {
+				*(optr++) = (u16_t)((*(inputptr) >> 16 & 0x0000ff00) | (*(inputptr + 1) >> 24 & 0x000000ff));
+				inputptr += 2;
+			}
+#endif
+		}
+		break;
+#endif
+	case S16_LE:
+		{
+			u32_t *optr = (u32_t *)(void *)outputptr;
+#if SL_LITTLE_ENDIAN
+			if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
+				while (cnt--) {
+					*(optr++) = (*(inputptr) >> 16 & 0x0000ffff) | (*(inputptr + 1) & 0xffff0000);
+					inputptr += 2;
+				}
+			} else {
+				while (cnt--) {
+					*(optr++) =  (gain(gainL, *(inputptr)) >> 16 & 0x0000ffff) | (gain(gainR, *(inputptr+1)) & 0xffff0000);
+					inputptr += 2;
+				}
+			}
+#else
+			if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
+				while (cnt--) {
+					s32_t lsample = *(inputptr++);
+					s32_t rsample = *(inputptr++);
+					*(optr++) = 
+						(lsample & 0x00ff0000) << 8 | (lsample & 0xff000000) >> 8 |
+						(rsample & 0x00ff0000) >> 8 | (rsample & 0xff000000) >> 24;
+				}
+			} else {
+				while (cnt--) {
+					s32_t lsample = gain(gainL, *(inputptr++));
+					s32_t rsample = gain(gainR, *(inputptr++));
+					*(optr++) = 
+						(lsample & 0x00ff0000) << 8 | (lsample & 0xff000000) >> 8 |
+						(rsample & 0x00ff0000) >> 8 | (rsample & 0xff000000) >> 24;
+				}
+			}
+#endif
+		}
+		break;
+	case S24_LE: 
+		{
+			u32_t *optr = (u32_t *)(void *)outputptr;
+#if SL_LITTLE_ENDIAN
+			if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
+				while (cnt--) {
+					*(optr++) = *(inputptr++) >> 8;
+					*(optr++) = *(inputptr++) >> 8;
+				}
+			} else {
+				while (cnt--) {
+					*(optr++) = gain(gainL, *(inputptr++)) >> 8;
+					*(optr++) = gain(gainR, *(inputptr++)) >> 8;
+				}
+			}
+#else
+			if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
+				while (cnt--) {
+					s32_t lsample = *(inputptr++);
+					s32_t rsample = *(inputptr++);
+					*(optr++) = 
+						(lsample & 0xff000000) >> 16 | (lsample & 0x00ff0000) | (lsample & 0x0000ff00 << 16);
+					*(optr++) = 
+						(rsample & 0xff000000) >> 16 | (rsample & 0x00ff0000) | (rsample & 0x0000ff00 << 16);
+				}
+			} else {
+				while (cnt--) {
+					s32_t lsample = gain(gainL, *(inputptr++));
+					s32_t rsample = gain(gainR, *(inputptr++));
+					*(optr++) = 
+						(lsample & 0xff000000) >> 16 | (lsample & 0x00ff0000) | (lsample & 0x0000ff00 << 16);
+					*(optr++) = 
+						(rsample & 0xff000000) >> 16 | (rsample & 0x00ff0000) | (rsample & 0x0000ff00 << 16);
+				}
+			}
+#endif
+		}
+		break;
+	case S24_3LE:
+		{
+			u8_t *optr = (u8_t *)(void *)outputptr;
+			if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
+				while (cnt) {
+					// attempt to do 32 bit memory accesses - move 2 frames at once: 16 bytes -> 12 bytes
+					// falls through to exception case when not aligned or if less than 2 frames to move
+					if (((uintptr_t)optr & 0x3) == 0 && cnt >= 2) {
+						u32_t *o_ptr = (u32_t *)(void *)optr;
+						while (cnt >= 2) {
+							s32_t l1 = *(inputptr++); s32_t r1 = *(inputptr++);
+							s32_t l2 = *(inputptr++); s32_t r2 = *(inputptr++);
+#if SL_LITTLE_ENDIAN
+							*(o_ptr++) = (l1 & 0xffffff00) >>  8 | (r1 & 0x0000ff00) << 16;
+							*(o_ptr++) = (r1 & 0xffff0000) >> 16 | (l2 & 0x00ffff00) <<  8;
+							*(o_ptr++) = (l2 & 0xff000000) >> 24 | (r2 & 0xffffff00);
+#else
+							*(o_ptr++) = (l1 & 0x0000ff00) << 16 | (l1 & 0x00ff0000) | (l1 & 0xff000000) >> 16 |
+								(r1 & 0x0000ff00) >> 8; 
+							*(o_ptr++) = (r1 & 0x00ff0000) <<  8 | (r1 & 0xff000000) >> 8 | (l2 & 0x0000ff00) |
+								(l2 & 0x00ff0000) >> 16;
+							*(o_ptr++) = (l2 & 0xff000000) | (r2 & 0x0000ff00) << 8 | (r2 & 0x00ff0000) >> 8 |
+								(r2 & 0xff000000) >> 24;
+#endif
+							optr += 12;
+							cnt  -=  2;
+						}
+					} else {
+						s32_t lsample = *(inputptr++);
+						s32_t rsample = *(inputptr++);
+						*(optr++) = (lsample & 0x0000ff00) >>  8;
+						*(optr++) = (lsample & 0x00ff0000) >> 16;
+						*(optr++) = (lsample & 0xff000000) >> 24;
+						*(optr++) = (rsample & 0x0000ff00) >>  8;
+						*(optr++) = (rsample & 0x00ff0000) >> 16;
+						*(optr++) = (rsample & 0xff000000) >> 24;
+						cnt--;
+					}
+				}
+			} else {
+				while (cnt) {
+					// attempt to do 32 bit memory accesses - move 2 frames at once: 16 bytes -> 12 bytes
+					// falls through to exception case when not aligned or if less than 2 frames to move
+					if (((uintptr_t)optr & 0x3) == 0 && cnt >= 2) {
+						u32_t *o_ptr = (u32_t *)(void *)optr;
+						while (cnt >= 2) {
+							s32_t l1 = gain(gainL, *(inputptr++)); s32_t r1 = gain(gainR, *(inputptr++));
+							s32_t l2 = gain(gainL, *(inputptr++)); s32_t r2 = gain(gainR, *(inputptr++));
+#if SL_LITTLE_ENDIAN
+							*(o_ptr++) = (l1 & 0xffffff00) >>  8 | (r1 & 0x0000ff00) << 16;
+							*(o_ptr++) = (r1 & 0xffff0000) >> 16 | (l2 & 0x00ffff00) <<  8;
+							*(o_ptr++) = (l2 & 0xff000000) >> 24 | (r2 & 0xffffff00);
+#else
+							*(o_ptr++) = (l1 & 0x0000ff00) << 16 | (l1 & 0x00ff0000) | (l1 & 0xff000000) >> 16 |
+								(r1 & 0x0000ff00) >> 8; 
+							*(o_ptr++) = (r1 & 0x00ff0000) <<  8 | (r1 & 0xff000000) >> 8 | (l2 & 0x0000ff00) |
+								(l2 & 0x00ff0000) >> 16;
+							*(o_ptr++) = (l2 & 0xff000000) | (r2 & 0x0000ff00) << 8 | (r2 & 0x00ff0000) >> 8 |
+								(r2 & 0xff000000) >> 24;
+#endif
+							optr += 12;
+							cnt  -=  2;
+						}
+					} else {
+						s32_t lsample = gain(gainL, *(inputptr++));
+						s32_t rsample = gain(gainR, *(inputptr++));
+						*(optr++) = (lsample & 0x0000ff00) >>  8;
+						*(optr++) = (lsample & 0x00ff0000) >> 16;
+						*(optr++) = (lsample & 0xff000000) >> 24;
+						*(optr++) = (rsample & 0x0000ff00) >>  8;
+						*(optr++) = (rsample & 0x00ff0000) >> 16;
+						*(optr++) = (rsample & 0xff000000) >> 24;
+						cnt--;
+					}
+				}
+			}
+		}
+		break;
+	case S32_LE:
+		{
+			u32_t *optr = (u32_t *)(void *)outputptr;
+#if SL_LITTLE_ENDIAN
+			if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
+				memcpy(outputptr, inputptr, cnt * BYTES_PER_FRAME);
+			} else {
+				while (cnt--) {
+					*(optr++) = gain(gainL, *(inputptr++));
+					*(optr++) = gain(gainR, *(inputptr++));
+				}
+			}
+#else
+			if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
+				while (cnt--) {
+					s32_t lsample = *(inputptr++);
+					s32_t rsample = *(inputptr++);
+					*(optr++) = 
+						(lsample & 0xff000000) >> 24 | (lsample & 0x00ff0000) >> 8 |
+						(lsample & 0x0000ff00) << 8  | (lsample & 0x000000ff) << 24;
+					*(optr++) = 
+						(rsample & 0xff000000) >> 24 | (rsample & 0x00ff0000) >> 8 |
+						(rsample & 0x0000ff00) << 8  | (rsample & 0x000000ff) << 24;
+				}
+			} else {
+				while (cnt--) {
+					s32_t lsample = gain(gainL, *(inputptr++));
+					s32_t rsample = gain(gainR, *(inputptr++));
+					*(optr++) = 
+						(lsample & 0xff000000) >> 24 | (lsample & 0x00ff0000) >> 8 |
+						(lsample & 0x0000ff00) << 8  | (lsample & 0x000000ff) << 24;
+					*(optr++) = 
+						(rsample & 0xff000000) >> 24 | (rsample & 0x00ff0000) >> 8 |
+						(rsample & 0x0000ff00) << 8  | (rsample & 0x000000ff) << 24;
+				}
+			}
+#endif
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+#if !WIN
+inline 
+#endif
+void _apply_cross(struct buffer *outputbuf, frames_t out_frames, s32_t cross_gain_in, s32_t cross_gain_out, s32_t **cross_ptr) {
+	s32_t *ptr = (s32_t *)(void *)outputbuf->readp;
+	frames_t count = out_frames * 2;
+	while (count--) {
+		if (*cross_ptr > (s32_t *)outputbuf->wrap) {
+			*cross_ptr -= outputbuf->size / BYTES_PER_FRAME * 2;
+		}
+		*ptr = gain(cross_gain_out, *ptr) + gain(cross_gain_in, **cross_ptr);
+		ptr++; (*cross_ptr)++;
+	}
+}
+
+#if !WIN
+inline 
+#endif
+void _apply_gain(struct buffer *outputbuf, frames_t count, s32_t gainL, s32_t gainR) {
+	s32_t *ptrL = (s32_t *)(void *)outputbuf->readp;
+	s32_t *ptrR = (s32_t *)(void *)outputbuf->readp + 1;
+	while (count--) {
+		*ptrL = gain(gainL, *ptrL);
+		*ptrR = gain(gainR, *ptrR);
+		ptrL += 2;
+		ptrR += 2;
+	}
+}

+ 438 - 0
pcm.c

@@ -0,0 +1,438 @@
+/* 
+ *  Squeezelite - lightweight headless squeezebox emulator
+ *
+ *  (c) Adrian Smith 2012-2015, triode1@btinternet.com
+ *      Ralph Irving 2015-2017, ralph_irving@hotmail.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "squeezelite.h"
+
+extern log_level loglevel;
+
+extern struct buffer *streambuf;
+extern struct buffer *outputbuf;
+extern struct streamstate stream;
+extern struct outputstate output;
+extern struct decodestate decode;
+extern struct processstate process;
+
+bool pcm_check_header = false;
+
+#define LOCK_S   mutex_lock(streambuf->mutex)
+#define UNLOCK_S mutex_unlock(streambuf->mutex)
+#define LOCK_O   mutex_lock(outputbuf->mutex)
+#define UNLOCK_O mutex_unlock(outputbuf->mutex)
+#if PROCESS
+#define LOCK_O_direct   if (decode.direct) mutex_lock(outputbuf->mutex)
+#define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex)
+#define LOCK_O_not_direct   if (!decode.direct) mutex_lock(outputbuf->mutex)
+#define UNLOCK_O_not_direct if (!decode.direct) mutex_unlock(outputbuf->mutex)
+#define IF_DIRECT(x)    if (decode.direct) { x }
+#define IF_PROCESS(x)   if (!decode.direct) { x }
+#else
+#define LOCK_O_direct   mutex_lock(outputbuf->mutex)
+#define UNLOCK_O_direct mutex_unlock(outputbuf->mutex)
+#define LOCK_O_not_direct
+#define UNLOCK_O_not_direct
+#define IF_DIRECT(x)    { x }
+#define IF_PROCESS(x)
+#endif
+
+#define MAX_DECODE_FRAMES 4096
+
+static u32_t sample_rates[] = {
+	11025, 22050, 32000, 44100, 48000, 8000, 12000, 16000, 24000, 96000, 88200, 176400, 192000, 352800, 384000, 705600, 768000
+};
+
+static u32_t sample_rate;
+static u32_t sample_size;
+static u32_t channels;
+static bool  bigendian;
+static bool  limit;
+static u32_t audio_left;
+static u32_t bytes_per_frame;
+
+typedef enum { UNKNOWN = 0, WAVE, AIFF } header_format;
+
+static void _check_header(void) {
+	u8_t *ptr = streambuf->readp;
+	unsigned bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
+	header_format format = UNKNOWN;
+
+	// simple parsing of wav and aiff headers and get to samples
+
+	if (bytes > 12) {
+		if (!memcmp(ptr, "RIFF", 4) && !memcmp(ptr+8, "WAVE", 4)) {
+			LOG_INFO("WAVE");
+			format = WAVE;
+		} else if (!memcmp(ptr, "FORM", 4) && (!memcmp(ptr+8, "AIFF", 4) || !memcmp(ptr+8, "AIFC", 4))) {
+			LOG_INFO("AIFF");
+			format = AIFF;
+		}
+	}
+
+	if (format != UNKNOWN) {
+		ptr   += 12;
+		bytes -= 12;
+
+		while (bytes >= 8) {
+			char id[5];
+			unsigned len;
+			memcpy(id, ptr, 4);
+			id[4] = '\0';
+			
+			if (format == WAVE) {
+				len = *(ptr+4) | *(ptr+5) << 8 | *(ptr+6) << 16| *(ptr+7) << 24;
+			} else {
+				len = *(ptr+4) << 24 | *(ptr+5) << 16 | *(ptr+6) << 8 | *(ptr+7);
+			}
+				
+			LOG_INFO("header: %s len: %d", id, len);
+
+			if (format == WAVE && !memcmp(ptr, "data", 4)) {
+				ptr += 8;
+				_buf_inc_readp(streambuf, ptr - streambuf->readp);
+				audio_left = len;
+
+				if ((audio_left == 0xFFFFFFFF) || (audio_left == 0x7FFFEFFC)) {
+					LOG_INFO("wav audio size unknown: %u", audio_left);
+					limit = false;
+				} else {
+					LOG_INFO("wav audio size: %u", audio_left);
+					limit = true;
+				}
+				return;
+			}
+
+			if (format == AIFF && !memcmp(ptr, "SSND", 4) && bytes >= 16) {
+				unsigned offset = *(ptr+8) << 24 | *(ptr+9) << 16 | *(ptr+10) << 8 | *(ptr+11);
+				// following 4 bytes is blocksize - ignored
+				ptr += 8 + 8;
+				_buf_inc_readp(streambuf, ptr + offset - streambuf->readp);
+				
+				// Reading from an upsampled stream, length could be wrong.
+				// Only use length in header for files.
+				if (stream.state == STREAMING_FILE) {
+					audio_left = len - 8 - offset;
+					LOG_INFO("aif audio size: %u", audio_left);
+					limit = true;
+				}
+				return;
+			}
+
+			if (format == WAVE && !memcmp(ptr, "fmt ", 4) && bytes >= 24) {
+				// override the server parsed values with our own
+				channels    = *(ptr+10) | *(ptr+11) << 8;
+				sample_rate = *(ptr+12) | *(ptr+13) << 8 | *(ptr+14) << 16 | *(ptr+15) << 24;
+				sample_size = (*(ptr+22) | *(ptr+23) << 8) / 8;
+				bigendian   = 0;
+				LOG_INFO("pcm size: %u rate: %u chan: %u bigendian: %u", sample_size, sample_rate, channels, bigendian);
+			}
+
+			if (format == AIFF && !memcmp(ptr, "COMM", 4) && bytes >= 26) {
+				int exponent;
+				// override the server parsed values with our own
+				channels    = *(ptr+8) << 8 | *(ptr+9);
+				sample_size = (*(ptr+14) << 8 | *(ptr+15)) / 8;
+				bigendian   = 1;
+				// sample rate is encoded as IEEE 80 bit extended format
+				// make some assumptions to simplify processing - only use first 32 bits of mantissa
+				exponent = ((*(ptr+16) & 0x7f) << 8 | *(ptr+17)) - 16383 - 31;
+				sample_rate  = *(ptr+18) << 24 | *(ptr+19) << 16 | *(ptr+20) << 8 | *(ptr+21);
+				while (exponent < 0) { sample_rate >>= 1; ++exponent; }
+				while (exponent > 0) { sample_rate <<= 1; --exponent; }
+				LOG_INFO("pcm size: %u rate: %u chan: %u bigendian: %u", sample_size, sample_rate, channels, bigendian);
+			}
+
+			if (bytes >= len + 8) {
+				ptr   += len + 8;
+				bytes -= (len + 8);
+			} else {
+				LOG_WARN("run out of data");
+				return;
+			}
+		}
+
+	} else {
+		LOG_WARN("unknown format - can't parse header");
+	}
+}
+
+static decode_state pcm_decode(void) {
+	unsigned bytes, in, out;
+	frames_t frames, count;
+	u32_t *optr;
+	u8_t  *iptr;
+	u8_t tmp[16];
+	
+	LOCK_S;
+
+	if ( decode.new_stream && ( ( stream.state == STREAMING_FILE ) || pcm_check_header ) ) {
+		_check_header();
+	}
+
+	LOCK_O_direct;
+
+	bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
+
+	IF_DIRECT(
+		out = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
+	);
+	IF_PROCESS(
+		out = process.max_in_frames;
+	);
+
+	if ((stream.state <= DISCONNECT && bytes == 0) || (limit && audio_left == 0)) {
+		UNLOCK_O_direct;
+		UNLOCK_S;
+		return DECODE_COMPLETE;
+	}
+
+	if (decode.new_stream) {
+		LOG_INFO("setting track_start");
+		LOCK_O_not_direct;
+		output.track_start = outputbuf->writep;
+		decode.new_stream = false;
+#if DSD
+		if (sample_size == 3 &&
+			is_stream_dop(((u8_t *)streambuf->readp) + (bigendian?0:2),
+						  ((u8_t *)streambuf->readp) + (bigendian?0:2) + sample_size,
+						  sample_size * channels, bytes / (sample_size * channels))) {
+			LOG_INFO("file contains DOP");
+			if (output.dsdfmt == DOP_S24_LE || output.dsdfmt == DOP_S24_3LE)
+				output.next_fmt = output.dsdfmt;
+			else
+				output.next_fmt = DOP;
+			output.next_sample_rate = sample_rate;
+			output.fade = FADE_INACTIVE;
+		} else {
+			output.next_sample_rate = decode_newstream(sample_rate, output.supported_rates);
+			output.next_fmt = PCM;
+			if (output.fade_mode) _checkfade(true);
+		}
+#else
+		output.next_sample_rate = decode_newstream(sample_rate, output.supported_rates);
+		if (output.fade_mode) _checkfade(true);
+#endif
+		UNLOCK_O_not_direct;
+		IF_PROCESS(
+			out = process.max_in_frames;
+		);
+		bytes_per_frame = channels * sample_size;
+	}
+
+	IF_DIRECT(
+		optr = (u32_t *)outputbuf->writep;
+	);
+	IF_PROCESS(
+		optr = (u32_t *)process.inbuf;
+	);
+	iptr = (u8_t *)streambuf->readp;
+
+	in = bytes / bytes_per_frame;
+
+	//  handle frame wrapping round end of streambuf
+	//  - only need if resizing of streambuf does not avoid this, could occur in localfile case
+	if (in == 0 && bytes > 0 && _buf_used(streambuf) >= bytes_per_frame) {
+		memcpy(tmp, iptr, bytes);
+		memcpy(tmp + bytes, streambuf->buf, bytes_per_frame - bytes);
+		iptr = tmp;
+		in = 1;
+	}
+
+	frames = min(in, out);
+	frames = min(frames, MAX_DECODE_FRAMES);
+
+	if (limit && frames * bytes_per_frame > audio_left) {
+		LOG_INFO("reached end of audio");
+		frames = audio_left / bytes_per_frame;
+	}
+
+	count = frames * channels;
+
+	if (channels == 2) {
+		if (sample_size == 1) {
+			while (count--) {
+				*optr++ = *iptr++ << 24;
+			}
+		} else if (sample_size == 2) {
+			if (bigendian) {
+				while (count--) {
+					*optr++ = *(iptr) << 24 | *(iptr+1) << 16;
+					iptr += 2;
+				}
+			} else {
+				while (count--) {
+					*optr++ = *(iptr) << 16 | *(iptr+1) << 24;
+					iptr += 2;
+				}
+			}
+		} else if (sample_size == 3) {
+			if (bigendian) {
+				while (count--) {
+					*optr++ = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8;
+					iptr += 3;
+				}
+			} else {
+				while (count--) {
+					*optr++ = *(iptr) << 8 | *(iptr+1) << 16 | *(iptr+2) << 24;
+					iptr += 3;
+				}
+			}
+		} else if (sample_size == 4) {
+			if (bigendian) {
+				while (count--) {
+					*optr++ = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8 | *(iptr+3);
+					iptr += 4;
+				}
+			} else {
+				while (count--) {
+					*optr++ = *(iptr) | *(iptr+1) << 8 | *(iptr+2) << 16 | *(iptr+3) << 24;
+					iptr += 4;
+				}
+			}
+		}
+	} else if (channels == 1) {
+		if (sample_size == 1) {
+			while (count--) {
+				*optr = *iptr++ << 24;
+				*(optr+1) = *optr;
+				optr += 2;
+			}
+		} else if (sample_size == 2) {
+			if (bigendian) {
+				while (count--) {
+					*optr = *(iptr) << 24 | *(iptr+1) << 16;
+					*(optr+1) = *optr;
+					iptr += 2;
+					optr += 2;
+				}
+			} else {
+				while (count--) {
+					*optr = *(iptr) << 16 | *(iptr+1) << 24;
+					*(optr+1) = *optr;
+					iptr += 2;
+					optr += 2;
+				}
+			}
+		} else if (sample_size == 3) {
+			if (bigendian) {
+				while (count--) {
+					*optr = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8;
+					*(optr+1) = *optr;
+					iptr += 3;
+					optr += 2;
+				}
+			} else {
+				while (count--) {
+					*optr = *(iptr) << 8 | *(iptr+1) << 16 | *(iptr+2) << 24;
+					*(optr+1) = *optr;
+					iptr += 3;
+					optr += 2;
+				}
+			}
+		} else if (sample_size == 4) {
+			if (bigendian) {
+				while (count--) {
+					*optr++ = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8 | *(iptr+3);
+					*(optr+1) = *optr;
+					iptr += 4;
+					optr += 2;
+				}
+			} else {
+				while (count--) {
+					*optr++ = *(iptr) | *(iptr+1) << 8 | *(iptr+2) << 16 | *(iptr+3) << 24;
+					*(optr+1) = *optr;
+					iptr += 4;
+					optr += 2;
+				}
+			}
+		}
+	} else {
+		LOG_ERROR("unsupported channels");
+	}
+
+	LOG_SDEBUG("decoded %u frames", frames);
+
+	_buf_inc_readp(streambuf, frames * bytes_per_frame);
+
+	if (limit) {
+		audio_left -= frames * bytes_per_frame;
+	}
+
+	IF_DIRECT(
+		_buf_inc_writep(outputbuf, frames * BYTES_PER_FRAME);
+	);
+	IF_PROCESS(
+		process.in_frames = frames;
+	);
+
+	UNLOCK_O_direct;
+	UNLOCK_S;
+
+	return DECODE_RUNNING;
+}
+
+static void pcm_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
+	sample_size = size - '0' + 1;
+	sample_rate = sample_rates[rate - '0'];
+	channels    = chan - '0';
+	bigendian   = (endianness == '0');
+	limit       = false;
+
+	LOG_INFO("pcm size: %u rate: %u chan: %u bigendian: %u", sample_size, sample_rate, channels, bigendian);
+	buf_adjust(streambuf, sample_size * channels);
+}
+
+static void pcm_close(void) {
+	buf_adjust(streambuf, 1);
+}
+
+struct codec *register_pcm(void) {
+	if ( pcm_check_header )
+	{
+		static struct codec ret = { 
+			'p',         // id
+			"wav,aif,pcm", // types
+			4096,        // min read
+			102400,      // min space
+			pcm_open,    // open
+			pcm_close,   // close
+			pcm_decode,  // decode
+		};
+
+		LOG_INFO("using pcm to decode wav,aif,pcm");
+		return &ret;
+	}
+	else
+	{
+		static struct codec ret = { 
+			'p',         // id
+			"aif,pcm", // types
+			4096,        // min read
+			102400,      // min space
+			pcm_open,    // open
+			pcm_close,   // close
+			pcm_decode,  // decode
+		};
+
+		LOG_INFO("using pcm to decode aif,pcm");
+		return &ret;
+	}
+
+	return NULL;
+}

+ 148 - 0
scan.c

@@ -0,0 +1,148 @@
+/* Scan Example
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+/*
+    This example shows how to use the All Channel Scan or Fast Scan to connect
+    to a Wi-Fi network.
+
+    In the Fast Scan mode, the scan will stop as soon as the first network matching
+    the SSID is found. In this mode, an application can set threshold for the
+    authentication mode and the Signal strength. Networks that do not meet the
+    threshold requirements will be ignored.
+
+    In the All Channel Scan mode, the scan will end only after all the channels
+    are scanned, and connection will start with the best network. The networks
+    can be sorted based on Authentication Mode or Signal Strength. The priority
+    for the Authentication mode is:  WPA2 > WPA > WEP > Open
+*/
+#include "freertos/FreeRTOS.h"
+#include "freertos/event_groups.h"
+#include "esp_wifi.h"
+#include "esp_log.h"
+#include "esp_event.h"
+#include "nvs_flash.h"
+#include "sys/socket.h"
+#include "string.h"
+
+/*Set the SSID and Password via "make menuconfig"*/
+#define DEFAULT_SSID CONFIG_WIFI_SSID
+#define DEFAULT_PWD CONFIG_WIFI_PASSWORD
+
+#if CONFIG_WIFI_ALL_CHANNEL_SCAN
+#define DEFAULT_SCAN_METHOD WIFI_ALL_CHANNEL_SCAN
+#elif CONFIG_WIFI_FAST_SCAN
+#define DEFAULT_SCAN_METHOD WIFI_FAST_SCAN
+#else
+#define DEFAULT_SCAN_METHOD WIFI_FAST_SCAN
+#endif /*CONFIG_SCAN_METHOD*/
+
+#if CONFIG_WIFI_CONNECT_AP_BY_SIGNAL
+#define DEFAULT_SORT_METHOD WIFI_CONNECT_AP_BY_SIGNAL
+#elif CONFIG_WIFI_CONNECT_AP_BY_SECURITY
+#define DEFAULT_SORT_METHOD WIFI_CONNECT_AP_BY_SECURITY
+#else
+#define DEFAULT_SORT_METHOD WIFI_CONNECT_AP_BY_SIGNAL
+#endif /*CONFIG_SORT_METHOD*/
+
+#if CONFIG_FAST_SCAN_THRESHOLD
+#define DEFAULT_RSSI CONFIG_FAST_SCAN_MINIMUM_SIGNAL
+#if CONFIG_EXAMPLE_OPEN
+#define DEFAULT_AUTHMODE WIFI_AUTH_OPEN
+#elif CONFIG_EXAMPLE_WEP
+#define DEFAULT_AUTHMODE WIFI_AUTH_WEP
+#elif CONFIG_EXAMPLE_WPA
+#define DEFAULT_AUTHMODE WIFI_AUTH_WPA_PSK
+#elif CONFIG_EXAMPLE_WPA2
+#define DEFAULT_AUTHMODE WIFI_AUTH_WPA2_PSK
+#else
+#define DEFAULT_AUTHMODE WIFI_AUTH_OPEN
+#endif
+#else
+#define DEFAULT_RSSI -127
+#define DEFAULT_AUTHMODE WIFI_AUTH_OPEN
+#endif /*CONFIG_FAST_SCAN_THRESHOLD*/
+
+static const char *TAG = "scan";
+
+static void event_handler(void* arg, esp_event_base_t event_base, 
+                                int32_t event_id, void* event_data)
+{
+    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
+        esp_wifi_connect();
+    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
+        esp_wifi_connect();
+    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
+        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
+        ESP_LOGI(TAG, "got ip: %s", ip4addr_ntoa(&event->ip_info.ip));
+    }
+}
+
+
+/* Initialize Wi-Fi as sta and set scan method */
+static void wifi_scan(void)
+{
+    tcpip_adapter_init();
+    ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+
+    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
+    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
+
+    wifi_config_t wifi_config = {
+        .sta = {
+            .ssid = DEFAULT_SSID,
+            .password = DEFAULT_PWD,
+            .scan_method = DEFAULT_SCAN_METHOD,
+            .sort_method = DEFAULT_SORT_METHOD,
+            .threshold.rssi = DEFAULT_RSSI,
+            .threshold.authmode = DEFAULT_AUTHMODE,
+        },
+    };
+    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
+    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
+    ESP_ERROR_CHECK(esp_wifi_start());
+}
+
+int main(int argc, char**argv);
+
+void app_main()
+{
+	int i; 
+	char **argv, *_argv[] = {
+		"squeezelite-esp32",
+		"-n",
+		"ESP32",
+		"-d",
+		"all=info",
+		"-d",
+		"slimproto=debug",
+		"-b",
+		"128:2000",
+	};
+	
+	// can't do strtok on FLASH strings
+	argv = malloc(sizeof(_argv));
+	for (i = 0; i < sizeof(_argv)/sizeof(char*); i++) {
+		argv[i] = strdup(_argv[i]);
+	}
+	
+    // Initialize NVS
+    esp_err_t ret = nvs_flash_init();
+    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
+        ESP_ERROR_CHECK(nvs_flash_erase());
+        ret = nvs_flash_init();
+    }
+    ESP_ERROR_CHECK( ret );
+	
+	wifi_scan();
+	
+	main(sizeof(_argv)/sizeof(char*), argv);
+}

+ 976 - 0
slimproto.c

@@ -0,0 +1,976 @@
+/* 
+ *  Squeezelite - lightweight headless squeezebox emulator
+ *
+ *  (c) Adrian Smith 2012-2015, triode1@btinternet.com
+ *      Ralph Irving 2015-2017, ralph_irving@hotmail.com
+ *  
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Additions (c) Paul Hermann, 2015-2017 under the same license terms
+ *   -Control of Raspberry pi GPIO for amplifier power
+ *   -Launch script on power status change from LMS
+ */
+
+#include "squeezelite.h"
+#include "slimproto.h"
+
+static log_level loglevel;
+
+#define SQUEEZENETWORK "mysqueezebox.com:3483"
+
+#define PORT 3483
+
+#define MAXBUF 4096
+
+#if SL_LITTLE_ENDIAN
+#define LOCAL_PLAYER_IP   0x0100007f // 127.0.0.1
+#define LOCAL_PLAYER_PORT 0x9b0d     // 3483
+#else
+#define LOCAL_PLAYER_IP   0x7f000001 // 127.0.0.1
+#define LOCAL_PLAYER_PORT 0x0d9b     // 3483
+#endif
+
+static sockfd sock = -1;
+static in_addr_t slimproto_ip = 0;
+
+extern struct buffer *streambuf;
+extern struct buffer *outputbuf;
+
+extern struct streamstate stream;
+extern struct outputstate output;
+extern struct decodestate decode;
+
+extern struct codec *codecs[];
+#if IR
+extern struct irstate ir;
+#endif
+
+event_event wake_e;
+
+#define LOCK_S   mutex_lock(streambuf->mutex)
+#define UNLOCK_S mutex_unlock(streambuf->mutex)
+#define LOCK_O   mutex_lock(outputbuf->mutex)
+#define UNLOCK_O mutex_unlock(outputbuf->mutex)
+#define LOCK_D   mutex_lock(decode.mutex)
+#define UNLOCK_D mutex_unlock(decode.mutex)
+#if IR
+#define LOCK_I   mutex_lock(ir.mutex)
+#define UNLOCK_I mutex_unlock(ir.mutex)
+#endif
+
+static struct {
+	u32_t updated;
+	u32_t stream_start;
+	u32_t stream_full;
+	u32_t stream_size;
+	u64_t stream_bytes;
+	u32_t output_full;
+	u32_t output_size;
+	u32_t frames_played;
+	u32_t device_frames;
+	u32_t current_sample_rate;
+	u32_t last;
+	stream_state stream_state;
+} status;
+
+int autostart;
+bool sentSTMu, sentSTMo, sentSTMl;
+u32_t new_server;
+char *new_server_cap;
+#define PLAYER_NAME_LEN 64
+char player_name[PLAYER_NAME_LEN + 1] = "";
+const char *name_file = NULL;
+
+void send_packet(u8_t *packet, size_t len) {
+	u8_t *ptr = packet;
+	unsigned try = 0;
+	ssize_t n;
+
+	while (len) {
+		n = send(sock, ptr, len, MSG_NOSIGNAL);
+		if (n <= 0) {
+			if (n < 0 && last_error() == ERROR_WOULDBLOCK && try < 10) {
+				LOG_DEBUG("retrying (%d) writing to socket", ++try);
+				usleep(1000);
+				continue;
+			}
+			LOG_INFO("failed writing to socket: %s", strerror(last_error()));
+			return;
+		}
+		ptr += n;
+		len -= n;
+	}
+}
+
+static void sendHELO(bool reconnect, const char *fixed_cap, const char *var_cap, u8_t mac[6]) {
+	#define BASE_CAP "Model=squeezelite,AccuratePlayPoints=1,HasDigitalOut=1,HasPolarityInversion=1,Firmware=" VERSION
+	#define SSL_CAP "CanHTTPS=1"
+	const char *base_cap;
+	struct HELO_packet pkt;
+	
+#if USE_SSL
+#if !LINKALL
+	if (ssl_loaded) base_cap = SSL_CAP "," BASE_CAP;
+	else base_cap = BASE_CAP;
+#endif	
+	base_cap = SSL_CAP "," BASE_CAP;
+#else
+	base_cap = BASE_CAP;
+#endif	
+
+	memset(&pkt, 0, sizeof(pkt));
+	memcpy(&pkt.opcode, "HELO", 4);
+	pkt.length = htonl(sizeof(struct HELO_packet) - 8 + strlen(base_cap) + strlen(fixed_cap) + strlen(var_cap));
+	pkt.deviceid = 12; // squeezeplay
+	pkt.revision = 0;
+	packn(&pkt.wlan_channellist, reconnect ? 0x4000 : 0x0000);
+	packN(&pkt.bytes_received_H, (u64_t)status.stream_bytes >> 32);
+	packN(&pkt.bytes_received_L, (u64_t)status.stream_bytes & 0xffffffff);
+	memcpy(pkt.mac, mac, 6);
+
+	LOG_INFO("mac: %02x:%02x:%02x:%02x:%02x:%02x", pkt.mac[0], pkt.mac[1], pkt.mac[2], pkt.mac[3], pkt.mac[4], pkt.mac[5]);
+
+	LOG_INFO("cap: %s%s%s", base_cap, fixed_cap, var_cap);
+
+	send_packet((u8_t *)&pkt, sizeof(pkt));
+	send_packet((u8_t *)base_cap, strlen(base_cap));
+	send_packet((u8_t *)fixed_cap, strlen(fixed_cap));
+	send_packet((u8_t *)var_cap, strlen(var_cap));
+}
+
+static void sendSTAT(const char *event, u32_t server_timestamp) {
+	struct STAT_packet pkt;
+	u32_t now = gettime_ms();
+	u32_t ms_played;
+
+	if (status.current_sample_rate && status.frames_played && status.frames_played > status.device_frames) {
+		ms_played = (u32_t)(((u64_t)(status.frames_played - status.device_frames) * (u64_t)1000) / (u64_t)status.current_sample_rate);
+#ifndef STATUSHACK
+		if (now > status.updated) ms_played += (now - status.updated);
+#endif
+		LOG_SDEBUG("ms_played: %u (frames_played: %u device_frames: %u)", ms_played, status.frames_played, status.device_frames);
+	} else if (status.frames_played && now > status.stream_start) {
+		ms_played = now - status.stream_start;
+		LOG_SDEBUG("ms_played: %u using elapsed time (frames_played: %u device_frames: %u)", ms_played, status.frames_played, status.device_frames);
+	} else {
+		LOG_SDEBUG("ms_played: 0");
+		ms_played = 0;
+	}
+	
+	memset(&pkt, 0, sizeof(struct STAT_packet));
+	memcpy(&pkt.opcode, "STAT", 4);
+	pkt.length = htonl(sizeof(struct STAT_packet) - 8);
+	memcpy(&pkt.event, event, 4);
+	// num_crlf
+	// mas_initialized; mas_mode;
+	packN(&pkt.stream_buffer_fullness, status.stream_full);
+	packN(&pkt.stream_buffer_size, status.stream_size);
+	packN(&pkt.bytes_received_H, (u64_t)status.stream_bytes >> 32);
+	packN(&pkt.bytes_received_L, (u64_t)status.stream_bytes & 0xffffffff);
+	pkt.signal_strength = 0xffff;
+	packN(&pkt.jiffies, now);
+	packN(&pkt.output_buffer_size, status.output_size);
+	packN(&pkt.output_buffer_fullness, status.output_full);
+	packN(&pkt.elapsed_seconds, ms_played / 1000);
+	// voltage;
+	packN(&pkt.elapsed_milliseconds, ms_played);
+	pkt.server_timestamp = server_timestamp; // keep this is server format - don't unpack/pack
+	// error_code;
+
+	LOG_DEBUG("STAT: %s", event);
+
+	if (loglevel == lSDEBUG) {
+		LOG_SDEBUG("received bytesL: %u streambuf: %u outputbuf: %u calc elapsed: %u real elapsed: %u (diff: %d) device: %u delay: %d",
+				   (u32_t)status.stream_bytes, status.stream_full, status.output_full, ms_played, now - status.stream_start,
+				   ms_played - now + status.stream_start, status.device_frames * 1000 / status.current_sample_rate, now - status.updated);
+	}
+
+	send_packet((u8_t *)&pkt, sizeof(pkt));
+}
+
+static void sendDSCO(disconnect_code disconnect) {
+	struct DSCO_packet pkt;
+
+	memset(&pkt, 0, sizeof(pkt));
+	memcpy(&pkt.opcode, "DSCO", 4);
+	pkt.length = htonl(sizeof(pkt) - 8);
+	pkt.reason = disconnect & 0xFF;
+
+	LOG_DEBUG("DSCO: %d", disconnect);
+
+	send_packet((u8_t *)&pkt, sizeof(pkt));
+}
+
+static void sendRESP(const char *header, size_t len) {
+	struct RESP_header pkt_header;
+
+	memset(&pkt_header, 0, sizeof(pkt_header));
+	memcpy(&pkt_header.opcode, "RESP", 4);
+	pkt_header.length = htonl(sizeof(pkt_header) + len - 8);
+
+	LOG_DEBUG("RESP");
+
+	send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
+	send_packet((u8_t *)header, len);
+}
+
+static void sendMETA(const char *meta, size_t len) {
+	struct META_header pkt_header;
+
+	memset(&pkt_header, 0, sizeof(pkt_header));
+	memcpy(&pkt_header.opcode, "META", 4);
+	pkt_header.length = htonl(sizeof(pkt_header) + len - 8);
+
+	LOG_DEBUG("META");
+
+	send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
+	send_packet((u8_t *)meta, len);
+}
+
+static void sendSETDName(const char *name) {
+	struct SETD_header pkt_header;
+
+	memset(&pkt_header, 0, sizeof(pkt_header));
+	memcpy(&pkt_header.opcode, "SETD", 4);
+
+	pkt_header.id = 0; // id 0 is playername S:P:Squeezebox2
+	pkt_header.length = htonl(sizeof(pkt_header) + strlen(name) + 1 - 8);
+
+	LOG_DEBUG("set playername: %s", name);
+
+	send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
+	send_packet((u8_t *)name, strlen(name) + 1);
+}
+
+#if IR
+void sendIR(u32_t code, u32_t ts) {
+	struct IR_packet pkt;
+
+	memset(&pkt, 0, sizeof(pkt));
+	memcpy(&pkt.opcode, "IR  ", 4);
+	pkt.length = htonl(sizeof(pkt) - 8);
+
+	packN(&pkt.jiffies, ts);
+	pkt.ir_code = htonl(code);
+
+	LOG_DEBUG("IR: ir code: 0x%x ts: %u", code, ts);
+
+	send_packet((u8_t *)&pkt, sizeof(pkt));
+}
+#endif
+
+static void process_strm(u8_t *pkt, int len) {
+	struct strm_packet *strm = (struct strm_packet *)pkt;
+
+	LOG_DEBUG("strm command %c", strm->command);
+
+	switch(strm->command) {
+	case 't':
+		sendSTAT("STMt", strm->replay_gain); // STMt replay_gain is no longer used to track latency, but support it
+		break;
+	case 'q':
+		decode_flush();
+		output_flush();
+		status.frames_played = 0;
+		stream_disconnect();
+		sendSTAT("STMf", 0);
+		buf_flush(streambuf);
+		break;
+	case 'f':
+		decode_flush();
+		output_flush();
+		status.frames_played = 0;
+		if (stream_disconnect()) {
+			sendSTAT("STMf", 0);
+		}
+		buf_flush(streambuf);
+		break;
+	case 'p':
+		{
+			unsigned interval = unpackN(&strm->replay_gain);
+			LOCK_O;
+			output.pause_frames = interval * status.current_sample_rate / 1000;
+			if (interval) {
+				output.state = OUTPUT_PAUSE_FRAMES;
+			} else {
+				output.state = OUTPUT_STOPPED;
+				output.stop_time = gettime_ms();
+			}
+			UNLOCK_O;
+			if (!interval) sendSTAT("STMp", 0);
+			LOG_DEBUG("pause interval: %u", interval);
+		}
+		break;
+	case 'a':
+		{
+			unsigned interval = unpackN(&strm->replay_gain);
+			LOCK_O;
+			output.skip_frames = interval * status.current_sample_rate / 1000;
+			output.state = OUTPUT_SKIP_FRAMES;				
+			UNLOCK_O;
+			LOG_DEBUG("skip ahead interval: %u", interval);
+		}
+		break;
+	case 'u':
+		{
+			unsigned jiffies = unpackN(&strm->replay_gain);
+			LOCK_O;
+			output.state = jiffies ? OUTPUT_START_AT : OUTPUT_RUNNING;
+			output.start_at = jiffies;
+#ifdef STATUSHACK
+			status.frames_played = output.frames_played;
+#endif
+			UNLOCK_O;
+
+			LOG_DEBUG("unpause at: %u now: %u", jiffies, gettime_ms());
+			sendSTAT("STMr", 0);
+		}
+		break;
+	case 's':
+		{
+			unsigned header_len = len - sizeof(struct strm_packet);
+			char *header = (char *)(pkt + sizeof(struct strm_packet));
+			in_addr_t ip = (in_addr_t)strm->server_ip; // keep in network byte order
+			u16_t port = strm->server_port; // keep in network byte order
+			if (ip == 0) ip = slimproto_ip; 
+
+			LOG_DEBUG("strm s autostart: %c transition period: %u transition type: %u codec: %c", 
+					  strm->autostart, strm->transition_period, strm->transition_type - '0', strm->format);
+			
+			autostart = strm->autostart - '0';
+
+			sendSTAT("STMf", 0);
+			if (header_len > MAX_HEADER -1) {
+				LOG_WARN("header too long: %u", header_len);
+				break;
+			}
+			if (strm->format != '?') {
+				codec_open(strm->format, strm->pcm_sample_size, strm->pcm_sample_rate, strm->pcm_channels, strm->pcm_endianness);
+			} else if (autostart >= 2) {
+				// extension to slimproto to allow server to detect codec from response header and send back in codc message
+				LOG_DEBUG("streaming unknown codec");
+			} else {
+				LOG_WARN("unknown codec requires autostart >= 2");
+				break;
+			}
+			if (ip == LOCAL_PLAYER_IP && port == LOCAL_PLAYER_PORT) {
+				// extension to slimproto for LocalPlayer - header is filename not http header, don't expect cont
+				stream_file(header, header_len, strm->threshold * 1024);
+				autostart -= 2;
+			} else {
+				stream_sock(ip, port, header, header_len, strm->threshold * 1024, autostart >= 2);
+			}
+			sendSTAT("STMc", 0);
+			sentSTMu = sentSTMo = sentSTMl = false;
+			LOCK_O;
+			output.threshold = strm->output_threshold;
+			output.next_replay_gain = unpackN(&strm->replay_gain);
+			output.fade_mode = strm->transition_type - '0';
+			output.fade_secs = strm->transition_period;
+			output.invert    = (strm->flags & 0x03) == 0x03;
+			LOG_DEBUG("set fade mode: %u", output.fade_mode);
+			UNLOCK_O;
+		}
+		break;
+	default:
+		LOG_WARN("unhandled strm %c", strm->command);
+		break;
+	}
+}
+
+static void process_cont(u8_t *pkt, int len) {
+	struct cont_packet *cont = (struct cont_packet *)pkt;
+	cont->metaint = unpackN(&cont->metaint);
+
+	LOG_DEBUG("cont metaint: %u loop: %u", cont->metaint, cont->loop);
+
+	if (autostart > 1) {
+		autostart -= 2;
+		LOCK_S;
+		if (stream.state == STREAMING_WAIT) {
+			stream.state = STREAMING_BUFFERING;
+			stream.meta_interval = stream.meta_next = cont->metaint;
+		}
+		UNLOCK_S;
+		wake_controller();
+	}
+}
+
+static void process_codc(u8_t *pkt, int len) {
+	struct codc_packet *codc = (struct codc_packet *)pkt;
+
+	LOG_DEBUG("codc: %c", codc->format);
+	codec_open(codc->format, codc->pcm_sample_size, codc->pcm_sample_rate, codc->pcm_channels, codc->pcm_endianness);
+}
+
+static void process_aude(u8_t *pkt, int len) {
+	struct aude_packet *aude = (struct aude_packet *)pkt;
+
+	LOG_DEBUG("enable spdif: %d dac: %d", aude->enable_spdif, aude->enable_dac);
+
+	LOCK_O;
+	if (!aude->enable_spdif && output.state != OUTPUT_OFF) {
+		output.state = OUTPUT_OFF;
+	}
+	if (aude->enable_spdif && output.state == OUTPUT_OFF && !output.idle_to) {
+		output.state = OUTPUT_STOPPED;
+		output.stop_time = gettime_ms();
+	}
+	UNLOCK_O;
+}
+
+static void process_audg(u8_t *pkt, int len) {
+	struct audg_packet *audg = (struct audg_packet *)pkt;
+	audg->gainL = unpackN(&audg->gainL);
+	audg->gainR = unpackN(&audg->gainR);
+
+	LOG_DEBUG("audg gainL: %u gainR: %u adjust: %u", audg->gainL, audg->gainR, audg->adjust);
+
+	set_volume(audg->adjust ? audg->gainL : FIXED_ONE, audg->adjust ? audg->gainR : FIXED_ONE);
+}
+
+static void process_setd(u8_t *pkt, int len) {
+	struct setd_packet *setd = (struct setd_packet *)pkt;
+
+	// handle player name query and change
+	if (setd->id == 0) {
+		if (len == 5) {
+			if (strlen(player_name)) {
+				sendSETDName(player_name);
+			}
+		} else if (len > 5) {
+			strncpy(player_name, setd->data, PLAYER_NAME_LEN);
+			player_name[PLAYER_NAME_LEN] = '\0';
+			LOG_INFO("set name: %s", setd->data);
+			// confirm change to server
+			sendSETDName(setd->data);
+			// write name to name_file if -N option set
+			if (name_file) {
+				FILE *fp = fopen(name_file, "w");
+				if (fp) {
+					LOG_INFO("storing name in %s", name_file);
+					fputs(player_name, fp);
+					fclose(fp);
+				} else {
+					LOG_WARN("unable to store new name in %s", name_file);
+				}
+			}
+		}
+	}
+}
+
+#define SYNC_CAP ",SyncgroupID="
+#define SYNC_CAP_LEN 13
+
+static void process_serv(u8_t *pkt, int len) {
+	struct serv_packet *serv = (struct serv_packet *)pkt;
+
+	unsigned slimproto_port = 0;
+	char squeezeserver[] = SQUEEZENETWORK;
+	
+	if(pkt[4] == 0 && pkt[5] == 0 && pkt[6] == 0 && pkt[7] == 1) {
+		server_addr(squeezeserver, &new_server, &slimproto_port);
+	} else {
+		new_server = serv->server_ip;
+	}
+
+	LOG_INFO("switch server");
+
+	if (len - sizeof(struct serv_packet) == 10) {
+		if (!new_server_cap) {
+			new_server_cap = malloc(SYNC_CAP_LEN + 10 + 1);
+		}
+		new_server_cap[0] = '\0';
+		strcat(new_server_cap, SYNC_CAP);
+		strncat(new_server_cap, (const char *)(pkt + sizeof(struct serv_packet)), 10);
+	} else {
+		if (new_server_cap) {
+			free(new_server_cap);
+			new_server_cap = NULL;
+		}
+	}		
+}
+
+struct handler {
+	char opcode[5];
+	void (*handler)(u8_t *, int);
+};
+
+static struct handler handlers[] = {
+	{ "strm", process_strm },
+	{ "cont", process_cont },
+	{ "codc", process_codc },
+	{ "aude", process_aude },
+	{ "audg", process_audg },
+	{ "setd", process_setd },
+	{ "serv", process_serv },
+	{ "",     NULL  },
+};
+
+static void process(u8_t *pack, int len) {
+	struct handler *h = handlers;
+	while (h->handler && strncmp((char *)pack, h->opcode, 4)) { h++; }
+
+	if (h->handler) {
+		LOG_DEBUG("%s", h->opcode);
+		h->handler(pack, len);
+	} else {
+		pack[4] = '\0';
+		LOG_WARN("unhandled %s", (char *)pack);
+	}
+}
+
+static bool running;
+
+static void slimproto_run() {
+	static u8_t buffer[MAXBUF];
+	int  expect = 0;
+	int  got    = 0;
+	u32_t now;
+	static u32_t last = 0;
+	event_handle ehandles[2];
+	int timeouts = 0;
+
+	set_readwake_handles(ehandles, sock, wake_e);
+
+	while (running && !new_server) {
+
+		bool wake = false;
+		event_type ev;
+
+		if ((ev = wait_readwake(ehandles, 1000)) != EVENT_TIMEOUT) {
+	
+			if (ev == EVENT_READ) {
+
+				if (expect > 0) {
+					int n = recv(sock, buffer + got, expect, 0);
+					if (n <= 0) {
+						if (n < 0 && last_error() == ERROR_WOULDBLOCK) {
+							continue;
+						}
+						LOG_INFO("error reading from socket: %s", n ? strerror(last_error()) : "closed");
+						return;
+					}
+					expect -= n;
+					got += n;
+					if (expect == 0) {
+						process(buffer, got);
+						got = 0;
+					}
+				} else if (expect == 0) {
+					int n = recv(sock, buffer + got, 2 - got, 0);
+					if (n <= 0) {
+						if (n < 0 && last_error() == ERROR_WOULDBLOCK) {
+							continue;
+						}
+						LOG_INFO("error reading from socket: %s", n ? strerror(last_error()) : "closed");
+						return;
+					}
+					got += n;
+					if (got == 2) {
+						expect = buffer[0] << 8 | buffer[1]; // length pack 'n'
+						got = 0;
+						if (expect > MAXBUF) {
+							LOG_ERROR("FATAL: slimproto packet too big: %d > %d", expect, MAXBUF);
+							return;
+						}
+					}
+				} else {
+					LOG_ERROR("FATAL: negative expect");
+					return;
+				}
+
+			}
+
+			if (ev == EVENT_WAKE) {
+				wake = true;
+			}
+
+			timeouts = 0;
+
+		} else if (++timeouts > 35) {
+
+			// expect message from server every 5 seconds, but 30 seconds on mysb.com so timeout after 35 seconds
+			LOG_INFO("No messages from server - connection dead");
+			return;
+		}
+
+		// update playback state when woken or every 100ms
+		now = gettime_ms();
+
+		if (wake || now - last > 100 || last > now) {
+			bool _sendSTMs = false;
+			bool _sendDSCO = false;
+			bool _sendRESP = false;
+			bool _sendMETA = false;
+			bool _sendSTMd = false;
+			bool _sendSTMt = false;
+			bool _sendSTMl = false;
+			bool _sendSTMu = false;
+			bool _sendSTMo = false;
+			bool _sendSTMn = false;
+			bool _stream_disconnect = false;
+			bool _start_output = false;
+			decode_state _decode_state;
+			disconnect_code disconnect_code;
+			static char header[MAX_HEADER];
+			size_t header_len = 0;
+#if IR
+			bool _sendIR   = false;
+			u32_t ir_code, ir_ts;
+#endif
+			last = now;
+
+
+			LOCK_S;
+			status.stream_full = _buf_used(streambuf);
+			status.stream_size = streambuf->size;
+			status.stream_bytes = stream.bytes;
+			status.stream_state = stream.state;
+						
+			if (stream.state == DISCONNECT) {
+				disconnect_code = stream.disconnect;
+				stream.state = STOPPED;
+				_sendDSCO = true;
+			}
+			if (!stream.sent_headers && 
+				(stream.state == STREAMING_HTTP || stream.state == STREAMING_WAIT || stream.state == STREAMING_BUFFERING)) {
+				header_len = stream.header_len;
+				memcpy(header, stream.header, header_len);
+				_sendRESP = true;
+				stream.sent_headers = true;
+			}
+			if (stream.meta_send) {
+				header_len = stream.header_len;
+				memcpy(header, stream.header, header_len);
+				_sendMETA = true;
+				stream.meta_send = false;
+			}
+			UNLOCK_S;
+
+			LOCK_D;
+			if ((status.stream_state == STREAMING_HTTP || status.stream_state == STREAMING_FILE ||
+				(status.stream_state == DISCONNECT && stream.disconnect == DISCONNECT_OK)) &&
+				!sentSTMl && decode.state == DECODE_READY) {
+				if (autostart == 0) {
+					decode.state = DECODE_RUNNING;
+					_sendSTMl = true;
+					sentSTMl = true;
+				} else if (autostart == 1) {
+					decode.state = DECODE_RUNNING;
+					_start_output = true;
+				}
+				// autostart 2 and 3 require cont to be received first
+			}
+			if (decode.state == DECODE_COMPLETE || decode.state == DECODE_ERROR) {
+				if (decode.state == DECODE_COMPLETE) _sendSTMd = true;
+				if (decode.state == DECODE_ERROR)    _sendSTMn = true;
+				decode.state = DECODE_STOPPED;
+				if (status.stream_state == STREAMING_HTTP || status.stream_state == STREAMING_FILE) {
+					_stream_disconnect = true;
+				}
+			}
+			_decode_state = decode.state;
+			UNLOCK_D;
+			
+			LOCK_O;
+			status.output_full = _buf_used(outputbuf);
+			status.output_size = outputbuf->size;
+			status.frames_played = output.frames_played_dmp;
+			status.current_sample_rate = output.current_sample_rate;
+			status.updated = output.updated;
+			status.device_frames = output.device_frames;
+			
+			if (output.track_started) {
+				_sendSTMs = true;
+				output.track_started = false;
+				status.stream_start = output.track_start_time;
+#ifdef STATUSHACK 
+				status.frames_played = output.frames_played;
+#endif
+			}
+#if PORTAUDIO
+			if (output.pa_reopen) {
+				_pa_open();
+				output.pa_reopen = false;
+			}
+#endif
+			if (_start_output && (output.state == OUTPUT_STOPPED || output.state == OUTPUT_OFF)) {
+				output.state = OUTPUT_BUFFER;
+			}
+			if (output.state == OUTPUT_RUNNING && !sentSTMu && status.output_full == 0 && status.stream_state <= DISCONNECT &&
+				_decode_state == DECODE_STOPPED) {
+
+				_sendSTMu = true;
+				sentSTMu = true;
+				LOG_DEBUG("output underrun");
+				output.state = OUTPUT_STOPPED;
+				output.stop_time = now;
+			}
+			if (output.state == OUTPUT_RUNNING && !sentSTMo && status.output_full == 0 && status.stream_state == STREAMING_HTTP) {
+
+				_sendSTMo = true;
+				sentSTMo = true;
+			}
+			if (output.state == OUTPUT_STOPPED && output.idle_to && (now - output.stop_time > output.idle_to)) {
+				output.state = OUTPUT_OFF;
+				LOG_DEBUG("output timeout");
+			}
+			if (output.state == OUTPUT_RUNNING && now - status.last > 1000) {
+				_sendSTMt = true;
+				status.last = now;
+			}
+			UNLOCK_O;
+
+#if IR
+			LOCK_I;
+			if (ir.code) {
+				_sendIR = true;
+				ir_code = ir.code;
+				ir_ts   = ir.ts;
+				ir.code = 0;
+			}
+			UNLOCK_I;
+#endif
+
+			if (_stream_disconnect) stream_disconnect();
+
+			// send packets once locks released as packet sending can block
+			if (_sendDSCO) sendDSCO(disconnect_code);
+			if (_sendSTMs) sendSTAT("STMs", 0);
+			if (_sendSTMd) sendSTAT("STMd", 0);
+			if (_sendSTMt) sendSTAT("STMt", 0);
+			if (_sendSTMl) sendSTAT("STMl", 0);
+			if (_sendSTMu) sendSTAT("STMu", 0);
+			if (_sendSTMo) sendSTAT("STMo", 0);
+			if (_sendSTMn) sendSTAT("STMn", 0);
+			if (_sendRESP) sendRESP(header, header_len);
+			if (_sendMETA) sendMETA(header, header_len);
+#if IR
+			if (_sendIR)   sendIR(ir_code, ir_ts);
+#endif
+		}
+	}
+}
+
+// called from other threads to wake state machine above
+void wake_controller(void) {
+	wake_signal(wake_e);
+}
+
+in_addr_t discover_server(char *default_server) {
+	struct sockaddr_in d;
+	struct sockaddr_in s;
+	char *buf;
+	struct pollfd pollinfo;
+	unsigned port;
+
+	int disc_sock = socket(AF_INET, SOCK_DGRAM, 0);
+
+	socklen_t enable = 1;
+	setsockopt(disc_sock, SOL_SOCKET, SO_BROADCAST, (const void *)&enable, sizeof(enable));
+
+	buf = "e";
+
+	memset(&d, 0, sizeof(d));
+	d.sin_family = AF_INET;
+	d.sin_port = htons(PORT);
+	d.sin_addr.s_addr = htonl(INADDR_BROADCAST);
+
+	pollinfo.fd = disc_sock;
+	pollinfo.events = POLLIN;
+
+	do {
+
+		LOG_INFO("sending discovery");
+		memset(&s, 0, sizeof(s));
+
+		if (sendto(disc_sock, buf, 1, 0, (struct sockaddr *)&d, sizeof(d)) < 0) {
+			LOG_INFO("error sending disovery");
+		}
+
+		if (poll(&pollinfo, 1, 5000) == 1) {
+			char readbuf[10];
+			socklen_t slen = sizeof(s);
+			recvfrom(disc_sock, readbuf, 10, 0, (struct sockaddr *)&s, &slen);
+			LOG_INFO("got response from: %s:%d", inet_ntoa(s.sin_addr), ntohs(s.sin_port));
+		}
+
+		if (default_server) {
+			server_addr(default_server, &s.sin_addr.s_addr, &port);
+		}
+
+	} while (s.sin_addr.s_addr == 0 && running);
+
+	closesocket(disc_sock);
+
+	return s.sin_addr.s_addr;
+}
+
+#define FIXED_CAP_LEN 256
+#define VAR_CAP_LEN   128
+
+void slimproto(log_level level, char *server, u8_t mac[6], const char *name, const char *namefile, const char *modelname, int maxSampleRate) {
+	struct sockaddr_in serv_addr;
+	static char fixed_cap[FIXED_CAP_LEN], var_cap[VAR_CAP_LEN] = "";
+	bool reconnect = false;
+	unsigned failed_connect = 0;
+	unsigned slimproto_port = 0;
+	in_addr_t previous_server = 0;
+	int i;
+
+	memset(&status, 0, sizeof(status));
+
+	wake_create(wake_e);
+
+	loglevel = level;
+	running = true;
+
+	if (server) {
+		server_addr(server, &slimproto_ip, &slimproto_port);
+	}
+
+	if (!slimproto_ip) {
+		slimproto_ip = discover_server(server);
+	}
+
+	if (!slimproto_port) {
+		slimproto_port = PORT;
+	}
+
+	if (name) {
+		strncpy(player_name, name, PLAYER_NAME_LEN);
+		player_name[PLAYER_NAME_LEN] = '\0';
+	}
+
+	if (namefile) {
+		FILE *fp;
+		name_file = namefile;
+		fp = fopen(namefile, "r");
+		if (fp) {
+			if (!fgets(player_name, PLAYER_NAME_LEN, fp)) {
+				player_name[PLAYER_NAME_LEN] = '\0';
+			} else {
+				// strip any \n from fgets response
+				int len = strlen(player_name);
+				if (len > 0 && player_name[len - 1] == '\n') {
+					player_name[len - 1] = '\0';
+				}
+				LOG_INFO("retrieved name %s from %s", player_name, name_file);
+			}
+			fclose(fp);
+		}
+	}
+
+	if (!running) return;
+
+	LOCK_O;
+	snprintf(fixed_cap, FIXED_CAP_LEN, ",ModelName=%s,MaxSampleRate=%u", modelname ? modelname : MODEL_NAME_STRING,
+			 ((maxSampleRate > 0) ? maxSampleRate : output.supported_rates[0]));
+	
+	for (i = 0; i < MAX_CODECS; i++) {
+		if (codecs[i] && codecs[i]->id && strlen(fixed_cap) < FIXED_CAP_LEN - 10) {
+			strcat(fixed_cap, ",");
+			strcat(fixed_cap, codecs[i]->types);
+		}
+	}
+	UNLOCK_O;
+
+	memset(&serv_addr, 0, sizeof(serv_addr));
+	serv_addr.sin_family = AF_INET;
+	serv_addr.sin_addr.s_addr = slimproto_ip;
+	serv_addr.sin_port = htons(slimproto_port);
+
+	LOG_INFO("connecting to %s:%d", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port));
+
+	new_server = 0;
+
+	while (running) {
+
+		if (new_server) {
+			previous_server = slimproto_ip;
+			slimproto_ip = serv_addr.sin_addr.s_addr = new_server;
+			LOG_INFO("switching server to %s:%d", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port));
+			new_server = 0;
+			reconnect = false;
+		}
+
+		sock = socket(AF_INET, SOCK_STREAM, 0);
+
+		set_nonblock(sock);
+		set_nosigpipe(sock);
+
+		if (connect_timeout(sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr), 5) != 0) {
+
+			if (previous_server) {
+				slimproto_ip = serv_addr.sin_addr.s_addr = previous_server;
+				LOG_INFO("new server not reachable, reverting to previous server %s:%d", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port));
+			} else {
+				LOG_INFO("unable to connect to server %u", failed_connect);
+				sleep(5);
+			}
+
+			// rediscover server if it was not set at startup
+			if (!server && ++failed_connect > 5) {
+				slimproto_ip = serv_addr.sin_addr.s_addr = discover_server(NULL);
+			}
+
+		} else {
+
+			struct sockaddr_in our_addr;
+			socklen_t len;
+
+			LOG_INFO("connected");
+
+			var_cap[0] = '\0';
+			failed_connect = 0;
+
+			// check if this is a local player now we are connected & signal to server via 'loc' format
+			// this requires LocalPlayer server plugin to enable direct file access
+			len = sizeof(our_addr);
+			getsockname(sock, (struct sockaddr *) &our_addr, &len);
+
+			if (our_addr.sin_addr.s_addr == serv_addr.sin_addr.s_addr) {
+				LOG_INFO("local player");
+				strcat(var_cap, ",loc");
+			}
+
+			// add on any capablity to be sent to the new server
+			if (new_server_cap) {
+				strcat(var_cap, new_server_cap);
+				free(new_server_cap);
+				new_server_cap = NULL;
+			}
+
+			sendHELO(reconnect, fixed_cap, var_cap, mac);
+
+			slimproto_run();
+
+			if (!reconnect) {
+				reconnect = true;
+			}
+
+			usleep(100000);
+		}
+
+		previous_server = 0;
+
+		closesocket(sock);
+	}
+}
+
+void slimproto_stop(void) {
+	LOG_INFO("slimproto stop");
+	running = false;
+}

+ 185 - 0
slimproto.h

@@ -0,0 +1,185 @@
+/* 
+ *  Squeezelite - lightweight headless squeezebox emulator
+ *
+ *  (c) Adrian Smith 2012-2015, triode1@btinternet.com
+ *      Ralph Irving 2015-2017, ralph_irving@hotmail.com
+ *  
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+// packet formats for slimproto
+
+#ifndef SUN
+#pragma pack(push, 1)
+#else
+#pragma pack(1)
+#endif
+
+// from S:N:Slimproto _hello_handler
+struct HELO_packet {
+	char  opcode[4];
+	u32_t length;
+	u8_t  deviceid;
+	u8_t  revision;
+	u8_t  mac[6];
+	u8_t  uuid[16];
+	u16_t wlan_channellist;
+	u32_t bytes_received_H, bytes_received_L;
+	char  lang[2];
+	//	u8_t capabilities[];
+};
+
+// S:N:Slimproto _stat_handler
+struct STAT_packet {
+	char  opcode[4];
+	u32_t length;
+	u32_t event;
+	u8_t  num_crlf;
+	u8_t  mas_initialized;
+	u8_t  mas_mode;
+	u32_t stream_buffer_size;
+	u32_t stream_buffer_fullness;
+	u32_t bytes_received_H;
+	u32_t bytes_received_L;
+	u16_t signal_strength;
+	u32_t jiffies;
+	u32_t output_buffer_size;
+	u32_t output_buffer_fullness;
+	u32_t elapsed_seconds;
+	u16_t voltage;
+	u32_t elapsed_milliseconds;
+	u32_t server_timestamp;
+	u16_t error_code;
+};
+
+// S:N:Slimproto _disco_handler
+struct DSCO_packet {
+	char  opcode[4];
+	u32_t length;
+	u8_t  reason;
+};
+
+// S:N:Slimproto _http_response_handler
+struct RESP_header {
+	char  opcode[4];
+	u32_t length;
+	// char header[] - added in sendRESP
+};
+
+// S:N:Slimproto _http_metadata_handler
+struct META_header {
+	char  opcode[4];
+	u32_t length;
+	// char metadata[]
+};
+
+// S:N:Slimproto _http_setting_handler
+struct SETD_header {
+	char  opcode[4];
+	u32_t length;
+	u8_t  id;
+	// data
+};
+
+#if IR
+struct IR_packet {
+	char  opcode[4];
+	u32_t length;
+	u32_t jiffies;
+	u8_t  format; // ignored by server
+	u8_t  bits;   // ignored by server
+	u32_t ir_code;
+};
+#endif
+
+// from S:P:Squeezebox stream_s
+struct strm_packet {
+	char  opcode[4];
+	char  command;
+	u8_t  autostart;
+	u8_t  format;
+	u8_t  pcm_sample_size;
+	u8_t  pcm_sample_rate;
+	u8_t  pcm_channels;
+	u8_t  pcm_endianness;
+	u8_t  threshold;
+	u8_t  spdif_enable;
+	u8_t  transition_period;
+	u8_t  transition_type;
+	u8_t  flags;
+	u8_t  output_threshold;
+	u8_t  slaves;
+	u32_t replay_gain;
+	u16_t server_port;
+	u32_t server_ip;
+	//char request_string[];
+};
+
+// S:P:Squeezebox2
+struct aude_packet {
+	char  opcode[4];
+	u8_t  enable_spdif;
+	u8_t  enable_dac;
+};
+
+// S:P:Squeezebox2
+struct audg_packet {
+	char  opcode[4];
+	u32_t old_gainL;     // unused
+	u32_t old_gainR;     // unused
+	u8_t  adjust;
+	u8_t  preamp;        // unused
+	u32_t gainL;
+	u32_t gainR;
+	// squence ids - unused
+};
+
+// S:P:Squeezebox2
+struct cont_packet {
+	char  opcode[4];
+	u32_t metaint;
+	u8_t  loop;
+	// guids we don't use
+};
+
+// S:C:Commands
+struct serv_packet {
+	char  opcode[4];
+	u32_t server_ip;
+	// possible sync group
+};
+
+// S:P:Squeezebox2
+struct setd_packet {
+	char  opcode[4];
+	u8_t  id;
+	char  data[];
+};
+
+// codec open - this is an extension to slimproto to allow the server to read the header and then return decode params
+struct codc_packet {
+	char  opcode[4];
+	u8_t  format;
+	u8_t  pcm_sample_size;
+	u8_t  pcm_sample_rate;
+	u8_t  pcm_channels;
+	u8_t  pcm_endianness;
+};
+
+#ifndef SUN
+#pragma pack(pop)
+#else
+#pragma pack()
+#endif

+ 786 - 0
squeezelite.h

@@ -0,0 +1,786 @@
+/* 
+ *  Squeezelite - lightweight headless squeezebox emulator
+ *
+ *  (c) Adrian Smith 2012-2015, triode1@btinternet.com
+ *      Ralph Irving 2015-2017, ralph_irving@hotmail.com
+ *  
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Additions (c) Paul Hermann, 2015-2017 under the same license terms
+ *   -Control of Raspberry pi GPIO for amplifier power
+ *   -Launch script on power status change from LMS
+ */
+
+// make may define: PORTAUDIO, SELFPIPE, RESAMPLE, RESAMPLE_MP, VISEXPORT, GPIO, IR, DSD, LINKALL to influence build
+
+#define MAJOR_VERSION "1.9"
+#define MINOR_VERSION "2"
+#define MICRO_VERSION "1145"
+
+#if defined(CUSTOM_VERSION)
+#define VERSION "v" MAJOR_VERSION "." MINOR_VERSION "-" MICRO_VERSION STR(CUSTOM_VERSION)
+#else
+#define VERSION "v" MAJOR_VERSION "." MINOR_VERSION "-" MICRO_VERSION
+#endif
+
+
+#if !defined(MODEL_NAME)
+#define MODEL_NAME SqueezeLite
+#endif
+
+#define QUOTE(name) #name
+#define STR(macro)  QUOTE(macro)
+#define MODEL_NAME_STRING STR(MODEL_NAME)
+
+// build detection
+#if defined(linux)
+#define LINUX     1
+#define OSX       0
+#define WIN       0
+#define FREEBSD   0
+#elif defined (__APPLE__)
+#define LINUX     0
+#define OSX       1
+#define WIN       0
+#define FREEBSD   0
+#elif defined (_MSC_VER)
+#define LINUX     0
+#define OSX       0
+#define WIN       1
+#define FREEBSD   0
+#elif defined(__FreeBSD__)
+#define LINUX     0
+#define OSX       0
+#define WIN       0
+#define FREEBSD   1
+#elif defined (__sun)
+#define SUN       1
+#define LINUX     1
+#define PORTAUDIO 1
+#define PA18API   1
+#define OSX       0
+#define WIN       0
+#elif defined (POSIX)
+#undef POSIX
+#define POSIX 	  1
+#else
+#error unknown target
+#endif
+
+#if defined(DACAUDIO)
+#undef DACAUDIO
+#define DACAUDIO  1
+#elif LINUX && !defined(PORTAUDIO)
+#define ALSA      1
+#define PORTAUDIO 0
+#else
+#define ALSA      0
+#define PORTAUDIO 1
+#endif
+
+#if !defined(LOOPBACK)
+#if SUN
+#define EVENTFD   0
+#define WINEVENT  0
+#define SELFPIPE  1
+#elif LINUX && !defined(SELFPIPE)
+#define EVENTFD   1
+#define SELFPIPE  0
+#define WINEVENT  0
+#endif
+#if (LINUX && !EVENTFD) || OSX || FREEBSD
+#define EVENTFD   0
+#define SELFPIPE  1
+#define WINEVENT  0
+#endif
+#if WIN
+#define EVENTFD   0
+#define SELFPIPE  0
+#define WINEVENT  1
+#endif
+#else
+#define EVENTFD   0
+#define SELFPIPE  0
+#define WINEVENT  0
+#undef LOOPBACK
+#define LOOPBACK  1
+#endif
+
+#if defined(RESAMPLE) || defined(RESAMPLE_MP)
+#undef  RESAMPLE
+#define RESAMPLE  1 // resampling
+#define PROCESS   1 // any sample processing (only resampling at present)
+#else
+#define RESAMPLE  0
+#define PROCESS   0
+#endif
+#if defined(RESAMPLE_MP)
+#undef RESAMPLE_MP
+#define RESAMPLE_MP 1
+#else
+#define RESAMPLE_MP 0
+#endif
+
+#if defined(FFMPEG)
+#undef FFMPEG
+#define FFMPEG    1
+#else
+#define FFMPEG    0
+#endif
+
+#if (LINUX || OSX) && defined(VISEXPORT)
+#undef VISEXPORT
+#define VISEXPORT 1 // visulizer export support uses linux shared memory
+#else
+#define VISEXPORT 0
+#endif
+
+#if LINUX && defined(IR)
+#undef IR
+#define IR 1
+#else
+#define IR 0
+#endif
+
+#if defined(DSD)
+#undef DSD
+#define DSD       1
+#define IF_DSD(x) { x }
+#else
+#undef DSD
+#define DSD       0
+#define IF_DSD(x)
+#endif
+
+#if defined(LINKALL)
+#undef LINKALL
+#define LINKALL   1 // link all libraries at build time - requires all to be available at run time
+#else
+#define LINKALL   0
+#endif
+
+#if defined (USE_SSL)
+#undef USE_SSL
+#define USE_SSL 1
+#else
+#define USE_SSL 0
+#endif
+
+
+#if !LINKALL
+
+// dynamically loaded libraries at run time
+
+#if LINUX
+#define LIBFLAC "libFLAC.so.8"
+#define LIBMAD  "libmad.so.0"
+#define LIBMPG "libmpg123.so.0"
+#define LIBVORBIS "libvorbisfile.so.3"
+#define LIBTREMOR "libvorbisidec.so.1"
+#define LIBFAAD "libfaad.so.2"
+#define LIBAVUTIL   "libavutil.so.%d"
+#define LIBAVCODEC  "libavcodec.so.%d"
+#define LIBAVFORMAT "libavformat.so.%d"
+#define LIBSOXR "libsoxr.so.0"
+#define LIBLIRC "liblirc_client.so.0"
+#endif
+
+#if OSX
+#define LIBFLAC "libFLAC.8.dylib"
+#define LIBMAD  "libmad.0.dylib"
+#define LIBMPG "libmpg123.0.dylib"
+#define LIBVORBIS "libvorbisfile.3.dylib"
+#define LIBTREMOR "libvorbisidec.1.dylib"
+#define LIBFAAD "libfaad.2.dylib"
+#define LIBAVUTIL   "libavutil.%d.dylib"
+#define LIBAVCODEC  "libavcodec.%d.dylib"
+#define LIBAVFORMAT "libavformat.%d.dylib"
+#define LIBSOXR "libsoxr.0.dylib"
+#endif
+
+#if WIN
+#define LIBFLAC "libFLAC.dll"
+#define LIBMAD  "libmad-0.dll"
+#define LIBMPG "libmpg123-0.dll"
+#define LIBVORBIS "libvorbisfile.dll"
+#define LIBTREMOR "libvorbisidec.dll"
+#define LIBFAAD "libfaad2.dll"
+#define LIBAVUTIL   "avutil-%d.dll"
+#define LIBAVCODEC  "avcodec-%d.dll"
+#define LIBAVFORMAT "avformat-%d.dll"
+#define LIBSOXR "libsoxr.dll"
+#endif
+
+#if FREEBSD
+#define LIBFLAC "libFLAC.so.11"
+#define LIBMAD  "libmad.so.2"
+#define LIBMPG "libmpg123.so.0"
+#define LIBVORBIS "libvorbisfile.so.6"
+#define LIBTREMOR "libvorbisidec.so.1"
+#define LIBFAAD "libfaad.so.2"
+#define LIBAVUTIL   "libavutil.so.%d"
+#define LIBAVCODEC  "libavcodec.so.%d"
+#define LIBAVFORMAT "libavformat.so.%d"
+#endif
+
+#endif // !LINKALL
+
+// config options
+#define STREAMBUF_SIZE (2 * 1024 * 1024)
+#define OUTPUTBUF_SIZE (44100 * 8 * 10)
+#define OUTPUTBUF_SIZE_CROSSFADE (OUTPUTBUF_SIZE * 12 / 10)
+
+#define MAX_HEADER 4096 // do not reduce as icy-meta max is 4080
+
+#if ALSA
+#define ALSA_BUFFER_TIME  40
+#define ALSA_PERIOD_COUNT 4
+#define OUTPUT_RT_PRIORITY 45
+#endif
+
+#define SL_LITTLE_ENDIAN (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+
+#if SUN || OSXPPC
+#undef SL_LITTLE_ENDIAN
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/types.h>
+
+#if LINUX || OSX || FREEBSD || POSIX
+#include <unistd.h>
+#include <stdbool.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <poll.h>
+#if !LINKALL
+#include <dlfcn.h>
+#endif
+#include <pthread.h>
+#include <signal.h>
+#if SUN
+#include <sys/types.h>
+#endif /* SUN */
+
+#define STREAM_THREAD_STACK_SIZE  8 * 1024
+#define DECODE_THREAD_STACK_SIZE 8 * 1024
+#define OUTPUT_THREAD_STACK_SIZE  8 * 1024
+#define IR_THREAD_STACK_SIZE      8 * 1024
+#if !OSX
+#define thread_t pthread_t;
+#endif
+#define closesocket(s) close(s)
+#define last_error() errno
+#define ERROR_WOULDBLOCK EWOULDBLOCK
+
+#ifdef SUN
+typedef uint8_t  u8_t;
+typedef uint16_t u16_t;
+typedef uint32_t u32_t;
+typedef uint64_t u64_t;
+#elif POSIX
+typedef unsigned long long u64_t;
+#else
+typedef u_int8_t  u8_t;
+typedef u_int16_t u16_t;
+typedef u_int32_t u32_t;
+typedef u_int64_t u64_t;
+#endif /* SUN */
+typedef int16_t   s16_t;
+typedef int32_t   s32_t;
+typedef int64_t   s64_t;
+
+#define mutex_type pthread_mutex_t
+#define mutex_create(m) pthread_mutex_init(&m, NULL)
+#if POSIX
+#define mutex_create_p(m) mutex_create(m)
+#else
+#define mutex_create_p(m) pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT); pthread_mutex_init(&m, &attr); pthread_mutexattr_destroy(&attr)
+#endif
+#define mutex_lock(m) pthread_mutex_lock(&m)
+#define mutex_unlock(m) pthread_mutex_unlock(&m)
+#define mutex_destroy(m) pthread_mutex_destroy(&m)
+#define thread_type pthread_t
+
+#endif
+
+#if WIN
+
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <io.h>
+
+#define STREAM_THREAD_STACK_SIZE (1024 * 64)
+#define DECODE_THREAD_STACK_SIZE (1024 * 128)
+#define OUTPUT_THREAD_STACK_SIZE (1024 * 64)
+
+typedef unsigned __int8  u8_t;
+typedef unsigned __int16 u16_t;
+typedef unsigned __int32 u32_t;
+typedef unsigned __int64 u64_t;
+typedef __int16 s16_t;
+typedef __int32 s32_t;
+typedef __int64 s64_t;
+
+typedef BOOL bool;
+#define true TRUE
+#define false FALSE
+
+#define inline __inline
+
+#define mutex_type HANDLE
+#define mutex_create(m) m = CreateMutex(NULL, FALSE, NULL)
+#define mutex_create_p mutex_create
+#define mutex_lock(m) WaitForSingleObject(m, INFINITE)
+#define mutex_unlock(m) ReleaseMutex(m)
+#define mutex_destroy(m) CloseHandle(m)
+#define thread_type HANDLE
+
+#define usleep(x) Sleep(x/1000)
+#define sleep(x) Sleep(x*1000)
+#define last_error() WSAGetLastError()
+#define ERROR_WOULDBLOCK WSAEWOULDBLOCK
+#define open _open
+#define read _read
+#define snprintf _snprintf
+
+#define in_addr_t u32_t
+#define socklen_t int
+#define ssize_t int
+
+#define RTLD_NOW 0
+
+#endif
+
+#if !defined(MSG_NOSIGNAL)
+#define MSG_NOSIGNAL 0
+#endif
+
+typedef u32_t frames_t;
+typedef int sockfd;
+
+#if EVENTFD
+#include <sys/eventfd.h>
+#define event_event int
+#define event_handle struct pollfd
+#define wake_create(e) e = eventfd(0, 0)
+#define wake_signal(e) eventfd_write(e, 1)
+#define wake_clear(e) eventfd_t val; eventfd_read(e, &val)
+#define wake_close(e) close(e)
+#endif
+
+#if SELFPIPE
+#define event_handle struct pollfd
+#define event_event struct wake
+#define wake_create(e) pipe(e.fds); set_nonblock(e.fds[0]); set_nonblock(e.fds[1])
+#define wake_signal(e) write(e.fds[1], ".", 1)
+#define wake_clear(e) char c[10]; read(e, &c, 10)
+#define wake_close(e) close(e.fds[0]); close(e.fds[1])
+struct wake { 
+	int fds[2];
+};
+#endif
+
+#if LOOPBACK
+#define event_handle struct pollfd
+#define event_event struct wake
+#define wake_create(e) _wake_create(&e)
+#define wake_signal(e) send(e.fds[1], ".", 1, 0)
+#define wake_clear(e) char c; recv(e, &c, 1, 0)
+#define wake_close(e) closesocket(e.mfds); closesocket(e.fds[0]); closesocket(e.fds[1])
+struct wake {
+	int mfds;
+	int fds[2];
+};
+void _wake_create(event_event*);
+#endif
+
+#if WINEVENT
+#define event_event HANDLE
+#define event_handle HANDLE
+#define wake_create(e) e = CreateEvent(NULL, FALSE, FALSE, NULL)
+#define wake_signal(e) SetEvent(e)
+#define wake_close(e) CloseHandle(e)
+#endif
+
+// printf/scanf formats for u64_t
+#if (LINUX && __WORDSIZE == 64) || (FREEBSD && __LP64__)
+#define FMT_u64 "%lu"
+#define FMT_x64 "%lx"
+#elif __GLIBC_HAVE_LONG_LONG || defined __GNUC__ || WIN || SUN
+#define FMT_u64 "%llu"
+#define FMT_x64 "%llx"
+#else
+#error can not support u64_t
+#endif
+
+#define MAX_SILENCE_FRAMES 2048
+
+#define FIXED_ONE 0x10000
+
+#define BYTES_PER_FRAME 8
+
+#define min(a,b) (((a) < (b)) ? (a) : (b))
+
+// logging
+typedef enum { lERROR = 0, lWARN, lINFO, lDEBUG, lSDEBUG } log_level;
+
+const char *logtime(void);
+void logprint(const char *fmt, ...);
+
+#define LOG_ERROR(fmt, ...) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
+#define LOG_WARN(fmt, ...)  if (loglevel >= lWARN)  logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
+#define LOG_INFO(fmt, ...)  if (loglevel >= lINFO)  logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
+#define LOG_DEBUG(fmt, ...) if (loglevel >= lDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
+#define LOG_SDEBUG(fmt, ...) if (loglevel >= lSDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
+
+// utils.c (non logging)
+typedef enum { EVENT_TIMEOUT = 0, EVENT_READ, EVENT_WAKE } event_type;
+#if WIN && USE_SSL
+char* strcasestr(const char *haystack, const char *needle);
+#endif
+
+char *next_param(char *src, char c);
+u32_t gettime_ms(void);
+void get_mac(u8_t *mac);
+void set_nonblock(sockfd s);
+int connect_timeout(sockfd sock, const struct sockaddr *addr, socklen_t addrlen, int timeout);
+void server_addr(char *server, in_addr_t *ip_ptr, unsigned *port_ptr);
+void set_readwake_handles(event_handle handles[], sockfd s, event_event e);
+event_type wait_readwake(event_handle handles[], int timeout);
+void packN(u32_t *dest, u32_t val);
+void packn(u16_t *dest, u16_t val);
+u32_t unpackN(u32_t *src);
+u16_t unpackn(u16_t *src);
+#if OSX
+void set_nosigpipe(sockfd s);
+#else
+#define set_nosigpipe(s)
+#endif
+#if SUN
+void init_daemonize(void);
+int daemon(int,int);
+#endif
+#if WIN
+void winsock_init(void);
+void winsock_close(void);
+void *dlopen(const char *filename, int flag);
+void *dlsym(void *handle, const char *symbol);
+char *dlerror(void);
+int poll(struct pollfd *fds, unsigned long numfds, int timeout);
+#endif
+#if LINUX || FREEBSD
+void touch_memory(u8_t *buf, size_t size);
+#endif
+
+// buffer.c
+struct buffer {
+	u8_t *buf;
+	u8_t *readp;
+	u8_t *writep;
+	u8_t *wrap;
+	size_t size;
+	size_t base_size;
+	mutex_type mutex;
+};
+
+// _* called with mutex locked
+unsigned _buf_used(struct buffer *buf);
+unsigned _buf_space(struct buffer *buf);
+unsigned _buf_cont_read(struct buffer *buf);
+unsigned _buf_cont_write(struct buffer *buf);
+void _buf_inc_readp(struct buffer *buf, unsigned by);
+void _buf_inc_writep(struct buffer *buf, unsigned by);
+void buf_flush(struct buffer *buf);
+void buf_adjust(struct buffer *buf, size_t mod);
+void _buf_resize(struct buffer *buf, size_t size);
+void buf_init(struct buffer *buf, size_t size);
+void buf_destroy(struct buffer *buf);
+
+// slimproto.c
+void slimproto(log_level level, char *server, u8_t mac[6], const char *name, const char *namefile, const char *modelname, int maxSampleRate);
+void slimproto_stop(void);
+void wake_controller(void);
+
+// stream.c
+typedef enum { STOPPED = 0, DISCONNECT, STREAMING_WAIT,
+			   STREAMING_BUFFERING, STREAMING_FILE, STREAMING_HTTP, SEND_HEADERS, RECV_HEADERS } stream_state;
+typedef enum { DISCONNECT_OK = 0, LOCAL_DISCONNECT = 1, REMOTE_DISCONNECT = 2, UNREACHABLE = 3, TIMEOUT = 4 } disconnect_code;
+
+struct streamstate {
+	stream_state state;
+	disconnect_code disconnect;
+	char *header;
+	size_t header_len;
+	bool sent_headers;
+	bool cont_wait;
+	u64_t bytes;
+	unsigned threshold;
+	u32_t meta_interval;
+	u32_t meta_next;
+	u32_t meta_left;
+	bool  meta_send;
+};
+
+void stream_init(log_level level, unsigned stream_buf_size);
+void stream_close(void);
+void stream_file(const char *header, size_t header_len, unsigned threshold);
+void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, unsigned threshold, bool cont_wait);
+bool stream_disconnect(void);
+
+// decode.c
+typedef enum { DECODE_STOPPED = 0, DECODE_READY, DECODE_RUNNING, DECODE_COMPLETE, DECODE_ERROR } decode_state;
+
+struct decodestate {
+	decode_state state;
+	bool new_stream;
+	mutex_type mutex;
+#if PROCESS
+	bool direct;
+	bool process;
+#endif
+};
+
+#if PROCESS
+struct processstate {
+	u8_t *inbuf, *outbuf;
+	unsigned max_in_frames, max_out_frames;
+	unsigned in_frames, out_frames;
+	unsigned in_sample_rate, out_sample_rate;
+	unsigned long total_in, total_out;
+};
+#endif
+
+struct codec {
+	char id;
+	char *types;
+	unsigned min_read_bytes;
+	unsigned min_space;
+	void (*open)(u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t endianness);
+	void (*close)(void);
+	decode_state (*decode)(void);
+};
+
+void decode_init(log_level level, const char *include_codecs, const char *exclude_codecs);
+void decode_close(void);
+void decode_flush(void);
+unsigned decode_newstream(unsigned sample_rate, unsigned supported_rates[]);
+void codec_open(u8_t format, u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t endianness);
+
+#if PROCESS
+// process.c
+void process_samples(void);
+void process_drain(void);
+void process_flush(void);
+unsigned process_newstream(bool *direct, unsigned raw_sample_rate, unsigned supported_rates[]);
+void process_init(char *opt);
+#endif
+
+#if RESAMPLE
+// resample.c
+void resample_samples(struct processstate *process);
+bool resample_drain(struct processstate *process);
+bool resample_newstream(struct processstate *process, unsigned raw_sample_rate, unsigned supported_rates[]);
+void resample_flush(void);
+bool resample_init(char *opt);
+#endif
+
+// output.c output_alsa.c output_pa.c output_pack.c
+typedef enum { OUTPUT_OFF = -1, OUTPUT_STOPPED = 0, OUTPUT_BUFFER, OUTPUT_RUNNING, 
+			   OUTPUT_PAUSE_FRAMES, OUTPUT_SKIP_FRAMES, OUTPUT_START_AT } output_state;
+
+#if DSD
+typedef enum { PCM, DOP, DSD_U8, DSD_U16_LE, DSD_U32_LE, DSD_U16_BE, DSD_U32_BE, DOP_S24_LE, DOP_S24_3LE } dsd_format;
+typedef enum { S32_LE, S24_LE, S24_3LE, S16_LE, U8, U16_LE, U16_BE, U32_LE, U32_BE } output_format;
+#else
+typedef enum { S32_LE, S24_LE, S24_3LE, S16_LE } output_format;
+#endif
+
+typedef enum { FADE_INACTIVE = 0, FADE_DUE, FADE_ACTIVE } fade_state;
+typedef enum { FADE_UP = 1, FADE_DOWN, FADE_CROSS } fade_dir;
+typedef enum { FADE_NONE = 0, FADE_CROSSFADE, FADE_IN, FADE_OUT, FADE_INOUT } fade_mode;
+
+#define MAX_SUPPORTED_SAMPLERATES 18
+#define TEST_RATES = { 768000, 705600, 384000, 352800, 192000, 176400, 96000, 88200, 48000, 44100, 32000, 24000, 22500, 16000, 12000, 11025, 8000, 0 }
+
+struct outputstate {
+	output_state state;
+	output_format format;
+	const char *device;
+#if ALSA
+	unsigned buffer;
+	unsigned period;
+#endif
+	bool  track_started; 
+#if PORTAUDIO
+	bool  pa_reopen;
+	unsigned latency;
+	int pa_hostapi_option;
+#endif
+	int (* write_cb)(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, s32_t cross_gain_in, s32_t cross_gain_out, s32_t **cross_ptr);
+	unsigned start_frames;
+	unsigned frames_played;
+	unsigned frames_played_dmp;// frames played at the point delay is measured
+	unsigned current_sample_rate;
+	unsigned supported_rates[MAX_SUPPORTED_SAMPLERATES]; // ordered largest first so [0] is max_rate
+	unsigned default_sample_rate;
+	bool error_opening;
+	unsigned device_frames;
+	u32_t updated;
+	u32_t track_start_time;
+	u32_t current_replay_gain;
+	union {
+		u32_t pause_frames;
+		u32_t skip_frames;
+		u32_t start_at;
+	};
+	unsigned next_sample_rate; // set in decode thread
+	u8_t  *track_start;        // set in decode thread
+	u32_t gainL;               // set by slimproto
+	u32_t gainR;               // set by slimproto
+	bool  invert;              // set by slimproto
+	u32_t next_replay_gain;    // set by slimproto
+	unsigned threshold;        // set by slimproto
+	fade_state fade;
+	u8_t *fade_start;
+	u8_t *fade_end;
+	fade_dir fade_dir;
+	fade_mode fade_mode;       // set by slimproto
+	unsigned fade_secs;        // set by slimproto
+	unsigned rate_delay;
+	bool delay_active;
+	u32_t stop_time;
+	u32_t idle_to;
+#if DSD
+	dsd_format next_fmt;       // set in decode thread
+	dsd_format outfmt;
+	dsd_format dsdfmt;	       // set in dsd_init - output for DSD: DOP, DSD_U8, ...
+	unsigned dsd_delay;		   // set in dsd_init - delay in ms switching to/from dop
+#endif
+};
+
+void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[], unsigned idle);
+void output_close_common(void);
+void output_flush(void);
+// _* called with mutex locked
+frames_t _output_frames(frames_t avail);
+void _checkfade(bool);
+
+// output_alsa.c
+#if ALSA
+void list_devices(void);
+void list_mixers(const char *output_device);
+void set_volume(unsigned left, unsigned right);
+bool test_open(const char *device, unsigned rates[], bool userdef_rates);
+void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned rt_priority, unsigned idle, char *mixer_device, char *volume_mixer, bool mixer_unmute, bool mixer_linear);
+void output_close_alsa(void);
+#endif
+
+// output_pa.c
+#if PORTAUDIO
+void list_devices(void);
+void set_volume(unsigned left, unsigned right);
+bool test_open(const char *device, unsigned rates[], bool userdef_rates);
+void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle);
+void output_close_pa(void);
+void _pa_open(void);
+#endif
+
+// output_dac.c
+#if DACAUDIO
+void set_volume(unsigned left, unsigned right);
+void output_init_dac(log_level level, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle);
+void output_close_dac(void);
+#endif
+
+// output_stdout.c
+void output_init_stdout(log_level level, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay);
+void output_close_stdout(void);
+
+// output_pack.c
+void _scale_and_pack_frames(void *outputptr, s32_t *inputptr, frames_t cnt, s32_t gainL, s32_t gainR, output_format format);
+void _apply_cross(struct buffer *outputbuf, frames_t out_frames, s32_t cross_gain_in, s32_t cross_gain_out, s32_t **cross_ptr);
+void _apply_gain(struct buffer *outputbuf, frames_t count, s32_t gainL, s32_t gainR);
+s32_t gain(s32_t gain, s32_t sample);
+s32_t to_gain(float f);
+
+// output_vis.c
+#if VISEXPORT
+void _vis_export(struct buffer *outputbuf, struct outputstate *output, frames_t out_frames, bool silence);
+void output_vis_init(log_level level, u8_t *mac);
+void vis_stop(void);
+#else
+#define _vis_export(...)
+#define vis_stop()
+#endif
+
+// dop.c
+#if DSD
+bool is_stream_dop(u8_t *lptr, u8_t *rptr, int step, frames_t frames);
+void update_dop(u32_t *ptr, frames_t frames, bool invert);
+void dsd_silence_frames(u32_t *ptr, frames_t frames);
+void dsd_invert(u32_t *ptr, frames_t frames);
+void dsd_init(dsd_format format, unsigned delay);
+#endif
+
+// codecs
+#define MAX_CODECS 9
+
+struct codec *register_flac(void);
+struct codec *register_pcm(void);
+struct codec *register_mad(void);
+struct codec *register_mpg(void);
+struct codec *register_vorbis(void);
+struct codec *register_faad(void);
+struct codec *register_dsd(void);
+struct codec *register_ff(const char *codec);
+
+//gpio.c
+#if GPIO
+void relay( int state);
+void relay_script(int state);
+int gpio_pin;
+bool gpio_active_low;
+bool gpio_active;
+char *power_script;
+//  my amp state
+int ampstate;
+#endif
+
+// ir.c
+#if IR
+struct irstate {
+	mutex_type mutex;
+	u32_t code;
+	u32_t ts;
+};
+
+void ir_init(log_level level, char *lircrc);
+void ir_close(void);
+#endif
+
+// sslsym.c
+#if USE_SSL && !LINKALL
+bool load_ssl_symbols(void);
+void free_ssl_symbols(void);
+bool ssl_loaded;
+#endif
+

+ 590 - 0
stream.c

@@ -0,0 +1,590 @@
+/* 
+ *  Squeezelite - lightweight headless squeezebox emulator
+ *
+ *  (c) Adrian Smith 2012-2015, triode1@btinternet.com
+ *      Ralph Irving 2015-2017, ralph_irving@hotmail.com
+ *  
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+// stream thread
+
+#define _GNU_SOURCE
+
+#include "squeezelite.h"
+
+#include <fcntl.h>
+
+#if USE_SSL
+#include "openssl/ssl.h"
+#include "openssl/err.h"
+#endif
+
+#if SUN
+#include <signal.h>
+#endif
+static log_level loglevel;
+
+static struct buffer buf;
+struct buffer *streambuf = &buf;
+
+#define LOCK   mutex_lock(streambuf->mutex)
+#define UNLOCK mutex_unlock(streambuf->mutex)
+
+static sockfd fd;
+
+struct streamstate stream;
+
+#if USE_SSL
+static SSL_CTX *SSLctx;
+SSL *ssl;
+#endif
+
+#if !USE_SSL
+#define _recv(ssl, fc, buf, n, opt) recv(fd, buf, n, opt)
+#define _send(ssl, fd, buf, n, opt) send(fd, buf, n, opt)
+#define _poll(ssl, pollinfo, timeout) poll(pollinfo, 1, timeout)
+#define _last_error() last_error()
+#else
+#define _last_error() ERROR_WOULDBLOCK
+
+static int _recv(SSL *ssl, int fd, void *buffer, size_t bytes, int options) {
+	int n;
+	if (!ssl) return recv(fd, buffer, bytes, options);
+	n = SSL_read(ssl, (u8_t*) buffer, bytes);
+	if (n <= 0 && SSL_get_error(ssl, n) == SSL_ERROR_ZERO_RETURN) return 0;
+	return n;
+}
+
+static int _send(SSL *ssl, int fd, void *buffer, size_t bytes, int options) {
+	int n;
+	if (!ssl) return send(fd, buffer, bytes, options);
+	while (1) {
+		int err;
+		ERR_clear_error();
+		if ((n = SSL_write(ssl, (u8_t*) buffer, bytes)) >= 0) return n;
+		err = SSL_get_error(ssl, n);
+		if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) continue;
+		LOG_INFO("SSL write error %d", err );
+		return n;
+	}
+}
+
+/*
+can't mimic exactly poll as SSL is a real pain. Even if SSL_pending returns
+0, there might be bytes to read but when select (poll) return > 0, there might
+be no frame available. As well select (poll) < 0 does not mean that there is
+no data pending
+*/
+static int _poll(SSL *ssl, struct pollfd *pollinfo, int timeout) {
+	if (!ssl) return poll(pollinfo, 1, timeout);
+	if (pollinfo->events & POLLIN && SSL_pending(ssl)) {
+		if (pollinfo->events & POLLOUT) poll(pollinfo, 1, 0);
+		pollinfo->revents = POLLIN;
+		return 1;
+	}
+	return poll(pollinfo, 1, timeout);
+}
+#endif
+
+
+static bool send_header(void) {
+	char *ptr = stream.header;
+	int len = stream.header_len;
+
+	unsigned try = 0;
+	ssize_t n;
+	
+	while (len) {
+		n = _send(ssl, fd, ptr, len, MSG_NOSIGNAL);
+		if (n <= 0) {
+			if (n < 0 && _last_error() == ERROR_WOULDBLOCK && try < 10) {
+				LOG_SDEBUG("retrying (%d) writing to socket", ++try);
+				usleep(1000);
+				continue;
+			}
+			LOG_INFO("failed writing to socket: %s", strerror(last_error()));
+			stream.disconnect = LOCAL_DISCONNECT;
+			stream.state = DISCONNECT;
+			wake_controller();
+			return false;
+		}
+		LOG_SDEBUG("wrote %d bytes to socket", n);
+		ptr += n;
+		len -= n;
+	}
+	LOG_SDEBUG("wrote header");
+	return true;
+}
+
+static bool running = true;
+
+static void _disconnect(stream_state state, disconnect_code disconnect) {
+	stream.state = state;
+	stream.disconnect = disconnect;
+#if USE_SSL
+	if (ssl) {
+		SSL_shutdown(ssl);
+		SSL_free(ssl);
+		ssl = NULL;
+	}
+#endif
+	closesocket(fd);
+	fd = -1;
+	wake_controller();
+}
+
+static void *stream_thread() {
+
+	while (running) {
+
+		struct pollfd pollinfo;
+		size_t space;
+
+		LOCK;
+
+		space = min(_buf_space(streambuf), _buf_cont_write(streambuf));
+
+		if (fd < 0 || !space || stream.state <= STREAMING_WAIT) {
+			UNLOCK;
+			usleep(100000);
+			continue;
+		}
+
+		if (stream.state == STREAMING_FILE) {
+
+			int n = read(fd, streambuf->writep, space);
+			if (n == 0) {
+				LOG_INFO("end of stream");
+				_disconnect(DISCONNECT, DISCONNECT_OK);
+			}
+			if (n > 0) {
+				_buf_inc_writep(streambuf, n);
+				stream.bytes += n;
+				LOG_SDEBUG("streambuf read %d bytes", n);
+			}
+			if (n < 0) {
+				LOG_WARN("error reading: %s", strerror(last_error()));
+				_disconnect(DISCONNECT, REMOTE_DISCONNECT);
+			}
+
+			UNLOCK;
+			continue;
+
+		} else {
+
+			pollinfo.fd = fd;
+			pollinfo.events = POLLIN;
+			if (stream.state == SEND_HEADERS) {
+				pollinfo.events |= POLLOUT;
+			}
+		}
+
+		UNLOCK;
+
+		if (_poll(ssl, &pollinfo, 100)) {
+
+			LOCK;
+
+			// check socket has not been closed while in poll
+			if (fd < 0) {
+				UNLOCK;
+				continue;
+			}
+
+			if ((pollinfo.revents & POLLOUT) && stream.state == SEND_HEADERS) {
+				if (send_header()) stream.state = RECV_HEADERS;
+				stream.header_len = 0;
+				UNLOCK;
+				continue;
+			}
+					
+			if (pollinfo.revents & (POLLIN | POLLHUP)) {
+
+				// get response headers
+				if (stream.state == RECV_HEADERS) {
+
+					// read one byte at a time to catch end of header
+					char c;
+					static int endtok;
+
+					int n = _recv(ssl, fd, &c, 1, 0);
+					if (n <= 0) {
+						if (n < 0 && _last_error() == ERROR_WOULDBLOCK) {
+							UNLOCK;
+							continue;
+						}
+						LOG_INFO("error reading headers: %s", n ? strerror(last_error()) : "closed");
+						_disconnect(STOPPED, LOCAL_DISCONNECT);
+						UNLOCK;
+						continue;
+					}
+
+					*(stream.header + stream.header_len) = c;
+					stream.header_len++;
+
+					if (stream.header_len > MAX_HEADER - 1) {
+						LOG_ERROR("received headers too long: %u", stream.header_len);
+						_disconnect(DISCONNECT, LOCAL_DISCONNECT);
+					}
+
+					if (stream.header_len > 1 && (c == '\r' || c == '\n')) {
+						endtok++;
+						if (endtok == 4) {
+							*(stream.header + stream.header_len) = '\0';
+							LOG_INFO("headers: len: %d\n%s", stream.header_len, stream.header);
+							stream.state = stream.cont_wait ? STREAMING_WAIT : STREAMING_BUFFERING;
+							wake_controller();
+						}
+					} else {
+						endtok = 0;
+					}
+				
+					UNLOCK;
+					continue;
+				}
+				
+				// receive icy meta data
+				if (stream.meta_interval && stream.meta_next == 0) {
+
+					if (stream.meta_left == 0) {
+						// read meta length
+						u8_t c;
+						int n = _recv(ssl, fd, &c, 1, 0);
+						if (n <= 0) {
+							if (n < 0 && _last_error() == ERROR_WOULDBLOCK) {
+								UNLOCK;
+								continue;
+							}
+							LOG_INFO("error reading icy meta: %s", n ? strerror(last_error()) : "closed");
+							_disconnect(STOPPED, LOCAL_DISCONNECT);
+							UNLOCK;
+							continue;
+						}
+						stream.meta_left = 16 * c;
+						stream.header_len = 0; // amount of received meta data
+						// MAX_HEADER must be more than meta max of 16 * 255
+					}
+
+					if (stream.meta_left) {
+						int n = _recv(ssl, fd, stream.header + stream.header_len, stream.meta_left, 0);
+						if (n <= 0) {
+							if (n < 0 && _last_error() == ERROR_WOULDBLOCK) {
+								UNLOCK;
+								continue;
+							}
+							LOG_INFO("error reading icy meta: %s", n ? strerror(last_error()) : "closed");
+							_disconnect(STOPPED, LOCAL_DISCONNECT);
+							UNLOCK;
+							continue;
+						}
+						stream.meta_left -= n;
+						stream.header_len += n;
+					}
+					
+					if (stream.meta_left == 0) {
+						if (stream.header_len) {
+							*(stream.header + stream.header_len) = '\0';
+							LOG_INFO("icy meta: len: %u\n%s", stream.header_len, stream.header);
+							stream.meta_send = true;
+							wake_controller();
+						}
+						stream.meta_next = stream.meta_interval;
+						UNLOCK;
+						continue;
+					}
+
+				// stream body into streambuf
+				} else {
+					int n;
+
+					space = min(_buf_space(streambuf), _buf_cont_write(streambuf));
+					if (stream.meta_interval) {
+						space = min(space, stream.meta_next);
+					}
+					
+					n = _recv(ssl, fd, streambuf->writep, space, 0);
+					if (n == 0) {
+						LOG_INFO("end of stream");
+						_disconnect(DISCONNECT, DISCONNECT_OK);
+					}
+					if (n < 0 && _last_error() != ERROR_WOULDBLOCK) {
+						LOG_INFO("error reading: %s", strerror(last_error()));
+						_disconnect(DISCONNECT, REMOTE_DISCONNECT);
+					}
+					
+					if (n > 0) {
+						_buf_inc_writep(streambuf, n);
+						stream.bytes += n;
+						if (stream.meta_interval) {
+							stream.meta_next -= n;
+						}
+					} else {
+						UNLOCK;
+						continue;
+					}
+
+					if (stream.state == STREAMING_BUFFERING && stream.bytes > stream.threshold) {
+						stream.state = STREAMING_HTTP;
+						wake_controller();
+					}
+				
+					LOG_SDEBUG("streambuf read %d bytes", n);
+				}
+			}
+
+			UNLOCK;
+			
+		} else {
+			
+			LOG_SDEBUG("poll timeout");
+		}
+	}
+	
+#if USE_SSL	
+	if (SSLctx) {
+		SSL_CTX_free(SSLctx);
+	}	
+#endif	
+
+	return 0;
+}
+
+static thread_type thread;
+
+void stream_init(log_level level, unsigned stream_buf_size) {
+	loglevel = level;
+
+	LOG_INFO("init stream");
+	LOG_DEBUG("streambuf size: %u", stream_buf_size);
+
+	buf_init(streambuf, stream_buf_size);
+	if (streambuf->buf == NULL) {
+		LOG_ERROR("unable to malloc buffer");
+		exit(0);
+	}
+	
+#if USE_SSL
+#if !LINKALL
+	if (ssl_loaded) {
+#endif
+	SSL_library_init();
+	SSLctx = SSL_CTX_new(SSLv23_client_method());
+	if (SSLctx == NULL) {
+		LOG_ERROR("unable to allocate SSL context");
+		exit(0);
+	}	
+	SSL_CTX_set_options(SSLctx, SSL_OP_NO_SSLv2);
+#if !LINKALL
+	}
+#endif	
+	ssl = NULL;
+#endif
+	
+#if SUN
+	signal(SIGPIPE, SIG_IGN);	/* Force sockets to return -1 with EPIPE on pipe signal */
+#endif
+	stream.state = STOPPED;
+	stream.header = malloc(MAX_HEADER);
+	*stream.header = '\0';
+
+	fd = -1;
+
+#if LINUX || FREEBSD
+	touch_memory(streambuf->buf, streambuf->size);
+#endif
+
+#if LINUX || OSX || FREEBSD || POSIX
+	pthread_attr_t attr;
+	pthread_attr_init(&attr);
+#ifdef PTHREAD_STACK_MIN	
+	pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + STREAM_THREAD_STACK_SIZE);
+#endif
+	pthread_create(&thread, &attr, stream_thread, NULL);
+	pthread_attr_destroy(&attr);
+#endif
+#if WIN
+	thread = CreateThread(NULL, STREAM_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&stream_thread, NULL, 0, NULL);
+#endif
+}
+
+void stream_close(void) {
+	LOG_INFO("close stream");
+	LOCK;
+	running = false;
+	UNLOCK;
+#if LINUX || OSX || FREEBSD || POSIX
+	pthread_join(thread, NULL);
+#endif
+	free(stream.header);
+	buf_destroy(streambuf);
+}
+
+void stream_file(const char *header, size_t header_len, unsigned threshold) {
+	buf_flush(streambuf);
+
+	LOCK;
+
+	stream.header_len = header_len;
+	memcpy(stream.header, header, header_len);
+	*(stream.header+header_len) = '\0';
+
+	LOG_INFO("opening local file: %s", stream.header);
+
+#if WIN
+	fd = open(stream.header, O_RDONLY | O_BINARY);
+#else
+	fd = open(stream.header, O_RDONLY);
+#endif
+
+	stream.state = STREAMING_FILE;
+	if (fd < 0) {
+		LOG_INFO("can't open file: %s", stream.header);
+		stream.state = DISCONNECT;
+	}
+	wake_controller();
+	
+	stream.cont_wait = false;
+	stream.meta_interval = 0;
+	stream.meta_next = 0;
+	stream.meta_left = 0;
+	stream.meta_send = false;
+	stream.sent_headers = false;
+	stream.bytes = 0;
+	stream.threshold = threshold;
+
+	UNLOCK;
+}
+
+void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, unsigned threshold, bool cont_wait) {
+	struct sockaddr_in addr;
+
+	int sock = socket(AF_INET, SOCK_STREAM, 0);
+
+	if (sock < 0) {
+		LOG_ERROR("failed to create socket");
+		return;
+	}
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_addr.s_addr = ip;
+	addr.sin_port = port;
+
+	LOG_INFO("connecting to %s:%d", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
+
+	set_nonblock(sock);
+	set_nosigpipe(sock);
+
+	if (connect_timeout(sock, (struct sockaddr *) &addr, sizeof(addr), 10) < 0) {
+		LOG_INFO("unable to connect to server");
+		LOCK;
+		stream.state = DISCONNECT;
+		stream.disconnect = UNREACHABLE;
+		UNLOCK;
+		return;
+	}
+	
+#if USE_SSL
+	if (ntohs(port) == 443) {
+		char *server = strcasestr(header, "Host:");
+
+		ssl = SSL_new(SSLctx);
+		SSL_set_fd(ssl, sock);
+
+		// add SNI
+		if (server) {
+			char *p, *servername = malloc(1024);
+
+			sscanf(server, "Host:%255[^:]s", servername);
+			for (p = servername; *p == ' '; p++);
+			SSL_set_tlsext_host_name(ssl, p);
+			free(servername);
+		}
+		
+		while (1) {
+			int status, err = 0;
+
+			ERR_clear_error();
+			status = SSL_connect(ssl);
+
+			// successful negotiation
+			if (status == 1) break;
+
+			// error or non-blocking requires more time
+			if (status < 0) {
+				err = SSL_get_error(ssl, status);
+				if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) continue;
+			}
+
+			LOG_WARN("unable to open SSL socket %d (%d)", status, err);
+			closesocket(sock);
+			SSL_free(ssl);
+			ssl = NULL;
+			LOCK;
+			stream.state = DISCONNECT;
+			stream.disconnect = UNREACHABLE;
+			UNLOCK;
+
+			return;
+		}
+	} else {
+		ssl = NULL;	
+	}
+#endif
+
+	buf_flush(streambuf);
+
+	LOCK;
+
+	fd = sock;
+	stream.state = SEND_HEADERS;
+	stream.cont_wait = cont_wait;
+	stream.meta_interval = 0;
+	stream.meta_next = 0;
+	stream.meta_left = 0;
+	stream.meta_send = false;
+	stream.header_len = header_len;
+	memcpy(stream.header, header, header_len);
+	*(stream.header+header_len) = '\0';
+
+	LOG_INFO("header: %s", stream.header);
+
+	stream.sent_headers = false;
+	stream.bytes = 0;
+	stream.threshold = threshold;
+
+	UNLOCK;
+}
+
+bool stream_disconnect(void) {
+	bool disc = false;
+	LOCK;
+#if USE_SSL
+	if (ssl) {
+		SSL_shutdown(ssl);
+		SSL_free(ssl);
+		ssl = NULL;
+	}
+#endif
+	if (fd != -1) {
+		closesocket(fd);
+		fd = -1;
+		disc = true;
+	}
+	stream.state = STOPPED;
+	UNLOCK;
+	return disc;
+}

+ 563 - 0
utils.c

@@ -0,0 +1,563 @@
+/* 
+ *  Squeezelite - lightweight headless squeezebox emulator
+ *
+ *  (c) Adrian Smith 2012-2015, triode1@btinternet.com
+ *      Ralph Irving 2015-2017, ralph_irving@hotmail.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "squeezelite.h"
+
+#if LINUX || OSX || FREEBSD || POSIX
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <netdb.h>
+#if FREEBSD
+#include <ifaddrs.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#endif
+#endif
+#if SUN
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#endif
+#if WIN
+#include <iphlpapi.h>
+#if USE_SSL
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#endif
+#endif
+#if OSX
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <ifaddrs.h>
+#include <netdb.h>
+#endif
+
+#include <fcntl.h>
+
+// logging functions
+const char *logtime(void) {
+	static char buf[100];
+#if WIN
+	SYSTEMTIME lt;
+	GetLocalTime(&lt);
+	sprintf(buf, "[%02d:%02d:%02d.%03d]", lt.wHour, lt.wMinute, lt.wSecond, lt.wMilliseconds);
+#else
+	struct timeval tv;
+	gettimeofday(&tv, NULL);
+	strftime(buf, sizeof(buf), "[%T.", localtime(&tv.tv_sec));
+	sprintf(buf+strlen(buf), "%06ld]", (long)tv.tv_usec);
+#endif
+	return buf;
+}
+
+void logprint(const char *fmt, ...) {
+	va_list args;
+	va_start(args, fmt);
+	vfprintf(stderr, fmt, args);
+	fflush(stderr);
+}
+
+// cmdline parsing
+char *next_param(char *src, char c) {
+	static char *str = NULL;
+	char *ptr, *ret;
+	if (src) str = src;
+	if (str && (ptr = strchr(str, c))) {
+		ret = str;
+		*ptr = '\0';
+		str = ptr + 1;
+	} else {
+		ret = str;
+		str = NULL;
+	}
+
+	return ret && ret[0] ? ret : NULL;
+}
+
+// clock
+u32_t gettime_ms(void) {
+#if WIN
+	return GetTickCount();
+#else
+#if LINUX || FREEBSD || POSIX
+	struct timespec ts;
+#ifdef CLOCK_MONOTONIC
+	if (!clock_gettime(CLOCK_MONOTONIC, &ts)) {
+#else
+	if (!clock_gettime(CLOCK_REALTIME, &ts)) {
+#endif
+		return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
+	}
+#endif
+	struct timeval tv;
+	gettimeofday(&tv, NULL);
+	return tv.tv_sec * 1000 + tv.tv_usec / 1000;
+#endif
+}
+
+// mac address
+#if LINUX && !defined(SUN)
+// search first 4 interfaces returned by IFCONF
+void get_mac(u8_t mac[]) {
+	char *utmac;
+	struct ifconf ifc;
+	struct ifreq *ifr, *ifend;
+	struct ifreq ifreq;
+	struct ifreq ifs[4];
+
+	utmac = getenv("UTMAC");
+	if (utmac)
+	{
+		if ( strlen(utmac) == 17 )
+		{
+			if (sscanf(utmac,"%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx",
+				&mac[0],&mac[1],&mac[2],&mac[3],&mac[4],&mac[5]) == 6)
+			{
+				return;
+			}
+		}
+
+	}
+
+	mac[0] = mac[1] = mac[2] = mac[3] = mac[4] = mac[5] = 0;
+
+	int s = socket(AF_INET, SOCK_DGRAM, 0);
+
+	ifc.ifc_len = sizeof(ifs);
+	ifc.ifc_req = ifs;
+
+	if (ioctl(s, SIOCGIFCONF, &ifc) == 0) {
+		ifend = ifs + (ifc.ifc_len / sizeof(struct ifreq));
+
+		for (ifr = ifc.ifc_req; ifr < ifend; ifr++) {
+			if (ifr->ifr_addr.sa_family == AF_INET) {
+
+				strncpy(ifreq.ifr_name, ifr->ifr_name, sizeof(ifreq.ifr_name));
+				if (ioctl (s, SIOCGIFHWADDR, &ifreq) == 0) {
+					memcpy(mac, ifreq.ifr_hwaddr.sa_data, 6);
+					if (mac[0]+mac[1]+mac[2] != 0) {
+						break;
+					}
+				}
+			}
+		}
+	}
+
+	close(s);
+}
+#endif
+
+#if SUN
+void get_mac(u8_t mac[]) {
+	struct  arpreq          parpreq;
+	struct  sockaddr_in     *psa;
+	struct  in_addr         inaddr;
+	struct  hostent         *phost;
+	char                    hostname[MAXHOSTNAMELEN];
+	char                    **paddrs;
+	char                    *utmac;
+	int                     sock;
+	int                     status=0;
+
+	utmac = getenv("UTMAC");
+	if (utmac)
+	{
+		if ( strlen(utmac) == 17 )
+		{
+			if (sscanf(utmac,"%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx",
+				&mac[0],&mac[1],&mac[2],&mac[3],&mac[4],&mac[5]) == 6)
+			{
+				return;
+			}
+		}
+
+	}
+
+	mac[0] = mac[1] = mac[2] = mac[3] = mac[4] = mac[5] = 0;
+
+	gethostname(hostname,  MAXHOSTNAMELEN);
+
+	phost = gethostbyname(hostname);
+
+	paddrs = phost->h_addr_list;
+	memcpy(&inaddr.s_addr, *paddrs, sizeof(inaddr.s_addr));
+
+	sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+
+	if(sock == -1)
+	{
+		mac[5] = 1;
+		return;
+	}
+
+	memset(&parpreq, 0, sizeof(struct arpreq));
+	psa = (struct sockaddr_in *) &parpreq.arp_pa;
+	memset(psa, 0, sizeof(struct sockaddr_in));
+	psa->sin_family = AF_INET;
+	memcpy(&psa->sin_addr, *paddrs, sizeof(struct in_addr));
+
+	status = ioctl(sock, SIOCGARP, &parpreq);
+
+	if(status == -1)
+	{
+		mac[5] = 2;
+		return;
+	}
+
+	mac[0] = (unsigned char) parpreq.arp_ha.sa_data[0];
+	mac[1] = (unsigned char) parpreq.arp_ha.sa_data[1];
+	mac[2] = (unsigned char) parpreq.arp_ha.sa_data[2];
+	mac[3] = (unsigned char) parpreq.arp_ha.sa_data[3];
+	mac[4] = (unsigned char) parpreq.arp_ha.sa_data[4];
+	mac[5] = (unsigned char) parpreq.arp_ha.sa_data[5];
+}
+#endif
+
+#if OSX || FREEBSD
+void get_mac(u8_t mac[]) {
+	struct ifaddrs *addrs, *ptr;
+	const struct sockaddr_dl *dlAddr;
+	const unsigned char *base;
+	
+	mac[0] = mac[1] = mac[2] = mac[3] = mac[4] = mac[5] = 0;
+	
+	if (getifaddrs(&addrs) == 0) {
+		ptr = addrs;
+		while (ptr) {
+			if (ptr->ifa_addr->sa_family == AF_LINK && ((const struct sockaddr_dl *) ptr->ifa_addr)->sdl_type == IFT_ETHER) {
+				dlAddr = (const struct sockaddr_dl *)ptr->ifa_addr;
+				base = (const unsigned char*) &dlAddr->sdl_data[dlAddr->sdl_nlen];
+				memcpy(mac, base, min(dlAddr->sdl_alen, 6));
+				break;
+			}
+			ptr = ptr->ifa_next;
+		}
+		freeifaddrs(addrs);
+	}
+}
+#endif
+
+#if WIN
+#pragma comment(lib, "IPHLPAPI.lib")
+void get_mac(u8_t mac[]) {
+	IP_ADAPTER_INFO AdapterInfo[16];
+	DWORD dwBufLen = sizeof(AdapterInfo);
+	DWORD dwStatus = GetAdaptersInfo(AdapterInfo, &dwBufLen);
+	
+	mac[0] = mac[1] = mac[2] = mac[3] = mac[4] = mac[5] = 0;
+
+	if (GetAdaptersInfo(AdapterInfo, &dwBufLen) == ERROR_SUCCESS) {
+		memcpy(mac, AdapterInfo[0].Address, 6);
+	}
+}
+#endif
+
+void set_nonblock(sockfd s) {
+#if WIN
+	u_long iMode = 1;
+	ioctlsocket(s, FIONBIO, &iMode);
+#else
+	int flags = fcntl(s, F_GETFL,0);
+	fcntl(s, F_SETFL, flags | O_NONBLOCK);
+#endif
+}
+
+// connect for socket already set to non blocking with timeout in seconds
+int connect_timeout(sockfd sock, const struct sockaddr *addr, socklen_t addrlen, int timeout) {
+	fd_set w, e;
+	struct timeval tval;
+
+	if (connect(sock, addr, addrlen) < 0) {
+#if !WIN
+		if (last_error() != EINPROGRESS) {
+#else
+		if (last_error() != WSAEWOULDBLOCK) {
+#endif
+			return -1;
+		}
+	}
+
+	FD_ZERO(&w);
+	FD_SET(sock, &w);
+	e = w;
+	tval.tv_sec = timeout;
+	tval.tv_usec = 0;
+
+	// only return 0 if w set and sock error is zero, otherwise return error code
+	if (select(sock + 1, NULL, &w, &e, timeout ? &tval : NULL) == 1 && FD_ISSET(sock, &w)) {
+		int	error = 0;
+		socklen_t len = sizeof(error);
+		getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *)&error, &len);
+		return error;
+	}
+
+	return -1;
+}
+
+void server_addr(char *server, in_addr_t *ip_ptr, unsigned *port_ptr) {
+	struct addrinfo *res = NULL;
+	struct addrinfo hints;
+	const char *port = NULL;
+	
+	if (strtok(server, ":")) {
+		port = strtok(NULL, ":");
+		if (port) {
+			*port_ptr = atoi(port);
+		}
+	}
+
+	memset(&hints, 0, sizeof(struct addrinfo));
+	hints.ai_family = AF_INET;
+	
+	getaddrinfo(server, NULL, &hints, &res);
+	
+	if (res && res->ai_addr) {
+		*ip_ptr = ((struct sockaddr_in*)res->ai_addr)->sin_addr.s_addr;
+	} 
+	
+	if (res) {
+		freeaddrinfo(res);
+	}
+}
+
+void set_readwake_handles(event_handle handles[], sockfd s, event_event e) {
+#if WINEVENT
+	handles[0] = WSACreateEvent();
+	handles[1] = e;
+	WSAEventSelect(s, handles[0], FD_READ | FD_CLOSE);
+#elif SELFPIPE || LOOPBACK
+	handles[0].fd = s;
+	handles[1].fd = e.fds[0];
+	handles[0].events = POLLIN;
+	handles[1].events = POLLIN;
+#else
+	handles[0].fd = s;
+	handles[1].fd = e;
+	handles[0].events = POLLIN;
+	handles[1].events = POLLIN;
+#endif
+}
+
+event_type wait_readwake(event_handle handles[], int timeout) {
+#if WINEVENT
+	int wait = WSAWaitForMultipleEvents(2, handles, FALSE, timeout, FALSE);
+	if (wait == WSA_WAIT_EVENT_0) {
+		WSAResetEvent(handles[0]);
+		return EVENT_READ;
+	} else if (wait == WSA_WAIT_EVENT_0 + 1) {
+		return EVENT_WAKE;
+	} else {
+		return EVENT_TIMEOUT;
+	}
+#else
+	if (poll(handles, 2, timeout) > 0) {
+		if (handles[0].revents) {
+			return EVENT_READ;
+		}
+		if (handles[1].revents) {
+			wake_clear(handles[1].fd);
+			return EVENT_WAKE;
+		}
+	}
+	return EVENT_TIMEOUT;
+#endif
+}
+
+#if LOOPBACK
+void _wake_create(event_event* e) {
+	struct sockaddr_in addr;
+	short port;
+	socklen_t len;
+	
+	e->mfds = e->fds[0] = e->fds[1] = -1;
+	addr.sin_family = AF_INET;
+	addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+	// create sending socket - will wait for connections
+	addr.sin_port = 0;
+	e->mfds = socket(AF_INET, SOCK_STREAM, 0);
+	bind(e->mfds, (struct sockaddr*) &addr, sizeof(addr));
+	len = sizeof(struct sockaddr);
+
+	// get assigned port & listen
+	getsockname(e->mfds, (struct sockaddr *) &addr, &len);
+	port = addr.sin_port;
+	listen(e->mfds, 1);
+
+	// create receiving socket
+	addr.sin_port = 0;
+	e->fds[0] = socket(AF_INET, SOCK_STREAM, 0);
+	bind(e->fds[0], (struct sockaddr*) &addr, sizeof(addr));
+
+	// connect to sender (we listen so it can be blocking)
+	addr.sin_port = port;
+	connect(e->fds[0], (struct sockaddr*) &addr, sizeof(addr));
+
+	// this one will work or fail, but not block
+	len = sizeof(struct sockaddr);
+	e->fds[1] = accept(e->mfds, (struct sockaddr*) &addr, &len);
+}
+#endif
+
+// pack/unpack to network byte order
+void packN(u32_t *dest, u32_t val) {
+	u8_t *ptr = (u8_t *)dest;
+	*(ptr)   = (val >> 24) & 0xFF; *(ptr+1) = (val >> 16) & 0xFF; *(ptr+2) = (val >> 8) & 0xFF;	*(ptr+3) = val & 0xFF;
+}
+
+void packn(u16_t *dest, u16_t val) {
+	u8_t *ptr = (u8_t *)dest;
+	*(ptr) = (val >> 8) & 0xFF; *(ptr+1) = val & 0xFF;
+}
+
+u32_t unpackN(u32_t *src) {
+	u8_t *ptr = (u8_t *)src;
+	return *(ptr) << 24 | *(ptr+1) << 16 | *(ptr+2) << 8 | *(ptr+3);
+} 
+
+u16_t unpackn(u16_t *src) {
+	u8_t *ptr = (u8_t *)src;
+	return *(ptr) << 8 | *(ptr+1);
+} 
+
+#if OSX
+void set_nosigpipe(sockfd s) {
+	int set = 1;
+	setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));
+}
+#endif
+
+#if WIN
+void winsock_init(void) {
+	WSADATA wsaData;
+	WORD wVersionRequested = MAKEWORD(2, 2);
+	int WSerr = WSAStartup(wVersionRequested, &wsaData);
+	if (WSerr != 0) {
+		LOG_ERROR("Bad winsock version");
+		exit(1);
+	}
+}
+
+void winsock_close(void) {
+	WSACleanup();
+}
+
+void *dlopen(const char *filename, int flag) {
+	SetLastError(0);
+	return LoadLibrary((LPCTSTR)filename);
+}
+
+void *dlsym(void *handle, const char *symbol) {
+	SetLastError(0);
+	return (void *)GetProcAddress(handle, symbol);
+}
+
+char *dlerror(void) {
+	static char ret[32];
+	int last = GetLastError();
+	if (last) {
+		sprintf(ret, "code: %i", last);
+		SetLastError(0);
+		return ret;
+	}
+	return NULL;
+}
+
+int poll(struct pollfd *fds, unsigned long numfds, int timeout) {
+	fd_set r, w;
+	struct timeval tv;
+	int ret, i, max_fds = fds[0].fd;
+
+	FD_ZERO(&r);
+	FD_ZERO(&w);
+
+	for (i = 0; i < numfds; i++) {
+		if (fds[i].events & POLLIN) FD_SET(fds[i].fd, &r);
+		if (fds[i].events & POLLOUT) FD_SET(fds[i].fd, &w);
+		if (max_fds < fds[i].fd) max_fds = fds[i].fd;
+	}
+
+	tv.tv_sec = timeout / 1000;
+	tv.tv_usec = 1000 * (timeout % 1000);
+
+	ret = select(max_fds + 1, &r, &w, NULL, &tv);
+
+	if (ret < 0) return ret;
+
+	for (i = 0; i < numfds; i++) {
+		fds[i].revents = 0;
+		if (FD_ISSET(fds[i].fd, &r)) fds[i].revents |= POLLIN;
+		if (FD_ISSET(fds[i].fd, &w)) fds[i].revents |= POLLOUT;
+	}
+
+	return ret;
+}
+#endif
+
+#if LINUX || FREEBSD
+void touch_memory(u8_t *buf, size_t size) {
+	u8_t *ptr;
+	for (ptr = buf; ptr < buf + size; ptr += sysconf(_SC_PAGESIZE)) {
+		*ptr = 0;
+	}
+}
+#endif
+
+#if WIN && USE_SSL
+char *strcasestr(const char *haystack, const char *needle) {
+	size_t length_needle;
+	size_t length_haystack;
+	size_t i;
+
+	if (!haystack || !needle)
+		return NULL;
+
+	length_needle = strlen(needle);
+	length_haystack = strlen(haystack) - length_needle + 1;
+
+	for (i = 0; i < length_haystack; i++)
+	{
+		size_t j;
+
+		for (j = 0; j < length_needle; j++)
+		{
+			unsigned char c1;
+			unsigned char c2;
+
+			c1 = haystack[i+j];
+			c2 = needle[j];
+			if (toupper(c1) != toupper(c2))
+				goto next;
+		}
+		return (char *) haystack + i;
+		next:
+			;
+	}
+
+	return NULL;
+}
+#endif