philippe44 6 роки тому
батько
коміт
978afb363c
25 змінених файлів з 1409 додано та 11 видалено
  1. 4 3
      CMakeLists.txt
  2. 9 0
      Makefile
  3. 18 0
      README.md
  4. 0 8
      component.mk
  5. 6 0
      components/codecs/component.mk
  6. 5 0
      main/CMakeLists.txt
  7. 0 0
      main/Kconfig.projbuild
  8. 0 0
      main/buffer.c
  9. 9 0
      main/component.mk
  10. 3 0
      main/decode.c
  11. 0 0
      main/esp32.c
  12. 643 0
      main/faad.c
  13. 296 0
      main/flac.c
  14. 416 0
      main/mad.c
  15. 0 0
      main/main.c
  16. 0 0
      main/output.c
  17. 0 0
      main/output_dac.c
  18. 0 0
      main/output_pack.c
  19. 0 0
      main/pcm.c
  20. 0 0
      main/scan.c
  21. 0 0
      main/slimproto.c
  22. 0 0
      main/slimproto.h
  23. 0 0
      main/squeezelite.h
  24. 0 0
      main/stream.c
  25. 0 0
      main/utils.c

+ 4 - 3
CMakeLists.txt

@@ -1,5 +1,6 @@
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
 cmake_minimum_required(VERSION 3.5)
-set(COMPONENT_SRCS "scan.c")
-set(COMPONENT_ADD_INCLUDEDIRS ".")
 
-register_component()
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(squeezelite)

+ 9 - 0
Makefile

@@ -0,0 +1,9 @@
+#
+# This is a project Makefile. It is assumed the directory this Makefile resides in is a
+# project subdirectory.
+#
+
+PROJECT_NAME := squeezelite
+
+include $(IDF_PATH)/make/project.mk
+

+ 18 - 0
README.md

@@ -0,0 +1,18 @@
+Adding squeezelite
+ - libmad must be in a separated component otherwise linker whines about long call 
+ - libfaad requires -mlongcalls
+ - set IDF_PATH=/home/esp-idf
+ - set ESPPORT=COM9
+ - change <esp-idf>\components\partition_table\partitions_singleapp.csv to 2M instead of 1M (or more)
+ 
+# Wifi SCAN Example
+
+This example shows how to use scan of ESP32.
+
+We have two way to scan, fast scan and all channel scan:
+
+* fast scan: in this mode, scan will finish after find match AP even didn't scan all the channel, you can set thresholds for signal and authmode, it will ignore the AP which below the thresholds.
+
+* all channel scan : scan will end after checked all the channel, it will store four of the whole matched AP, you can set the sort method base on rssi or authmode, after scan, it will choose the best one 
+
+and try to connect. Because it need malloc dynamic memory to store match AP, and most of cases is to connect to better signal AP, so it needn't record all the AP matched. The number of matches is limited to 4 in order to limit dynamic memory usage. Four matches allows APs with the same SSID name and all possible auth modes - Open, WEP, WPA and WPA2.

+ 0 - 8
component.mk

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

+ 6 - 0
components/codecs/component.mk

@@ -0,0 +1,6 @@
+#
+# "main" pseudo-component makefile.
+#
+# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
+COMPONENT_ADD_LDFLAGS=$(COMPONENT_PATH)/lib/libmad.a $(COMPONENT_PATH)/lib/libesp-flac.a $(COMPONENT_PATH)/lib/libfaad.a -l$(COMPONENT_NAME)
+#COMPONENT_ADD_LDFLAGS=$(COMPONENT_PATH)/lib/libmad.a $(COMPONENT_PATH)/lib/libesp-flac.a -l$(COMPONENT_NAME)

+ 5 - 0
main/CMakeLists.txt

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

+ 0 - 0
Kconfig.projbuild → main/Kconfig.projbuild


+ 0 - 0
buffer.c → main/buffer.c


+ 9 - 0
main/component.mk

@@ -0,0 +1,9 @@
+#
+# "main" pseudo-component makefile.
+#
+# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
+CFLAGS += -DPOSIX -DLINKALL -DLOOPBACK -DDACAUDIO -I$(COMPONENT_PATH)/../libmad -I$(COMPONENT_PATH)/../libflac/include -I$(COMPONENT_PATH)/../faad2/include
+LDFLAGS += -s
+
+
+

+ 3 - 0
decode.c → main/decode.c

@@ -166,13 +166,16 @@ void decode_init(log_level level, const char *include_codecs, const char *exclud
 	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());
 

+ 0 - 0
esp32.c → main/esp32.c


+ 643 - 0
main/faad.c

@@ -0,0 +1,643 @@
+/* 
+ *  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"
+
+#include <neaacdec.h>
+
+#define WRAPBUF_LEN 2048
+
+struct chunk_table {
+	u32_t sample, offset;
+};
+
+struct faad {
+	NeAACDecHandle hAac;
+	u8_t type;
+	// following used for mp4 only
+	u32_t consume;
+	u32_t pos;
+	u32_t sample;
+	u32_t nextchunk;
+	void *stsc;
+	u32_t skip;
+	u64_t samples;
+	u64_t sttssamples;
+	bool  empty;
+	struct chunk_table *chunkinfo;
+	// faad symbols to be dynamically loaded
+#if !LINKALL
+	NeAACDecConfigurationPtr (* NeAACDecGetCurrentConfiguration)(NeAACDecHandle);
+	unsigned char (* NeAACDecSetConfiguration)(NeAACDecHandle, NeAACDecConfigurationPtr);
+	NeAACDecHandle (* NeAACDecOpen)(void);
+	void (* NeAACDecClose)(NeAACDecHandle);
+	long (* NeAACDecInit)(NeAACDecHandle, unsigned char *, unsigned long, unsigned long *, unsigned char *);
+	char (* NeAACDecInit2)(NeAACDecHandle, unsigned char *pBuffer, unsigned long, unsigned long *, unsigned char *);
+	void *(* NeAACDecDecode)(NeAACDecHandle, NeAACDecFrameInfo *, unsigned char *, unsigned long);
+	char *(* NeAACDecGetErrorMessage)(unsigned char);
+#endif
+};
+
+static struct faad *a;
+
+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;
+
+#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 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 IF_DIRECT(x)    { x }
+#define IF_PROCESS(x)
+#endif
+
+#if LINKALL
+#define NEAAC(h, fn, ...) (NeAACDec ## fn)(__VA_ARGS__)
+#else
+#define NEAAC(h, fn, ...) (h)->NeAACDec##fn(__VA_ARGS__)
+#endif
+
+// minimal code for mp4 file parsing to extract audio config and find media data
+
+// adapted from faad2/common/mp4ff
+u32_t mp4_desc_length(u8_t **buf) {
+	u8_t b;
+	u8_t num_bytes = 0;
+	u32_t length = 0;
+
+	do {
+		b = **buf;
+		*buf += 1;
+		num_bytes++;
+		length = (length << 7) | (b & 0x7f);
+	} while ((b & 0x80) && num_bytes < 4);
+
+	return length;
+}
+
+// read mp4 header to extract config data
+static int read_mp4_header(unsigned long *samplerate_p, unsigned char *channels_p) {
+	size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
+	char type[5];
+	u32_t len;
+
+	while (bytes >= 8) {
+		// count trak to find the first playable one
+		static unsigned trak, play;
+		u32_t consume;
+
+		len = unpackN((u32_t *)streambuf->readp);
+		memcpy(type, streambuf->readp + 4, 4);
+		type[4] = '\0';
+
+		if (!strcmp(type, "moov")) {
+			trak = 0;
+			play = 0;
+		}
+		if (!strcmp(type, "trak")) {
+			trak++;
+		}
+
+		// extract audio config from within esds and pass to DecInit2
+		if (!strcmp(type, "esds") && bytes > len) {
+			unsigned config_len;
+			u8_t *ptr = streambuf->readp + 12;
+			if (*ptr++ == 0x03) {
+				mp4_desc_length(&ptr);
+				ptr += 4;
+			} else {
+				ptr += 3;
+			}
+			mp4_desc_length(&ptr);
+			ptr += 13;
+			if (*ptr++ != 0x05) {
+				LOG_WARN("error parsing esds");
+				return -1;
+			}
+			config_len = mp4_desc_length(&ptr);
+			if (NEAAC(a, Init2, a->hAac, ptr, config_len, samplerate_p, channels_p) == 0) {
+				LOG_DEBUG("playable aac track: %u", trak);
+				play = trak;
+			}
+		}
+
+		// extract the total number of samples from stts
+		if (!strcmp(type, "stts") && bytes > len) {
+			u32_t i;
+			u8_t *ptr = streambuf->readp + 12;
+			u32_t entries = unpackN((u32_t *)ptr);
+			ptr += 4;
+			for (i = 0; i < entries; ++i) {
+				u32_t count = unpackN((u32_t *)ptr);
+				u32_t size = unpackN((u32_t *)(ptr + 4));
+				a->sttssamples += count * size;
+				ptr += 8;
+			}
+			LOG_DEBUG("total number of samples contained in stts: " FMT_u64, a->sttssamples);
+		}
+
+		// stash sample to chunk info, assume it comes before stco
+		if (!strcmp(type, "stsc") && bytes > len && !a->chunkinfo) {
+			a->stsc = malloc(len - 12);
+			if (a->stsc == NULL) {
+				LOG_WARN("malloc fail");
+				return -1;
+			}
+			memcpy(a->stsc, streambuf->readp + 12, len - 12);
+		}
+
+		// build offsets table from stco and stored stsc
+		if (!strcmp(type, "stco") && bytes > len && play == trak) {
+			u32_t i;
+			// extract chunk offsets
+			u8_t *ptr = streambuf->readp + 12;
+			u32_t entries = unpackN((u32_t *)ptr);
+			ptr += 4;
+			a->chunkinfo = malloc(sizeof(struct chunk_table) * (entries + 1));
+			if (a->chunkinfo == NULL) {
+				LOG_WARN("malloc fail");
+				return -1;
+			}
+			for (i = 0; i < entries; ++i) {
+				a->chunkinfo[i].offset = unpackN((u32_t *)ptr);
+				a->chunkinfo[i].sample = 0;
+				ptr += 4;
+			}
+			a->chunkinfo[i].sample = 0;
+			a->chunkinfo[i].offset = 0;
+			// fill in first sample id for each chunk from stored stsc
+			if (a->stsc) {
+				u32_t stsc_entries = unpackN((u32_t *)a->stsc);
+				u32_t sample = 0;
+				u32_t last = 0, last_samples = 0;
+				u8_t *ptr = (u8_t *)a->stsc + 4;
+				while (stsc_entries--) {
+					u32_t first = unpackN((u32_t *)ptr);
+					u32_t samples = unpackN((u32_t *)(ptr + 4));
+					if (last) {
+						for (i = last - 1; i < first - 1; ++i) {
+							a->chunkinfo[i].sample = sample;
+							sample += last_samples;
+						}
+					}
+					if (stsc_entries == 0) {
+						for (i = first - 1; i < entries; ++i) {
+							a->chunkinfo[i].sample = sample;
+							sample += samples;
+						}
+					}
+					last = first;
+					last_samples = samples;
+					ptr += 12;
+				}
+				free(a->stsc);
+				a->stsc = NULL;
+			}
+		}
+
+		// found media data, advance to start of first chunk and return
+		if (!strcmp(type, "mdat")) {
+			_buf_inc_readp(streambuf, 8);
+			a->pos += 8;
+			bytes  -= 8;
+			if (play) {
+				LOG_DEBUG("type: mdat len: %u pos: %u", len, a->pos);
+				if (a->chunkinfo && a->chunkinfo[0].offset > a->pos) {
+					u32_t skip = a->chunkinfo[0].offset - a->pos; 	
+					LOG_DEBUG("skipping: %u", skip);
+					if (skip <= bytes) {
+						_buf_inc_readp(streambuf, skip);
+						a->pos += skip;
+					} else {
+						a->consume = skip;
+					}
+				}
+				a->sample = a->nextchunk = 1;
+				return 1;
+			} else {
+				LOG_DEBUG("type: mdat len: %u, no playable track found", len);
+				return -1;
+			}
+		}
+
+		// parse key-value atoms within ilst ---- entries to get encoder padding within iTunSMPB entry for gapless
+		if (!strcmp(type, "----") && bytes > len) {
+			u8_t *ptr = streambuf->readp + 8;
+			u32_t remain = len - 8, size;
+			if (!memcmp(ptr + 4, "mean", 4) && (size = unpackN((u32_t *)ptr)) < remain) {
+				ptr += size; remain -= size;
+			}
+			if (!memcmp(ptr + 4, "name", 4) && (size = unpackN((u32_t *)ptr)) < remain && !memcmp(ptr + 12, "iTunSMPB", 8)) {
+				ptr += size; remain -= size;
+			}
+			if (!memcmp(ptr + 4, "data", 4) && remain > 16 + 48) {
+				// data is stored as hex strings: 0 start end samples
+				u32_t b, c; u64_t d;
+				if (sscanf((const char *)(ptr + 16), "%x %x %x " FMT_x64, &b, &b, &c, &d) == 4) {
+					LOG_DEBUG("iTunSMPB start: %u end: %u samples: " FMT_u64, b, c, d);
+					if (a->sttssamples && a->sttssamples < b + c + d) {
+						LOG_DEBUG("reducing samples as stts count is less");
+						d = a->sttssamples - (b + c);
+					}
+					a->skip = b;
+					a->samples = d;
+				}
+			}
+		}
+
+		// default to consuming entire box
+		consume = len;
+
+		// read into these boxes so reduce consume
+		if (!strcmp(type, "moov") || !strcmp(type, "trak") || !strcmp(type, "mdia") || !strcmp(type, "minf") || !strcmp(type, "stbl") ||
+			!strcmp(type, "udta") || !strcmp(type, "ilst")) {
+			consume = 8;
+		}
+		// special cases which mix mix data in the enclosing box which we want to read into
+		if (!strcmp(type, "stsd")) consume = 16;
+		if (!strcmp(type, "mp4a")) consume = 36;
+		if (!strcmp(type, "meta")) consume = 12;
+
+		// consume rest of box if it has been parsed (all in the buffer) or is not one we want to parse
+		if (bytes >= consume) {
+			LOG_DEBUG("type: %s len: %u consume: %u", type, len, consume);
+			_buf_inc_readp(streambuf, consume);
+			a->pos += consume;
+			bytes -= consume;
+		} else if ( !(!strcmp(type, "esds") || !strcmp(type, "stts") || !strcmp(type, "stsc") || 
+					 !strcmp(type, "stco") || !strcmp(type, "----")) ) {
+			LOG_DEBUG("type: %s len: %u consume: %u - partial consume: %u", type, len, consume, bytes);
+			_buf_inc_readp(streambuf, bytes);
+			a->pos += bytes;
+			a->consume = consume - bytes;
+			break;
+		} else {
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static decode_state faad_decode(void) {
+	size_t bytes_total;
+	size_t bytes_wrap;
+	NeAACDecFrameInfo info;
+	s32_t *iptr;
+	bool endstream;
+	frames_t frames;
+
+	LOCK_S;
+	bytes_total = _buf_used(streambuf);
+	bytes_wrap  = min(bytes_total, _buf_cont_read(streambuf));
+
+	if (stream.state <= DISCONNECT && !bytes_total) {
+		UNLOCK_S;
+		return DECODE_COMPLETE;
+	}
+
+	if (a->consume) {
+		u32_t consume = min(a->consume, bytes_wrap);
+		LOG_DEBUG("consume: %u of %u", consume, a->consume);
+		_buf_inc_readp(streambuf, consume);
+		a->pos += consume;
+		a->consume -= consume;
+		UNLOCK_S;
+		return DECODE_RUNNING;
+	}
+
+	if (decode.new_stream) {
+		int found = 0;
+		static unsigned char channels;
+		static unsigned long samplerate;
+
+		if (a->type == '2') {
+
+			// adts stream - seek for header
+			while (bytes_wrap >= 2 && (*(streambuf->readp) != 0xFF || (*(streambuf->readp + 1) & 0xF6) != 0xF0)) {
+				_buf_inc_readp(streambuf, 1);
+				bytes_total--;
+				bytes_wrap--;
+			}
+			
+			if (bytes_wrap >= 2) {
+				long n = NEAAC(a, Init, a->hAac, streambuf->readp, bytes_wrap, &samplerate, &channels);
+				if (n < 0) {
+					found = -1;
+				} else {
+					_buf_inc_readp(streambuf, n);
+					found = 1;
+				}
+			}
+
+		} else {
+
+			// mp4 - read header
+			found = read_mp4_header(&samplerate, &channels);
+		}
+
+		if (found == 1) {
+
+			LOG_INFO("samplerate: %u channels: %u", samplerate, channels);
+			bytes_total = _buf_used(streambuf);
+			bytes_wrap  = min(bytes_total, _buf_cont_read(streambuf));
+
+			LOCK_O;
+			LOG_INFO("setting track_start");
+			output.next_sample_rate = decode_newstream(samplerate, output.supported_rates);
+			IF_DSD( output.next_fmt = PCM; )
+			output.track_start = outputbuf->writep;
+			if (output.fade_mode) _checkfade(true);
+			decode.new_stream = false;
+			UNLOCK_O;
+
+		} else if (found == -1) {
+
+			LOG_WARN("error reading stream header");
+			UNLOCK_S;
+			return DECODE_ERROR;
+
+		} else {
+
+			// not finished header parsing come back next time
+			UNLOCK_S;
+			return DECODE_RUNNING;
+		}
+	}
+
+	if (bytes_wrap < WRAPBUF_LEN && bytes_total > WRAPBUF_LEN) {
+
+		// make a local copy of frames which may have wrapped round the end of streambuf
+		u8_t buf[WRAPBUF_LEN];
+		memcpy(buf, streambuf->readp, bytes_wrap);
+		memcpy(buf + bytes_wrap, streambuf->buf, WRAPBUF_LEN - bytes_wrap);
+
+		iptr = NEAAC(a, Decode, a->hAac, &info, buf, WRAPBUF_LEN);
+
+	} else {
+
+		iptr = NEAAC(a, Decode, a->hAac, &info, streambuf->readp, bytes_wrap);
+	}
+
+	if (info.error) {
+		LOG_WARN("error: %u %s", info.error, NEAAC(a, GetErrorMessage, info.error));
+	}
+
+	endstream = false;
+
+	// mp4 end of chunk - skip to next offset
+	if (a->chunkinfo && a->chunkinfo[a->nextchunk].offset && a->sample++ == a->chunkinfo[a->nextchunk].sample) {
+
+		if (a->chunkinfo[a->nextchunk].offset > a->pos) {
+			u32_t skip = a->chunkinfo[a->nextchunk].offset - a->pos;
+			if (skip != info.bytesconsumed) {
+				LOG_DEBUG("skipping to next chunk pos: %u consumed: %u != skip: %u", a->pos, info.bytesconsumed, skip);
+			}
+			if (bytes_total >= skip) {
+				_buf_inc_readp(streambuf, skip);
+				a->pos += skip;
+			} else {
+				a->consume = skip;
+			}
+			a->nextchunk++;
+		} else {
+			LOG_ERROR("error: need to skip backwards!");
+			endstream = true;
+		}
+
+	// adts and mp4 when not at end of chunk 
+	} else if (info.bytesconsumed != 0) {
+
+		_buf_inc_readp(streambuf, info.bytesconsumed);
+		a->pos += info.bytesconsumed;
+
+	// error which doesn't advance streambuf - end
+	} else {
+		endstream = true;
+	}
+
+	UNLOCK_S;
+
+	if (endstream) {
+		LOG_WARN("unable to decode further");
+		return DECODE_ERROR;
+	}
+
+	if (!info.samples) {
+		a->empty = true;
+		return DECODE_RUNNING;
+	}
+
+	frames = info.samples / info.channels;
+
+	if (a->skip) {
+		u32_t skip;
+		if (a->empty) {
+			a->empty = false;
+			a->skip -= frames;
+			LOG_DEBUG("gapless: first frame empty, skipped %u frames at start", frames);
+		}
+		skip = min(frames, a->skip);
+		LOG_DEBUG("gapless: skipping %u frames at start", skip);
+		frames -= skip;
+		a->skip -= skip;
+		iptr += skip * info.channels;
+	}
+
+	if (a->samples) {
+		if (a->samples < frames) {
+			LOG_DEBUG("gapless: trimming %u frames from end", frames - a->samples);
+			frames = (frames_t)a->samples;
+		}
+		a->samples -= frames;
+	}
+
+	LOG_SDEBUG("write %u frames", frames);
+
+	LOCK_O_direct;
+
+	while (frames > 0) {
+		frames_t f;
+		frames_t count;
+		s32_t *optr;
+
+		IF_DIRECT(
+			f = _buf_cont_write(outputbuf) / BYTES_PER_FRAME;
+			optr = (s32_t *)outputbuf->writep;
+		);
+		IF_PROCESS(
+			f = process.max_in_frames;
+			optr = (s32_t *)process.inbuf;
+		);
+
+		f = min(f, frames);
+		count = f;
+		
+		if (info.channels == 2) {
+			while (count--) {
+				*optr++ = *iptr++ << 8;
+				*optr++ = *iptr++ << 8;
+			}
+		} else if (info.channels == 1) {
+			while (count--) {
+				*optr++ = *iptr << 8;
+				*optr++ = *iptr++ << 8;
+			}
+		} else {
+			LOG_WARN("unsupported number of channels");
+		}
+
+		frames -= f;
+
+		IF_DIRECT(
+			_buf_inc_writep(outputbuf, f * BYTES_PER_FRAME);
+		);
+		IF_PROCESS(
+			process.in_frames = f;
+			if (frames) LOG_ERROR("unhandled case");
+		);
+	}
+
+	UNLOCK_O_direct;
+
+	return DECODE_RUNNING;
+}
+
+static void faad_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
+	NeAACDecConfigurationPtr conf;
+
+	LOG_INFO("opening %s stream", size == '2' ? "adts" : "mp4");
+
+	a->type = size;
+	a->pos = a->consume = a->sample = a->nextchunk = 0;
+
+	if (a->chunkinfo) {
+		free(a->chunkinfo);
+	}
+	if (a->stsc) {
+		free(a->stsc);
+	}
+	a->chunkinfo = NULL;
+	a->stsc = NULL;
+	a->skip = 0;
+	a->samples = 0;
+	a->sttssamples = 0;
+	a->empty = false;
+
+	if (a->hAac) {
+		NEAAC(a, Close, a->hAac);
+	}
+	a->hAac = NEAAC(a, Open);
+
+	conf = NEAAC(a, GetCurrentConfiguration, a->hAac);
+
+	conf->outputFormat = FAAD_FMT_24BIT;
+	conf->downMatrix = 1;
+
+	if (!NEAAC(a, SetConfiguration, a->hAac, conf)) {
+		LOG_WARN("error setting config");
+	};
+}
+
+static void faad_close(void) {
+	NEAAC(a, Close, a->hAac);
+	a->hAac = NULL;
+	if (a->chunkinfo) {
+		free(a->chunkinfo);
+		a->chunkinfo = NULL;
+	}
+	if (a->stsc) {
+		free(a->stsc);
+		a->stsc = NULL;
+	}
+}
+
+static bool load_faad() {
+#if !LINKALL
+	void *handle = dlopen(LIBFAAD, RTLD_NOW);
+	char *err;
+
+	if (!handle) {
+		LOG_INFO("dlerror: %s", dlerror());
+		return false;
+	}
+
+	a->NeAACDecGetCurrentConfiguration = dlsym(handle, "NeAACDecGetCurrentConfiguration");
+	a->NeAACDecSetConfiguration = dlsym(handle, "NeAACDecSetConfiguration");
+	a->NeAACDecOpen = dlsym(handle, "NeAACDecOpen");
+	a->NeAACDecClose = dlsym(handle, "NeAACDecClose");
+	a->NeAACDecInit = dlsym(handle, "NeAACDecInit");
+	a->NeAACDecInit2 = dlsym(handle, "NeAACDecInit2");
+	a->NeAACDecDecode = dlsym(handle, "NeAACDecDecode");
+	a->NeAACDecGetErrorMessage = dlsym(handle, "NeAACDecGetErrorMessage");
+
+	if ((err = dlerror()) != NULL) {
+		LOG_INFO("dlerror: %s", err);		
+		return false;
+	}
+
+	LOG_INFO("loaded "LIBFAAD"");
+#endif
+
+	return true;
+}
+
+struct codec *register_faad(void) {
+	static struct codec ret = { 
+		'a',          // id
+		"aac",        // types
+		WRAPBUF_LEN,  // min read
+		20480,        // min space
+		faad_open,    // open
+		faad_close,   // close
+		faad_decode,  // decode
+	};
+
+	a = malloc(sizeof(struct faad));
+	if (!a) {
+		return NULL;
+	}
+
+	a->hAac = NULL;
+	a->chunkinfo = NULL;
+	a->stsc = NULL;
+
+	if (!load_faad()) {
+		return NULL;
+	}
+
+	LOG_INFO("using faad to decode aac");
+	return &ret;
+}

+ 296 - 0
main/flac.c

@@ -0,0 +1,296 @@
+/* 
+ *  Squeezelite - lightweight headless squeezeplay emulator for linux
+ *
+ *  (c) Adrian Smith 2012, triode1@btinternet.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"
+
+#include <FLAC/stream_decoder.h>
+
+struct flac {
+	FLAC__StreamDecoder *decoder;
+#if !LINKALL
+	// FLAC symbols to be dynamically loaded
+	const char **FLAC__StreamDecoderErrorStatusString;
+	const char **FLAC__StreamDecoderStateString;
+	FLAC__StreamDecoder * (* FLAC__stream_decoder_new)(void);
+	FLAC__bool (* FLAC__stream_decoder_reset)(FLAC__StreamDecoder *decoder);
+	void (* FLAC__stream_decoder_delete)(FLAC__StreamDecoder *decoder);
+	FLAC__StreamDecoderInitStatus (* FLAC__stream_decoder_init_stream)(
+		FLAC__StreamDecoder *decoder,
+		FLAC__StreamDecoderReadCallback read_callback,
+		FLAC__StreamDecoderSeekCallback seek_callback,
+		FLAC__StreamDecoderTellCallback tell_callback,
+		FLAC__StreamDecoderLengthCallback length_callback,
+		FLAC__StreamDecoderEofCallback eof_callback,
+		FLAC__StreamDecoderWriteCallback write_callback,
+		FLAC__StreamDecoderMetadataCallback metadata_callback,
+		FLAC__StreamDecoderErrorCallback error_callback,
+		void *client_data
+	);
+	FLAC__bool (* FLAC__stream_decoder_process_single)(FLAC__StreamDecoder *decoder);
+	FLAC__StreamDecoderState (* FLAC__stream_decoder_get_state)(const FLAC__StreamDecoder *decoder);
+#endif
+};
+
+static struct flac *f;
+
+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;
+
+#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 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 IF_DIRECT(x)    { x }
+#define IF_PROCESS(x)
+#endif
+
+#if LINKALL
+#define FLAC(h, fn, ...) (FLAC__ ## fn)(__VA_ARGS__)
+#define FLAC_A(h, a)     (FLAC__ ## a)
+#else
+#define FLAC(h, fn, ...) (h)->FLAC__##fn(__VA_ARGS__)
+#define FLAC_A(h, a)     (h)->FLAC__ ## a
+#endif
+
+static FLAC__StreamDecoderReadStatus read_cb(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *want, void *client_data) {
+	size_t bytes;
+	bool end;
+
+	LOCK_S;
+	bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
+	bytes = min(bytes, *want);
+	end = (stream.state <= DISCONNECT && bytes == 0);
+
+	memcpy(buffer, streambuf->readp, bytes);
+	_buf_inc_readp(streambuf, bytes);
+	UNLOCK_S;
+
+	*want = bytes;
+
+	return end ? FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM : FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
+}
+
+static FLAC__StreamDecoderWriteStatus write_cb(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame,
+											   const FLAC__int32 *const buffer[], void *client_data) {
+
+	size_t frames = frame->header.blocksize;
+	unsigned bits_per_sample = frame->header.bits_per_sample;
+	unsigned channels = frame->header.channels;
+
+	FLAC__int32 *lptr = (FLAC__int32 *)buffer[0];
+	FLAC__int32 *rptr = (FLAC__int32 *)buffer[channels > 1 ? 1 : 0];
+	
+	if (decode.new_stream) {
+		LOCK_O;
+		LOG_INFO("setting track_start");
+		output.track_start = outputbuf->writep;
+		decode.new_stream = false;
+
+#if DSD
+#if SL_LITTLE_ENDIAN
+#define MARKER_OFFSET 2
+#else
+#define MARKER_OFFSET 1
+#endif		
+		if (bits_per_sample == 24 && is_stream_dop(((u8_t *)lptr) + MARKER_OFFSET, ((u8_t *)rptr) + MARKER_OFFSET, 4, frames)) {
+			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 = frame->header.sample_rate;
+			output.fade = FADE_INACTIVE;
+		} else {
+			output.next_sample_rate = decode_newstream(frame->header.sample_rate, output.supported_rates);
+			output.next_fmt = PCM;
+			if (output.fade_mode) _checkfade(true);
+		}
+#else
+		output.next_sample_rate = decode_newstream(frame->header.sample_rate, output.supported_rates);
+		if (output.fade_mode) _checkfade(true);
+#endif
+
+		UNLOCK_O;
+	}
+
+	LOCK_O_direct;
+
+	while (frames > 0) {
+		frames_t f;
+		frames_t count;
+		s32_t *optr;
+
+		IF_DIRECT( 
+			optr = (s32_t *)outputbuf->writep; 
+			f = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME; 
+		);
+		IF_PROCESS(
+			optr = (s32_t *)process.inbuf;
+			f = process.max_in_frames;
+		);
+
+		f = min(f, frames);
+
+		count = f;
+
+		if (bits_per_sample == 8) {
+			while (count--) {
+				*optr++ = *lptr++ << 24;
+				*optr++ = *rptr++ << 24;
+			}
+		} else if (bits_per_sample == 16) {
+			while (count--) {
+				*optr++ = *lptr++ << 16;
+				*optr++ = *rptr++ << 16;
+			}
+		} else if (bits_per_sample == 24) {
+			while (count--) {
+				*optr++ = *lptr++ << 8;
+				*optr++ = *rptr++ << 8;
+			}
+		} else if (bits_per_sample == 32) {
+			while (count--) {
+				*optr++ = *lptr++;
+				*optr++ = *rptr++;
+			}
+		} else {
+			LOG_ERROR("unsupported bits per sample: %u", bits_per_sample);
+		}
+
+		frames -= f;
+
+		IF_DIRECT(
+			_buf_inc_writep(outputbuf, f * BYTES_PER_FRAME);
+		);
+		IF_PROCESS(
+			process.in_frames = f;
+			if (frames) LOG_ERROR("unhandled case");
+		);
+	}
+
+	UNLOCK_O_direct;
+
+	return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+}
+
+static void error_cb(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) {
+	LOG_INFO("flac error: %s", FLAC_A(f, StreamDecoderErrorStatusString)[status]);
+}
+
+static void flac_open(u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t endianness) {
+	if (f->decoder) {
+		FLAC(f, stream_decoder_reset, f->decoder);
+	} else {
+		f->decoder = FLAC(f, stream_decoder_new);
+	}
+	FLAC(f, stream_decoder_init_stream, f->decoder, &read_cb, NULL, NULL, NULL, NULL, &write_cb, NULL, &error_cb, NULL);
+}
+
+static void flac_close(void) {
+	FLAC(f, stream_decoder_delete, f->decoder);
+	f->decoder = NULL;
+}
+
+static decode_state flac_decode(void) {
+	bool ok = FLAC(f, stream_decoder_process_single, f->decoder);
+	FLAC__StreamDecoderState state = FLAC(f, stream_decoder_get_state, f->decoder);
+	
+	if (!ok && state != FLAC__STREAM_DECODER_END_OF_STREAM) {
+		LOG_INFO("flac error: %s", FLAC_A(f, StreamDecoderStateString)[state]);
+	};
+	
+	if (state == FLAC__STREAM_DECODER_END_OF_STREAM) {
+		return DECODE_COMPLETE;
+	} else if (state > FLAC__STREAM_DECODER_END_OF_STREAM) {
+		return DECODE_ERROR;
+	} else {
+		return DECODE_RUNNING;
+	}
+}
+
+static bool load_flac() {
+#if !LINKALL
+	void *handle = dlopen(LIBFLAC, RTLD_NOW);
+	char *err;
+
+	if (!handle) {
+		LOG_INFO("dlerror: %s", dlerror());
+		return false;
+	}
+
+	f->FLAC__StreamDecoderErrorStatusString = dlsym(handle, "FLAC__StreamDecoderErrorStatusString");
+	f->FLAC__StreamDecoderStateString = dlsym(handle, "FLAC__StreamDecoderStateString");
+	f->FLAC__stream_decoder_new = dlsym(handle, "FLAC__stream_decoder_new");
+	f->FLAC__stream_decoder_reset = dlsym(handle, "FLAC__stream_decoder_reset");
+	f->FLAC__stream_decoder_delete = dlsym(handle, "FLAC__stream_decoder_delete");
+	f->FLAC__stream_decoder_init_stream = dlsym(handle, "FLAC__stream_decoder_init_stream");
+	f->FLAC__stream_decoder_process_single = dlsym(handle, "FLAC__stream_decoder_process_single");
+	f->FLAC__stream_decoder_get_state = dlsym(handle, "FLAC__stream_decoder_get_state");
+
+	if ((err = dlerror()) != NULL) {
+		LOG_INFO("dlerror: %s", err);		
+		return false;
+	}
+
+	LOG_INFO("loaded "LIBFLAC);
+#endif
+
+	return true;
+}
+
+struct codec *register_flac(void) {
+	static struct codec ret = { 
+		'f',          // id
+		"flc",        // types
+		16384,        // min read
+		204800,       // min space
+		flac_open,    // open
+		flac_close,   // close
+		flac_decode,  // decode
+	};
+
+	f = malloc(sizeof(struct flac));
+	if (!f) {
+		return NULL;
+	}
+
+	f->decoder = NULL;
+
+	if (!load_flac()) {
+		return NULL;
+	}
+
+	LOG_INFO("using flac to decode flc");
+	return &ret;
+}

+ 416 - 0
main/mad.c

@@ -0,0 +1,416 @@
+/* 
+ *  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"
+
+#include <mad.h>
+
+#define MAD_DELAY 529
+
+#define READBUF_SIZE 2048 // local buffer used by decoder: FIXME merge with any other decoders needing one?
+
+struct mad {
+	u8_t *readbuf;
+	unsigned readbuf_len;
+	struct mad_stream stream;
+	struct mad_frame frame;
+	struct mad_synth synth;
+	enum mad_error last_error;
+	// for lame gapless processing
+	int checktags;
+	u32_t consume;
+	u32_t skip;
+	u64_t samples;
+	u32_t padding;
+#if !LINKALL
+	// mad symbols to be dynamically loaded
+	void (* mad_stream_init)(struct mad_stream *);
+	void (* mad_frame_init)(struct mad_frame *);
+	void (* mad_synth_init)(struct mad_synth *);
+	void (* mad_frame_finish)(struct mad_frame *);
+	void (* mad_stream_finish)(struct mad_stream *);
+	void (* mad_stream_buffer)(struct mad_stream *, unsigned char const *, unsigned long);
+	int  (* mad_frame_decode)(struct mad_frame *, struct mad_stream *);
+	void (* mad_synth_frame)(struct mad_synth *, struct mad_frame const *);
+	char const *(* mad_stream_errorstr)(struct mad_stream const *);
+#endif
+};
+
+static struct mad *m;
+
+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;
+
+#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 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 IF_DIRECT(x)    { x }
+#define IF_PROCESS(x)
+#endif
+
+#if LINKALL
+#define MAD(h, fn, ...) (mad_ ## fn)(__VA_ARGS__)
+#else
+#define MAD(h, fn, ...) (h)->mad_##fn(__VA_ARGS__)
+#endif
+
+// based on libmad minimad.c scale
+static inline u32_t scale(mad_fixed_t sample) {
+	sample += (1L << (MAD_F_FRACBITS - 24));
+	
+	if (sample >= MAD_F_ONE)
+		sample = MAD_F_ONE - 1;
+	else if (sample < -MAD_F_ONE)
+		sample = -MAD_F_ONE;
+	
+	return (s32_t)(sample >> (MAD_F_FRACBITS + 1 - 24)) << 8;
+}
+
+// check for id3.2 tag at start of file - http://id3.org/id3v2.4.0-structure, return length
+static unsigned _check_id3_tag(size_t bytes) {
+	u8_t *ptr = streambuf->readp;
+	u32_t size = 0;
+
+	if (bytes > 10 && *ptr == 'I' && *(ptr+1) == 'D' && *(ptr+2) == '3') {
+		// size is encoded as syncsafe integer, add 10 if footer present
+		if (*(ptr+6) < 0x80 && *(ptr+7) < 0x80 && *(ptr+8) < 0x80 && *(ptr+9) < 0x80) {
+			size = 10 + (*(ptr+6) << 21) + (*(ptr+7) << 14) + (*(ptr+8) << 7) + *(ptr+9) + ((*(ptr+5) & 0x10) ? 10 : 0);
+			LOG_DEBUG("id3.2 tag len: %u", size);
+		}
+	}
+
+	return size;
+}
+
+// check for lame gapless params, don't advance streambuf
+static void _check_lame_header(size_t bytes) {
+	u8_t *ptr = streambuf->readp;
+
+	if (*ptr == 0xff && (*(ptr+1) & 0xf0) == 0xf0 && bytes > 180) {
+
+		u32_t frame_count = 0, enc_delay = 0, enc_padding = 0;
+		u8_t flags;
+
+		// 2 channels
+		if (!memcmp(ptr + 36, "Xing", 4) || !memcmp(ptr + 36, "Info", 4)) {
+			ptr += 36 + 7;
+		// mono	
+		} else if (!memcmp(ptr + 21, "Xing", 4) || !memcmp(ptr + 21, "Info", 4)) {
+			ptr += 21 + 7;
+		}
+
+		flags = *ptr;
+
+		if (flags & 0x01) {
+			frame_count = unpackN((u32_t *)(ptr + 1));
+			ptr += 4;
+		}
+		if (flags & 0x02) ptr += 4;
+		if (flags & 0x04) ptr += 100;
+		if (flags & 0x08) ptr += 4;
+
+		if (!!memcmp(ptr+1, "LAME", 4)) {
+			return;
+		}
+
+		ptr += 22;
+
+		enc_delay   = (*ptr << 4 | *(ptr + 1) >> 4) + MAD_DELAY;
+		enc_padding = (*(ptr + 1) & 0xF) << 8 | *(ptr + 2);
+		enc_padding = enc_padding > MAD_DELAY ? enc_padding - MAD_DELAY : 0;
+
+		// add one frame to initial skip for this (empty) frame
+		m->skip    = enc_delay + 1152;
+		m->samples = frame_count * 1152 - enc_delay - enc_padding;
+		m->padding = enc_padding;
+		
+		LOG_INFO("gapless: skip: %u samples: " FMT_u64 " delay: %u padding: %u", m->skip, m->samples, enc_delay, enc_padding);
+	}
+}
+
+static decode_state mad_decode(void) {
+	size_t bytes;
+	bool eos = false;
+
+	LOCK_S;
+	bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
+	
+	if (m->checktags) {
+		if (m->checktags == 1) {
+			m->consume = _check_id3_tag(bytes);
+			m->checktags = 2;
+		}
+		if (m->consume) {
+			u32_t consume = min(m->consume, bytes);
+			LOG_DEBUG("consume: %u of %u", consume, m->consume);
+			_buf_inc_readp(streambuf, consume);
+			m->consume -= consume;
+			UNLOCK_S;
+			return DECODE_RUNNING;
+		}
+		if (m->checktags == 2) {
+			if (!stream.meta_interval) {
+				_check_lame_header(bytes);
+			}
+			m->checktags = 0;
+		}
+	}
+
+	if (m->stream.next_frame && m->readbuf_len) {
+		m->readbuf_len -= m->stream.next_frame - m->readbuf;
+		memmove(m->readbuf, m->stream.next_frame, m->readbuf_len);
+	}
+
+	bytes = min(bytes, READBUF_SIZE - m->readbuf_len);
+	memcpy(m->readbuf + m->readbuf_len, streambuf->readp, bytes);
+	m->readbuf_len += bytes;
+	_buf_inc_readp(streambuf, bytes);
+
+	if (stream.state <= DISCONNECT && _buf_used(streambuf) == 0) {
+		eos = true;
+		LOG_DEBUG("end of stream");
+		memset(m->readbuf + m->readbuf_len, 0, MAD_BUFFER_GUARD);
+		m->readbuf_len += MAD_BUFFER_GUARD;
+	}
+
+	UNLOCK_S;
+
+	MAD(m, stream_buffer, &m->stream, m->readbuf, m->readbuf_len);
+
+	while (true) {
+		size_t frames;
+		s32_t *iptrl;
+		s32_t *iptrr;
+		unsigned max_frames;
+
+		if (MAD(m, frame_decode, &m->frame, &m->stream) == -1) {
+			decode_state ret;
+			if (!eos && m->stream.error == MAD_ERROR_BUFLEN) {
+				ret = DECODE_RUNNING;
+			} else if (eos && (m->stream.error == MAD_ERROR_BUFLEN || m->stream.error == MAD_ERROR_LOSTSYNC
+					|| m->stream.error == MAD_ERROR_BADBITRATE)) {
+				ret = DECODE_COMPLETE;
+			} else if (!MAD_RECOVERABLE(m->stream.error)) {
+				LOG_INFO("mad_frame_decode error: %s - stopping decoder", MAD(m, stream_errorstr, &m->stream));
+				ret = DECODE_COMPLETE;
+			} else {
+				if (m->stream.error != m->last_error) {
+					// suppress repeat error messages
+					LOG_DEBUG("mad_frame_decode error: %s", MAD(m, stream_errorstr, &m->stream));
+				}
+				ret = DECODE_RUNNING;
+			}
+			m->last_error = m->stream.error;
+			return ret;
+		};
+
+		MAD(m, synth_frame, &m->synth, &m->frame);
+
+		if (decode.new_stream) {
+			LOCK_O;
+			LOG_INFO("setting track_start");
+			output.next_sample_rate = decode_newstream(m->synth.pcm.samplerate, output.supported_rates);
+			IF_DSD(	output.next_fmt = PCM; )
+			output.track_start = outputbuf->writep;
+			if (output.fade_mode) _checkfade(true);
+			decode.new_stream = false;
+			UNLOCK_O;
+		}
+
+		LOCK_O_direct;
+
+		IF_DIRECT(
+			max_frames = _buf_space(outputbuf) / BYTES_PER_FRAME;
+		);
+		IF_PROCESS(
+			max_frames = process.max_in_frames - process.in_frames;
+		);
+		
+		if (m->synth.pcm.length > max_frames) {
+			LOG_WARN("too many samples - dropping samples");
+			m->synth.pcm.length = max_frames;
+		}
+		
+		frames = m->synth.pcm.length;
+		iptrl = m->synth.pcm.samples[0];
+		iptrr = m->synth.pcm.samples[ m->synth.pcm.channels - 1 ];
+
+		if (m->skip) {
+			u32_t skip = min(m->skip, frames);
+			LOG_DEBUG("gapless: skipping %u frames at start", skip);
+			frames -= skip;
+			m->skip -= skip;
+			iptrl += skip;
+			iptrr += skip;
+		}
+
+		if (m->samples) {
+			if (m->samples < frames) {
+				LOG_DEBUG("gapless: trimming %u frames from end", frames - m->samples);
+				frames = (size_t)m->samples;
+			}
+			m->samples -= frames;
+			if (m->samples > 0 && eos && !(m->stream.next_frame[0] == 0xff && (m->stream.next_frame[1] & 0xf0) == 0xf0)) {
+				// this is the last frame to be decoded, but more samples expected so we must have skipped, remove padding
+				// note this only works if the padding is less than one frame of 1152 bytes otherswise some gap will remain
+				LOG_DEBUG("gapless: early end - trimming padding from end");
+				if (frames >= m->padding) {
+					frames -= m->padding;
+				} else {
+					frames = 0;
+				}
+				m->samples = 0;
+			}
+		}
+
+		LOG_SDEBUG("write %u frames", frames);
+
+		while (frames > 0) {
+			size_t f, count;
+			s32_t *optr;
+
+			IF_DIRECT(
+				f = min(frames, _buf_cont_write(outputbuf) / BYTES_PER_FRAME);
+				optr = (s32_t *)outputbuf->writep;
+			);
+			IF_PROCESS(
+				f = min(frames, process.max_in_frames - process.in_frames);
+				optr = (s32_t *)((u8_t *)process.inbuf + process.in_frames * BYTES_PER_FRAME);
+			);
+
+			count = f;
+
+			while (count--) {
+				*optr++ = scale(*iptrl++);
+				*optr++ = scale(*iptrr++);
+			}
+
+			frames -= f;
+
+			IF_DIRECT(
+				_buf_inc_writep(outputbuf, f * BYTES_PER_FRAME);
+			);
+			IF_PROCESS(
+				process.in_frames += f;
+			);
+		}
+
+		UNLOCK_O_direct;
+	}
+
+	return eos ? DECODE_COMPLETE : DECODE_RUNNING;
+}
+
+static void mad_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
+	if (!m->readbuf) {
+		m->readbuf = malloc(READBUF_SIZE + MAD_BUFFER_GUARD);
+	}
+	m->checktags = 1;
+	m->consume = 0;
+	m->skip = MAD_DELAY;
+	m->samples = 0;
+	m->readbuf_len = 0;
+	m->last_error = MAD_ERROR_NONE;
+	MAD(m, stream_init, &m->stream);
+	MAD(m, frame_init, &m->frame);
+	MAD(m, synth_init, &m->synth);
+}
+
+static void mad_close(void) {
+	mad_synth_finish(&m->synth); // macro only in current version
+	MAD(m, frame_finish, &m->frame);
+	MAD(m, stream_finish, &m->stream);
+	free(m->readbuf);
+	m->readbuf = NULL;
+}
+
+static bool load_mad() {
+#if !LINKALL
+	void *handle = dlopen(LIBMAD, RTLD_NOW);
+	char *err;
+
+	if (!handle) {
+		LOG_INFO("dlerror: %s", dlerror());
+		return false;
+	}
+	
+	m->mad_stream_init = dlsym(handle, "mad_stream_init");
+	m->mad_frame_init = dlsym(handle, "mad_frame_init");
+	m->mad_synth_init = dlsym(handle, "mad_synth_init");
+	m->mad_frame_finish = dlsym(handle, "mad_frame_finish");
+	m->mad_stream_finish = dlsym(handle, "mad_stream_finish");
+	m->mad_stream_buffer = dlsym(handle, "mad_stream_buffer");
+	m->mad_frame_decode = dlsym(handle, "mad_frame_decode");
+	m->mad_synth_frame = dlsym(handle, "mad_synth_frame");
+	m->mad_stream_errorstr = dlsym(handle, "mad_stream_errorstr");
+
+	if ((err = dlerror()) != NULL) {
+		LOG_INFO("dlerror: %s", err);		
+		return false;
+	}
+
+	LOG_INFO("loaded "LIBMAD);
+#endif
+
+	return true;
+}
+
+struct codec *register_mad(void) {
+	static struct codec ret = { 
+		'm',          // id
+		"mp3",        // types
+		READBUF_SIZE, // min read
+		206800,       // min space
+		mad_open,     // open
+		mad_close,    // close
+		mad_decode,   // decode
+	};
+
+	m = malloc(sizeof(struct mad));
+	if (!m) {
+		return NULL;
+	}
+
+	m->readbuf = NULL;
+	m->readbuf_len = 0;
+
+	if (!load_mad()) {
+		return NULL;
+	}
+
+	LOG_INFO("using mad to decode mp3");
+	return &ret;
+}

+ 0 - 0
main.c → main/main.c


+ 0 - 0
output.c → main/output.c


+ 0 - 0
output_dac.c → main/output_dac.c


+ 0 - 0
output_pack.c → main/output_pack.c


+ 0 - 0
pcm.c → main/pcm.c


+ 0 - 0
scan.c → main/scan.c


+ 0 - 0
slimproto.c → main/slimproto.c


+ 0 - 0
slimproto.h → main/slimproto.h


+ 0 - 0
squeezelite.h → main/squeezelite.h


+ 0 - 0
stream.c → main/stream.c


+ 0 - 0
utils.c → main/utils.c