Browse Source

add resample

consumes A LOT of CPU. Need alos to change the command line to set the
sample rate (-r) to a fixed value (BT) but accept igher (-Z) from LMS
and enable resampling (-R)
-R -Z 96000 -r \"44100-44100\"  or similar
philippe44 5 years ago
parent
commit
701c30a99b

+ 3 - 1
components/codecs/component.mk

@@ -8,7 +8,9 @@ COMPONENT_ADD_LDFLAGS=-l$(COMPONENT_NAME) 	\
 	$(COMPONENT_PATH)/lib/libhelix-aac.a 	\
 	$(COMPONENT_PATH)/lib/libvorbisidec.a	\
 	$(COMPONENT_PATH)/lib/libogg.a			\
-	$(COMPONENT_PATH)/lib/libalac.a
+	$(COMPONENT_PATH)/lib/libalac.a			\
+	$(COMPONENT_PATH)/lib/libsoxr.a
+	
 	
 	#$(COMPONENT_PATH)/lib/libfaad.a 	
 	#$(COMPONENT_PATH)/lib/libvorbisidec.a

+ 348 - 0
components/codecs/inc/soxr/soxr.h

@@ -0,0 +1,348 @@
+/* SoX Resampler Library       Copyright (c) 2007-13 robs@users.sourceforge.net
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version.
+ *
+ * This library 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 Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+
+
+/* -------------------------------- Gubbins --------------------------------- */
+
+#if !defined soxr_included
+#define soxr_included
+
+
+#if defined __cplusplus
+  #include <cstddef>
+  extern "C" {
+#else
+  #include <stddef.h>
+#endif
+
+#if defined SOXR_DLL
+  #if defined soxr_EXPORTS
+    #define SOXR __declspec(dllexport)
+  #else
+    #define SOXR __declspec(dllimport)
+  #endif
+#elif defined SOXR_VISIBILITY && defined __GNUC__ && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 1)
+  #define SOXR __attribute__ ((visibility("default")))
+#else
+  #define SOXR
+#endif
+
+typedef struct soxr_io_spec soxr_io_spec_t;
+typedef struct soxr_quality_spec soxr_quality_spec_t;
+typedef struct soxr_runtime_spec soxr_runtime_spec_t;
+
+
+
+/* ---------------------------- API conventions --------------------------------
+
+Buffer lengths (and occupancies) are expressed as the number of contained
+samples per channel.
+
+Parameter names for buffer lengths have the suffix `len'.
+
+A single-character `i' or 'o' is often used in names to give context as
+input or output (e.g. ilen, olen).                                            */
+
+
+
+/* --------------------------- Version management --------------------------- */
+
+/* E.g. #if SOXR_THIS_VERSION >= SOXR_VERSION(0,1,1) ...                      */
+
+#define SOXR_VERSION(x,y,z)     (((x)<<16)|((y)<<8)|(z))
+#define SOXR_THIS_VERSION       SOXR_VERSION(0,1,2)
+#define SOXR_THIS_VERSION_STR               "0.1.2"
+
+
+
+/* --------------------------- Type declarations ---------------------------- */
+
+typedef struct soxr * soxr_t;          /* A resampler for 1 or more channels. */
+typedef char const * soxr_error_t;                /* 0:no-error; non-0:error. */
+
+typedef void       * soxr_buf_t;  /* 1 buffer of channel-interleaved samples. */
+typedef void const * soxr_cbuf_t;                        /* Ditto; read-only. */
+
+typedef soxr_buf_t const  * soxr_bufs_t;/* Or, a separate buffer for each ch. */
+typedef soxr_cbuf_t const * soxr_cbufs_t;                /* Ditto; read-only. */
+
+typedef void const * soxr_in_t;      /* Either a soxr_cbuf_t or soxr_cbufs_t,
+                                        depending on itype in soxr_io_spec_t. */
+typedef void       * soxr_out_t;     /* Either a soxr_buf_t or soxr_bufs_t,
+                                        depending on otype in soxr_io_spec_t. */
+
+
+
+/* --------------------------- API main functions --------------------------- */
+
+SOXR char const * soxr_version(void);  /* Query library version: "libsoxr-x.y.z" */
+
+#define soxr_strerror(e)               /* Soxr counterpart to strerror. */     \
+    ((e)?(e):"no error")
+
+
+/* Create a stream resampler: */
+
+SOXR soxr_t soxr_create(
+    double      input_rate,      /* Input sample-rate. */
+    double      output_rate,     /* Output sample-rate. */
+    unsigned    num_channels,    /* Number of channels to be used. */
+        /* All following arguments are optional (may be set to NULL). */
+    soxr_error_t *,              /* To report any error during creation. */
+    soxr_io_spec_t const *,      /* To specify non-default I/O formats. */
+    soxr_quality_spec_t const *, /* To specify non-default resampling quality.*/
+    soxr_runtime_spec_t const *);/* To specify non-default runtime resources.
+
+    Default io_spec      is per soxr_io_spec(SOXR_FLOAT32_I, SOXR_FLOAT32_I)
+    Default quality_spec is per soxr_quality_spec(SOXR_HQ, 0)
+    Default runtime_spec is per soxr_runtime_spec(1)                          */
+
+
+
+/* If not using an app-supplied input function, after creating a stream
+ * resampler, repeatedly call: */
+
+SOXR soxr_error_t soxr_process(
+    soxr_t      resampler,      /* As returned by soxr_create. */
+                            /* Input (to be resampled): */
+    soxr_in_t   in,             /* Input buffer(s); may be NULL (see below). */
+    size_t      ilen,           /* Input buf. length (samples per channel). */
+    size_t      * idone,        /* To return actual # samples used (<= ilen). */
+                            /* Output (resampled): */
+    soxr_out_t  out,            /* Output buffer(s).*/
+    size_t      olen,           /* Output buf. length (samples per channel). */
+    size_t      * odone);       /* To return actual # samples out (<= olen).
+
+    Note that no special meaning is associated with ilen or olen equal to
+    zero.  End-of-input (i.e. no data is available nor shall be available)
+    may be indicated by seting `in' to NULL.                                  */
+
+
+
+/* If using an app-supplied input function, it must look and behave like this:*/
+
+typedef size_t /* data_len */
+  (* soxr_input_fn_t)(         /* Supply data to be resampled. */
+    void * input_fn_state,     /* As given to soxr_set_input_fn (below). */
+    soxr_in_t * data,          /* Returned data; see below. N.B. ptr to ptr(s)*/
+    size_t requested_len);     /* Samples per channel, >= returned data_len.
+
+  data_len  *data     Indicates    Meaning
+   ------- -------   ------------  -------------------------
+     !=0     !=0       Success     *data contains data to be
+                                   input to the resampler.
+      0    !=0 (or   End-of-input  No data is available nor
+           not set)                shall be available.
+      0       0        Failure     An error occurred whilst trying to
+                                   source data to be input to the resampler.  */
+
+/* and be registered with a previously created stream resampler using: */
+
+SOXR soxr_error_t soxr_set_input_fn(/* Set (or reset) an input function.*/
+    soxr_t resampler,            /* As returned by soxr_create. */
+    soxr_input_fn_t,             /* Function to supply data to be resampled.*/
+    void * input_fn_state,       /* If needed by the input function. */
+    size_t max_ilen);            /* Maximum value for input fn. requested_len.*/
+
+/* then repeatedly call: */
+
+SOXR size_t /*odone*/ soxr_output(/* Resample and output a block of data.*/
+    soxr_t resampler,            /* As returned by soxr_create. */
+    soxr_out_t data,             /* App-supplied buffer(s) for resampled data.*/
+    size_t olen);                /* Amount of data to output; >= odone. */
+
+
+
+/* Common stream resampler operations: */
+
+SOXR soxr_error_t soxr_error(soxr_t);   /* Query error status. */
+SOXR size_t   * soxr_num_clips(soxr_t); /* Query int. clip counter (for R/W). */
+SOXR double     soxr_delay(soxr_t);  /* Query current delay in output samples.*/
+SOXR char const * soxr_engine(soxr_t p); /* Query resampling engine name. */
+
+SOXR soxr_error_t soxr_clear(soxr_t); /* Ready for fresh signal, same config. */
+SOXR void         soxr_delete(soxr_t);  /* Free resources. */
+
+
+
+/* `Short-cut', single call to resample a (probably short) signal held entirely
+ * in memory.  See soxr_create and soxr_process above for parameter details.
+ * Note that unlike soxr_create however, the default quality spec. for
+ * soxr_oneshot is per soxr_quality_spec(SOXR_LQ, 0). */
+
+SOXR soxr_error_t soxr_oneshot(
+    double         input_rate,
+    double         output_rate,
+    unsigned       num_channels,
+    soxr_in_t    in , size_t ilen, size_t * idone,
+    soxr_out_t   out, size_t olen, size_t * odone,
+    soxr_io_spec_t const *,
+    soxr_quality_spec_t const *,
+    soxr_runtime_spec_t const *);
+
+
+
+/* For variable-rate resampling. See example # 5 for how to create a
+ * variable-rate resampler and how to use this function. */
+
+SOXR soxr_error_t soxr_set_io_ratio(soxr_t, double io_ratio, size_t slew_len);
+
+
+
+/* -------------------------- API type definitions -------------------------- */
+
+typedef enum {          /* Datatypes supported for I/O to/from the resampler: */
+  /* Internal; do not use: */
+  SOXR_FLOAT32, SOXR_FLOAT64, SOXR_INT32, SOXR_INT16, SOXR_SPLIT = 4,
+
+  /* Use for interleaved channels: */
+  SOXR_FLOAT32_I = SOXR_FLOAT32, SOXR_FLOAT64_I, SOXR_INT32_I, SOXR_INT16_I,
+
+  /* Use for split channels: */
+  SOXR_FLOAT32_S = SOXR_SPLIT  , SOXR_FLOAT64_S, SOXR_INT32_S, SOXR_INT16_S
+
+} soxr_datatype_t;
+
+#define soxr_datatype_size(x)  /* Returns `sizeof' a soxr_datatype_t sample. */\
+  ((unsigned char *)"\4\10\4\2")[(x)&3]
+
+
+
+struct soxr_io_spec {                                            /* Typically */
+  soxr_datatype_t itype;     /* Input datatype.                SOXR_FLOAT32_I */
+  soxr_datatype_t otype;     /* Output datatype.               SOXR_FLOAT32_I */
+  double scale;              /* Linear gain to apply during resampling.  1    */
+  void * e;                  /* Reserved for internal use                0    */
+  unsigned long flags;       /* Per the following #defines.              0    */
+};
+
+#define SOXR_TPDF              0     /* Applicable only if otype is INT16. */
+#define SOXR_NO_DITHER         8u    /* Disable the above. */
+
+
+
+struct soxr_quality_spec {                                       /* Typically */
+  double precision;         /* Conversion precision (in bits).           20   */
+  double phase_response;    /* 0=minimum, ... 50=linear, ... 100=maximum 50   */
+  double passband_end;      /* 0dB pt. bandwidth to preserve; nyquist=1  0.913*/
+  double stopband_begin;    /* Aliasing/imaging control; > passband_end   1   */
+  void * e;                 /* Reserved for internal use.                 0   */
+  unsigned long flags;      /* Per the following #defines.                0   */
+};
+
+#define SOXR_ROLLOFF_SMALL     0u    /* <= 0.01 dB */
+#define SOXR_ROLLOFF_MEDIUM    1u    /* <= 0.35 dB */
+#define SOXR_ROLLOFF_NONE      2u    /* For Chebyshev bandwidth. */
+
+#define SOXR_MAINTAIN_3DB_PT   4u  /* Reserved for internal use. */
+#define SOXR_HI_PREC_CLOCK     8u  /* Increase `irrational' ratio accuracy. */
+#define SOXR_DOUBLE_PRECISION 16u  /* Use D.P. calcs even if precision <= 20. */
+#define SOXR_VR               32u  /* Variable-rate resampling. */
+
+
+
+struct soxr_runtime_spec {                                       /* Typically */
+  unsigned log2_min_dft_size;/* For DFT efficiency. [8,15]              10    */
+  unsigned log2_large_dft_size;/* For DFT efficiency. [16,20]           17    */
+  unsigned coef_size_kbytes; /* For SOXR_COEF_INTERP_AUTO (below).      400   */
+  unsigned num_threads;      /* If built so. 0 means `automatic'.        1    */
+  void * e;                  /* Reserved for internal use.               0    */
+  unsigned long flags;       /* Per the following #defines.              0    */
+};
+                                   /* For `irrational' ratios only: */
+#define SOXR_COEF_INTERP_AUTO  0u    /* Auto select coef. interpolation. */
+#define SOXR_COEF_INTERP_LOW   2u    /* Man. select: less CPU, more memory. */
+#define SOXR_COEF_INTERP_HIGH  3u    /* Man. select: more CPU, less memory. */
+
+#define SOXR_STRICT_BUFFERING  4u  /* Reserved for future use. */
+#define SOXR_NOSMALLINTOPT     8u  /* For test purposes only. */
+
+
+
+/* -------------------------- API type constructors ------------------------- */
+
+/* These functions allow setting of the most commonly-used structure
+ * parameters, with other parameters being given default values.  The default
+ * values may then be overridden, directly in the structure, if needed.  */
+
+SOXR soxr_quality_spec_t soxr_quality_spec(
+    unsigned long recipe,       /* Per the #defines immediately below. */
+    unsigned long flags);       /* As soxr_quality_spec_t.flags. */
+
+                                  /* The 5 standard qualities found in SoX: */
+#define SOXR_QQ                 0   /* 'Quick' cubic interpolation. */
+#define SOXR_LQ                 1   /* 'Low' 16-bit with larger rolloff. */
+#define SOXR_MQ                 2   /* 'Medium' 16-bit with medium rolloff. */
+#define SOXR_HQ                 SOXR_20_BITQ /* 'High quality'. */
+#define SOXR_VHQ                SOXR_28_BITQ /* 'Very high quality'. */
+
+#define SOXR_16_BITQ            3
+#define SOXR_20_BITQ            4
+#define SOXR_24_BITQ            5
+#define SOXR_28_BITQ            6
+#define SOXR_32_BITQ            7
+                                    /* Libsamplerate equivalent qualities: */
+#define SOXR_LSR0Q              8     /* 'Best sinc'. */
+#define SOXR_LSR1Q              9     /* 'Medium sinc'. */
+#define SOXR_LSR2Q              10    /* 'Fast sinc'. */
+
+#define SOXR_LINEAR_PHASE       0x00
+#define SOXR_INTERMEDIATE_PHASE 0x10
+#define SOXR_MINIMUM_PHASE      0x30
+#define SOXR_STEEP_FILTER       0x40
+#define SOXR_ALLOW_ALIASING     0x80  /* Reserved for future use. */
+
+
+
+SOXR soxr_runtime_spec_t soxr_runtime_spec(
+    unsigned num_threads);
+
+
+
+SOXR soxr_io_spec_t soxr_io_spec(
+    soxr_datatype_t itype,
+    soxr_datatype_t otype);
+
+
+
+/* --------------------------- Advanced use only ---------------------------- */
+
+/* For new designs, the following functions/usage will probably not be needed.
+ * They might be useful when adding soxr into an existing design where values
+ * for the resampling-rate and/or number-of-channels parameters to soxr_create
+ * are not available when that function will be called.  In such cases, the
+ * relevant soxr_create parameter(s) can be given as 0, then one or both of the
+ * following (as appropriate) later invoked (but prior to calling soxr_process
+ * or soxr_output):
+ *
+ * soxr_set_error(soxr, soxr_set_io_ratio(soxr, io_ratio, 0));
+ * soxr_set_error(soxr, soxr_set_num_channels(soxr, num_channels));
+ */
+
+SOXR soxr_error_t soxr_set_error(soxr_t, soxr_error_t);
+SOXR soxr_error_t soxr_set_num_channels(soxr_t, unsigned);
+
+
+
+#undef SOXR
+
+#if defined __cplusplus
+}
+#endif
+
+#endif

BIN
components/codecs/lib/libsoxr.a


+ 0 - 1
main/Kconfig.projbuild

@@ -99,7 +99,6 @@ menu "Squeezelite-ESP32"
         help
             Include FAAD library for aac decoding.
     config INCLUDE_MAD
-    	depends on SPIRAM_SUPPORT
         bool "MAD"
         default 1
         help

+ 2 - 2
main/alac.c

@@ -329,14 +329,14 @@ static decode_state alac_decode(void) {
 			bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
 
 			LOG_INFO("setting track_start");
-			LOCK_O_not_direct;
+			LOCK_O;
 
 			output.next_sample_rate = decode_newstream(l->sample_rate, output.supported_rates);
 			output.track_start = outputbuf->writep;
 			if (output.fade_mode) _checkfade(true);
 			decode.new_stream = false;
 
-			UNLOCK_O_not_direct;
+			UNLOCK_O;
 		} else if (found == -1) {
 			LOG_WARN("[%p]: error reading stream header");
 			UNLOCK_S;

+ 3 - 2
main/component.mk

@@ -2,12 +2,13 @@
 # "main" pseudo-component makefile.
 #
 # (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
-CFLAGS += -O3 -DPOSIX -DLINKALL -DLOOPBACK -DNO_FAAD -DEMBEDDED -DTREMOR_ONLY -DBYTES_PER_FRAME=4 	\
+CFLAGS += -O3 -DPOSIX -DLINKALL -DLOOPBACK -DNO_FAAD -DRESAMPLE -DEMBEDDED -DTREMOR_ONLY -DBYTES_PER_FRAME=4 	\
 	-I$(COMPONENT_PATH)/../components/codecs/inc			\
 	-I$(COMPONENT_PATH)/../components/codecs/inc/mad 		\
 	-I$(COMPONENT_PATH)/../components/codecs/inc/alac		\
 	-I$(COMPONENT_PATH)/../components/codecs/inc/helix-aac	\
-	-I$(COMPONENT_PATH)/../components/codecs/inc/vorbis \
+	-I$(COMPONENT_PATH)/../components/codecs/inc/vorbis 	\
+	-I$(COMPONENT_PATH)/../components/codecs/inc/soxr 		\
 	-I$(COMPONENT_PATH)/../components/platform_esp32 
 	
 LDFLAGS += -s

+ 194 - 0
main/process.c

@@ -0,0 +1,194 @@
+/* 
+ *  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/>.
+ *
+ */
+
+// sample processing - only included when building with PROCESS set
+
+#include "squeezelite.h"
+
+#if PROCESS
+
+extern log_level loglevel;
+
+extern struct buffer *outputbuf;
+extern struct decodestate decode;
+struct processstate process;
+extern struct codec *codec;
+
+#define LOCK_D   mutex_lock(decode.mutex);
+#define UNLOCK_D mutex_unlock(decode.mutex);
+#define LOCK_O   mutex_lock(outputbuf->mutex)
+#define UNLOCK_O mutex_unlock(outputbuf->mutex)
+
+// macros to map to processing functions - currently only resample.c
+// this can be made more generic when multiple processing mechanisms get added
+#if RESAMPLE
+#define SAMPLES_FUNC resample_samples
+#define DRAIN_FUNC   resample_drain
+#define NEWSTREAM_FUNC resample_newstream
+#define FLUSH_FUNC   resample_flush
+#define INIT_FUNC    resample_init
+#endif
+
+
+// transfer all processed frames to the output buf
+static void _write_samples(void) {
+	frames_t frames = process.out_frames;
+	ISAMPLE_T *iptr   = (ISAMPLE_T *) process.outbuf;
+	unsigned cnt  = 10;
+
+	LOCK_O;
+
+	while (frames > 0) {
+
+		frames_t f = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
+		ISAMPLE_T *optr = (ISAMPLE_T*) outputbuf->writep;
+
+		if (f > 0) {
+
+			f = min(f, frames);
+			
+			memcpy(optr, iptr, f * BYTES_PER_FRAME);
+			
+			frames -= f;
+			
+			_buf_inc_writep(outputbuf, f * BYTES_PER_FRAME);
+			iptr += f * BYTES_PER_FRAME / sizeof(*iptr);
+
+		} else if (cnt--) {
+
+			// there should normally be space in the output buffer, but may need to wait during drain phase
+			UNLOCK_O;
+			usleep(10000);
+			LOCK_O;
+
+		} else {
+
+			// bail out if no space found after 100ms to avoid locking
+			LOG_ERROR("unable to get space in output buffer");
+			UNLOCK_O;
+			return;
+		}
+	}
+
+	UNLOCK_O;
+}
+
+// process samples - called with decode mutex set
+void process_samples(void) {
+
+	SAMPLES_FUNC(&process);
+
+	_write_samples();
+
+	process.in_frames = 0;
+}
+
+// drain at end of track - called with decode mutex set
+void process_drain(void) {
+	bool done;
+
+	do {
+
+		done = DRAIN_FUNC(&process);
+
+		_write_samples();
+
+	} while (!done);
+
+	LOG_DEBUG("processing track complete - frames in: %lu out: %lu", process.total_in, process.total_out);
+}	
+
+// new stream - called with decode mutex set
+unsigned process_newstream(bool *direct, unsigned raw_sample_rate, unsigned supported_rates[]) {
+
+	bool active = NEWSTREAM_FUNC(&process, raw_sample_rate, supported_rates);
+
+	LOG_INFO("processing: %s", active ? "active" : "inactive");
+
+	*direct = !active;
+
+	if (active) {
+
+		unsigned max_in_frames, max_out_frames;
+
+		process.in_frames = process.out_frames = 0;
+		process.total_in = process.total_out = 0;
+
+		max_in_frames = codec->min_space / BYTES_PER_FRAME ;
+
+		// increase size of output buffer by 10% as output rate is not an exact multiple of input rate
+		if (process.out_sample_rate % process.in_sample_rate == 0) {
+			max_out_frames = max_in_frames * (process.out_sample_rate / process.in_sample_rate);
+		} else {
+			max_out_frames = (int)(1.1 * (float)max_in_frames * (float)process.out_sample_rate / (float)process.in_sample_rate);
+		}
+
+		if (process.max_in_frames != max_in_frames) {
+			LOG_DEBUG("creating process buf in frames: %u", max_in_frames);
+			if (process.inbuf) free(process.inbuf);
+			process.inbuf = malloc(max_in_frames * BYTES_PER_FRAME);
+			process.max_in_frames = max_in_frames;
+		}
+		
+		if (process.max_out_frames != max_out_frames) {
+			LOG_DEBUG("creating process buf out frames: %u", max_out_frames);
+			if (process.outbuf) free(process.outbuf);
+			process.outbuf = malloc(max_out_frames * BYTES_PER_FRAME);
+			process.max_out_frames = max_out_frames;
+		}
+		
+		if (!process.inbuf || !process.outbuf) {
+			LOG_ERROR("malloc fail creating process buffers");
+			*direct = true;
+			return raw_sample_rate;
+		}
+		
+		return process.out_sample_rate;
+	}
+
+	return raw_sample_rate;
+}
+
+// process flush - called with decode mutex set
+void process_flush(void) {
+
+	LOG_INFO("process flush");
+
+	FLUSH_FUNC();
+
+	process.in_frames = 0;
+}
+
+// init - called with no mutex
+void process_init(char *opt) {
+
+	bool enabled = INIT_FUNC(opt);
+
+	memset(&process, 0, sizeof(process));
+
+	if (enabled) {
+		LOCK_D;
+		decode.process = true;
+		UNLOCK_D;
+	}
+}
+
+#endif // #if PROCESS

+ 377 - 0
main/resample.c

@@ -0,0 +1,377 @@
+/* 
+ *  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/>.
+ *
+ */
+
+// upsampling using libsoxr - only included if RESAMPLE set
+
+#include "squeezelite.h"
+
+#if RESAMPLE
+
+#include <math.h>
+#include <soxr.h>
+
+extern log_level loglevel;
+
+struct soxr {
+	soxr_t resampler;
+	size_t old_clips;
+	unsigned long q_recipe;
+	unsigned long q_flags;
+	double q_precision;         /* Conversion precision (in bits).           20    */
+	double q_phase_response;    /* 0=minimum, ... 50=linear, ... 100=maximum 50    */
+	double q_passband_end;      /* 0dB pt. bandwidth to preserve; nyquist=1  0.913 */
+	double q_stopband_begin;    /* Aliasing/imaging control; > passband_end   1    */
+	double scale;
+	bool max_rate;
+	bool exception;
+#if !LINKALL
+	// soxr symbols to be dynamically loaded
+	soxr_io_spec_t (* soxr_io_spec)(soxr_datatype_t itype, soxr_datatype_t otype);
+	soxr_quality_spec_t (* soxr_quality_spec)(unsigned long recipe, unsigned long flags);
+	soxr_t (* soxr_create)(double, double, unsigned, soxr_error_t *, 
+						   soxr_io_spec_t const *, soxr_quality_spec_t const *, soxr_runtime_spec_t const *);
+	void (* soxr_delete)(soxr_t);
+	soxr_error_t (* soxr_process)(soxr_t, soxr_in_t, size_t, size_t *, soxr_out_t, size_t olen, size_t *);
+	size_t *(* soxr_num_clips)(soxr_t);
+#if RESAMPLE_MP
+	soxr_runtime_spec_t (* soxr_runtime_spec)(unsigned num_threads);
+#endif
+	// soxr_strerror is a macro so not included here
+#endif
+};
+
+static struct soxr *r;
+
+#if LINKALL
+#define SOXR(h, fn, ...) (soxr_ ## fn)(__VA_ARGS__)
+#else
+#define SOXR(h, fn, ...) (h)->soxr_##fn(__VA_ARGS__)
+#endif
+
+
+void resample_samples(struct processstate *process) {
+	size_t idone, odone;
+	size_t clip_cnt;
+	
+	soxr_error_t error =
+		SOXR(r, process, r->resampler, process->inbuf, process->in_frames, &idone, process->outbuf, process->max_out_frames, &odone);
+	if (error) {
+		LOG_INFO("soxr_process error: %s", soxr_strerror(error));
+		return;
+	}
+	
+	if (idone != process->in_frames) {
+		// should not get here if buffers are big enough...
+		LOG_ERROR("should not get here - partial sox process: %u of %u processed %u of %u out",
+				  (unsigned)idone, process->in_frames, (unsigned)odone, process->max_out_frames);
+	}
+	
+	process->out_frames = odone;
+	process->total_in  += idone;
+	process->total_out += odone;
+	
+	clip_cnt = *(SOXR(r, num_clips, r->resampler));
+	if (clip_cnt - r->old_clips) {
+		LOG_SDEBUG("resampling clips: %u", (unsigned)(clip_cnt - r->old_clips));
+		r->old_clips = clip_cnt;
+	}
+}
+
+bool resample_drain(struct processstate *process) {
+	size_t odone;
+	size_t clip_cnt;
+		
+	soxr_error_t error = SOXR(r, process, r->resampler, NULL, 0, NULL, process->outbuf, process->max_out_frames, &odone);
+	if (error) {
+		LOG_INFO("soxr_process error: %s", soxr_strerror(error));
+		return true;
+	}
+	
+	process->out_frames = odone;
+	process->total_out += odone;
+	
+	clip_cnt = *(SOXR(r, num_clips, r->resampler));
+	if (clip_cnt - r->old_clips) {
+		LOG_DEBUG("resampling clips: %u", (unsigned)(clip_cnt - r->old_clips));
+		r->old_clips = clip_cnt;
+	}
+	
+	if (odone == 0) {
+
+		LOG_INFO("resample track complete - total track clips: %u", r->old_clips);
+
+		SOXR(r, delete, r->resampler);
+		r->resampler = NULL;
+
+		return true;
+
+	} else {
+
+		return false;
+	}
+}
+
+bool resample_newstream(struct processstate *process, unsigned raw_sample_rate, unsigned supported_rates[]) {
+	unsigned outrate = 0;
+	int i;
+
+	if (r->exception) {
+		// find direct match - avoid resampling
+		for (i = 0; supported_rates[i]; i++) {
+			if (raw_sample_rate == supported_rates[i]) {
+				outrate = raw_sample_rate;
+				break;
+			}
+		}
+		// else find next highest sync sample rate
+		while (!outrate && i >= 0) {
+			if (supported_rates[i] > raw_sample_rate && supported_rates[i] % raw_sample_rate == 0) {
+				outrate = supported_rates[i];
+				break;
+			}
+			i--;
+		}
+	}
+
+	if (!outrate) {
+		if (r->max_rate) {
+			// resample to max rate for device
+			outrate = supported_rates[0];
+		} else {
+			// resample to max sync sample rate
+			for (i = 0; supported_rates[i]; i++) {
+				if (supported_rates[i] % raw_sample_rate == 0 || raw_sample_rate % supported_rates[i] == 0) {
+					outrate = supported_rates[i];
+					break;
+				}
+			}
+		}
+		if (!outrate) {
+			outrate = supported_rates[0];
+		}
+	}
+
+	process->in_sample_rate = raw_sample_rate;
+	process->out_sample_rate = outrate;
+
+	if (r->resampler) {
+		SOXR(r, delete, r->resampler);
+		r->resampler = NULL;
+	}
+
+	if (raw_sample_rate != outrate) {
+
+		soxr_io_spec_t io_spec;
+		soxr_quality_spec_t q_spec;
+		soxr_error_t error;
+#if RESAMPLE_MP
+		soxr_runtime_spec_t r_spec;
+#endif
+
+		LOG_INFO("resampling from %u -> %u", raw_sample_rate, outrate);
+
+#if BYTES_PER_FRAME == 4
+		io_spec = SOXR(r, io_spec, SOXR_INT16_I, SOXR_INT16_I);
+#else		
+		io_spec = SOXR(r, io_spec, SOXR_INT32_I, SOXR_INT32_I);
+#endif	
+		io_spec.scale = r->scale;
+
+		q_spec = SOXR(r, quality_spec, r->q_recipe, r->q_flags);
+		if (r->q_precision > 0) {
+			q_spec.precision = r->q_precision;
+		}
+		if (r->q_passband_end > 0) {
+			q_spec.passband_end = r->q_passband_end;
+		}
+		if (r->q_stopband_begin > 0) {
+			q_spec.stopband_begin = r->q_stopband_begin;
+		}
+		if (r->q_phase_response > -1) {
+			q_spec.phase_response = r->q_phase_response;
+		}
+
+#if RESAMPLE_MP
+		r_spec = SOXR(r, runtime_spec, 0); // make use of libsoxr OpenMP support allowing parallel execution if multiple cores
+#endif		   
+
+		LOG_DEBUG("resampling with soxr_quality_spec_t[precision: %03.1f, passband_end: %03.6f, stopband_begin: %03.6f, "
+				  "phase_response: %03.1f, flags: 0x%02x], soxr_io_spec_t[scale: %03.2f]", q_spec.precision,
+				  q_spec.passband_end, q_spec.stopband_begin, q_spec.phase_response, q_spec.flags, io_spec.scale);
+
+#if RESAMPLE_MP
+		r->resampler = SOXR(r, create, raw_sample_rate, outrate, 2, &error, &io_spec, &q_spec, &r_spec);
+#else
+		r->resampler = SOXR(r, create, raw_sample_rate, outrate, 2, &error, &io_spec, &q_spec, NULL);
+#endif
+
+		if (error) {
+			LOG_INFO("soxr_create error: %s", soxr_strerror(error));
+			return false;
+		}
+
+		r->old_clips = 0;
+		return true;
+
+	} else {
+
+		LOG_INFO("disable resampling - rates match");
+		return false;
+	}
+}
+
+void resample_flush(void) {
+	if (r->resampler) {
+		SOXR(r, delete, r->resampler);
+		r->resampler = NULL;
+	}
+}
+
+static bool load_soxr(void) {
+#if !LINKALL
+	void *handle = dlopen(LIBSOXR, RTLD_NOW);
+	char *err;
+
+	if (!handle) {
+		LOG_INFO("dlerror: %s", dlerror());
+		return false;
+	}
+
+	r->soxr_io_spec = dlsym(handle, "soxr_io_spec");
+	r->soxr_quality_spec = dlsym(handle, "soxr_quality_spec");
+	r->soxr_create = dlsym(handle, "soxr_create");
+	r->soxr_delete = dlsym(handle, "soxr_delete");
+	r->soxr_process = dlsym(handle, "soxr_process");
+	r->soxr_num_clips = dlsym(handle, "soxr_num_clips");
+#if RESAMPLE_MP
+	r->soxr_runtime_spec = dlsym(handle, "soxr_runtime_spec");
+#endif
+
+	if ((err = dlerror()) != NULL) {
+		LOG_INFO("dlerror: %s", err);		
+		return false;
+	}
+
+	LOG_INFO("loaded "LIBSOXR);
+#endif
+
+	return true;
+}
+
+bool resample_init(char *opt) {
+	char *recipe = NULL, *flags = NULL;
+	char *atten = NULL;
+	char *precision = NULL, *passband_end = NULL, *stopband_begin = NULL, *phase_response = NULL;
+
+	r = malloc(sizeof(struct soxr));
+	if (!r) {
+		LOG_WARN("resampling disabled");
+		return false;
+	}
+
+	r->resampler = NULL;
+	r->old_clips = 0;
+	r->max_rate = false;
+	r->exception = false;
+
+	if (!load_soxr()) {
+		LOG_WARN("resampling disabled");
+		return false;
+	}
+
+	if (opt) {
+		recipe = next_param(opt, ':');
+		flags = next_param(NULL, ':');
+		atten = next_param(NULL, ':');
+		precision = next_param(NULL, ':');
+		passband_end = next_param(NULL, ':');
+		stopband_begin = next_param(NULL, ':');
+		phase_response = next_param(NULL, ':');
+	}
+
+
+#if BYTES_PER_FRAME == 4	
+	// default to LQ (16 bit) if not user specified
+	r->q_recipe = SOXR_LQ;
+#else
+	// default to HQ (20 bit) if not user specified
+	r->q_recipe = SOXR_HQ;
+#endif	
+	r->q_flags = 0;
+	// default to 1db of attenuation if not user specified
+	r->scale = pow(10, -1.0 / 20);
+	// override recipe derived values with user specified values
+	r->q_precision = 0;
+	r->q_passband_end = 0;
+	r->q_stopband_begin = 0;
+	r->q_phase_response = -1;
+
+	if (recipe && recipe[0] != '\0') {
+		if (strchr(recipe, 'v')) r->q_recipe = SOXR_VHQ;
+		if (strchr(recipe, 'h')) r->q_recipe = SOXR_HQ;
+		if (strchr(recipe, 'm')) r->q_recipe = SOXR_MQ;
+		if (strchr(recipe, 'l')) r->q_recipe = SOXR_LQ;
+		if (strchr(recipe, 'q')) r->q_recipe = SOXR_QQ;
+		if (strchr(recipe, 'L')) r->q_recipe |= SOXR_LINEAR_PHASE;
+		if (strchr(recipe, 'I')) r->q_recipe |= SOXR_INTERMEDIATE_PHASE;
+		if (strchr(recipe, 'M')) r->q_recipe |= SOXR_MINIMUM_PHASE;
+		if (strchr(recipe, 's')) r->q_recipe |= SOXR_STEEP_FILTER;
+		// X = async resampling to max_rate
+		if (strchr(recipe, 'X')) r->max_rate = true;
+		// E = exception, only resample if native rate is not supported
+		if (strchr(recipe, 'E')) r->exception = true;
+	}
+
+	if (flags) {
+		r->q_flags = strtoul(flags, 0, 16);
+	}
+
+	if (atten) {
+		double scale = pow(10, -atof(atten) / 20);
+		if (scale > 0 && scale <= 1.0) {
+			r->scale = scale;
+		}
+	}
+
+	if (precision) {
+		r->q_precision = atof(precision);
+	}
+
+	if (passband_end) {
+		r->q_passband_end = atof(passband_end) / 100;
+	}
+
+	if (stopband_begin) {
+		r->q_stopband_begin = atof(stopband_begin) / 100;
+	}
+
+	if (phase_response) {
+		r->q_phase_response = atof(phase_response);
+	}
+
+	LOG_INFO("resampling %s recipe: 0x%02x, flags: 0x%02x, scale: %03.2f, precision: %03.1f, passband_end: %03.5f, stopband_begin: %03.5f, phase_response: %03.1f",
+			r->max_rate ? "async" : "sync",
+			r->q_recipe, r->q_flags, r->scale, r->q_precision, r->q_passband_end, r->q_stopband_begin, r->q_phase_response);
+
+	return true;
+}
+
+#endif // #if RESAMPLE