Browse Source

fixed point resampling option

philippe44 5 years ago
parent
commit
a75f1f0cd5

+ 1 - 0
components/codecs/component.mk

@@ -9,6 +9,7 @@ COMPONENT_ADD_LDFLAGS=-l$(COMPONENT_NAME) 	\
 	$(COMPONENT_PATH)/lib/libvorbisidec.a	\
 	$(COMPONENT_PATH)/lib/libogg.a			\
 	$(COMPONENT_PATH)/lib/libalac.a			\
+	$(COMPONENT_PATH)/lib/libresample16.a		\
 	$(COMPONENT_PATH)/lib/libsoxr.a
 	
 	

+ 85 - 0
components/codecs/inc/resample16/resample16.h

@@ -0,0 +1,85 @@
+/*
+ * FILE: resample16.h
+ *
+ * The configuration constants below govern
+ * the number of bits in the input sample and filter coefficients, the 
+ * number of bits to the right of the binary-point for fixed-point math, etc.
+ *
+ */
+
+/* Conversion constants */
+#define Nhc       8
+#define Na        7
+#define Np       (Nhc+Na)
+#define Npc      (1<<Nhc)
+#define Amask    ((1<<Na)-1)
+#define Pmask    ((1<<Np)-1)
+#define Nh       16
+#define Nb       16
+#define Nhxn     14
+#define Nhg      (Nh-Nhxn)
+#define NLpScl   13
+
+/* Description of constants:
+ *
+ * Npc - is the number of look-up values available for the lowpass filter
+ *    between the beginning of its impulse response and the "cutoff time"
+ *    of the filter.  The cutoff time is defined as the reciprocal of the
+ *    lowpass-filter cut off frequence in Hz.  For example, if the
+ *    lowpass filter were a sinc function, Npc would be the index of the
+ *    impulse-response lookup-table corresponding to the first zero-
+ *    crossing of the sinc function.  (The inverse first zero-crossing
+ *    time of a sinc function equals its nominal cutoff frequency in Hz.)
+ *    Npc must be a power of 2 due to the details of the current
+ *    implementation. The default value of 512 is sufficiently high that
+ *    using linear interpolation to fill in between the table entries
+ *    gives approximately 16-bit accuracy in filter coefficients.
+ *
+ * Nhc - is log base 2 of Npc.
+ *
+ * Na - is the number of bits devoted to linear interpolation of the
+ *    filter coefficients.
+ *
+ * Np - is Na + Nhc, the number of bits to the right of the binary point
+ *    in the integer "time" variable. To the left of the point, it indexes
+ *    the input array (X), and to the right, it is interpreted as a number
+ *    between 0 and 1 sample of the input X.  Np must be less than 16 in
+ *    this implementation.
+ *
+ * Nh - is the number of bits in the filter coefficients. The sum of Nh and
+ *    the number of bits in the input data (typically 16) cannot exceed 32.
+ *    Thus Nh should be 16.  The largest filter coefficient should nearly
+ *    fill 16 bits (32767).
+ *
+ * Nb - is the number of bits in the input data. The sum of Nb and Nh cannot
+ *    exceed 32.
+ *
+ * Nhxn - is the number of bits to right shift after multiplying each input
+ *    sample times a filter coefficient. It can be as great as Nh and as
+ *    small as 0. Nhxn = Nh-2 gives 2 guard bits in the multiply-add
+ *    accumulation.  If Nhxn=0, the accumulation will soon overflow 32 bits.
+ *
+ * Nhg - is the number of guard bits in mpy-add accumulation (equal to Nh-Nhxn)
+ *
+ * NLpScl - is the number of bits allocated to the unity-gain normalization
+ *    factor.  The output of the lowpass filter is multiplied by LpScl and
+ *    then right-shifted NLpScl bits. To avoid overflow, we must have 
+ *    Nb+Nhg+NLpScl < 32.
+ */
+
+typedef char           BOOL;
+typedef short          HWORD;
+typedef unsigned short UHWORD;
+typedef int            WORD;
+typedef unsigned int   UWORD;
+
+struct resample16_s;
+
+typedef enum { RESAMPLE16_FAST, RESAMPLE16_SMALL, RESAMPLE16_LARGE, RESAMPLE_CUSTOM } resample16_filter_e;
+
+WORD 				resample16(struct resample16_s *r, HWORD X[],	int inCount, HWORD Y[]);
+struct resample16_s*  resample16_create(double factor, resample16_filter_e filter, BOOL interp);
+void 				resample16_delete(struct resample16_s *r);
+void 				resample16_flush(struct resample16_s *r);
+
+

BIN
components/codecs/lib/libresample16.a


BIN
components/codecs/lib/libsoxr.a


+ 2 - 1
main/component.mk

@@ -2,13 +2,14 @@
 # "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 -DRESAMPLE -DEMBEDDED -DTREMOR_ONLY -DBYTES_PER_FRAME=4 	\
+CFLAGS += -O3 -DPOSIX -DLINKALL -DLOOPBACK -DNO_FAAD -DRESAMPLE16 -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/soxr 		\
+	-I$(COMPONENT_PATH)/../components/codecs/inc/resample16	\
 	-I$(COMPONENT_PATH)/../components/platform_esp32 
 	
 LDFLAGS += -s

+ 12 - 5
main/main.c

@@ -111,6 +111,10 @@ static void usage(const char *argv0) {
 		   "  \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 RESAMPLE16
+		   "  -R -u [params]\tResample, params = (i|m)[:i],\n" 
+		   "   \t\t\t i = linear interpolation, m =  13 taps filter, i = interpolate filter coefficients\n"
+#endif
 #if DSD
 #if ALSA
 		   "  -D [delay][:format]\tOutput device supports DSD, delay = optional delay switching between PCM and DSD in ms\n"
@@ -132,7 +136,7 @@ static void usage(const char *argv0) {
 #if LINUX || FREEBSD || SUN
 		   "  -z \t\t\tDaemonize\n"
 #endif
-#if RESAMPLE
+#if RESAMPLE || RESAMPLE16
 		   "  -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"
@@ -183,6 +187,9 @@ static void usage(const char *argv0) {
 #if RESAMPLE
 		   " RESAMPLE"
 #endif
+#if RESAMPLE16
+		   " RESAMPLE16"
+#endif
 #endif
 #if FFMPEG
 		   " FFMPEG"
@@ -339,7 +346,7 @@ int main(int argc, char **argv) {
  * only allow '-Z <rate>' override of maxSampleRate 
  * reported by client if built with the capability to resample!
  */
-#if RESAMPLE
+#if RESAMPLE || RESAMPLE16
 				   "Z"
 #endif
 				   , opt) && optind < argc - 1) {
@@ -349,7 +356,7 @@ int main(int argc, char **argv) {
 #if ALSA
 						  "LX"
 #endif
-#if RESAMPLE
+#if RESAMPLE || RESAMPLE16
 						  "uR"
 #endif
 #if DSD
@@ -531,7 +538,7 @@ int main(int argc, char **argv) {
 			exit(0);
 			break;
 #endif			
-#if RESAMPLE
+#if RESAMPLE || RESAMPLE16
 		case 'u':
 		case 'R':
 			if (optind < argc && argv[optind] && argv[optind][0] != '-') {
@@ -783,7 +790,7 @@ else if(strstr(output_device,"DAC")!=NULL || strstr(output_device,"dac")!=NULL){
 
 	decode_init(log_decode, include_codecs, exclude_codecs);
 
-#if RESAMPLE
+#if RESAMPLE || RESAMPLE16
 	if (resample) {
 		process_init(resample);
 	}

+ 1 - 1
main/process.c

@@ -39,7 +39,7 @@ extern struct codec *codec;
 
 // macros to map to processing functions - currently only resample.c
 // this can be made more generic when multiple processing mechanisms get added
-#if RESAMPLE
+#if RESAMPLE || RESAMPLE16
 #define SAMPLES_FUNC resample_samples
 #define DRAIN_FUNC   resample_drain
 #define NEWSTREAM_FUNC resample_newstream

+ 2 - 2
main/resample.c

@@ -323,8 +323,8 @@ bool resample_init(char *opt) {
 	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_passband_end = 0.75;
+	r->q_stopband_begin = 1.25;
 
 	if (recipe && recipe[0] != '\0') {
 		if (strchr(recipe, 'v')) r->q_recipe = SOXR_VHQ;

+ 163 - 0
main/resample16.c

@@ -0,0 +1,163 @@
+/* 
+ *  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 RESAMPLE16
+
+#include <resample16.h>
+
+extern log_level loglevel;
+
+struct resample16 {
+	struct resample16_s *resampler;
+	bool max_rate;
+	bool exception;
+	bool interp;
+	resample16_filter_e filter;
+};
+
+static struct resample16 r;
+
+void resample_samples(struct processstate *process) {
+	ssize_t odone;
+	
+	odone = resample16(r.resampler, (HWORD*) process->inbuf, process->in_frames, (HWORD*) process->outbuf);
+
+	if (odone < 0) {
+		LOG_INFO("resample16 error");
+		return;
+	}
+	
+	process->out_frames = odone;
+	process->total_in  += process->in_frames;
+	process->total_out += odone;
+}
+
+bool resample_drain(struct processstate *process) {
+	process->out_frames = 0;
+	
+	LOG_INFO("resample track complete");
+
+	resample16_delete(r.resampler);
+	r.resampler = NULL;
+
+	return true;
+}
+
+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) {
+		resample16_delete(r.resampler);
+		r.resampler = NULL;
+	}
+
+	if (raw_sample_rate != outrate) {
+
+		LOG_INFO("resampling from %u -> %u", raw_sample_rate, outrate);
+		r.resampler = resample16_create((float) outrate / raw_sample_rate, RESAMPLE16_SMALL, false);
+
+		return true;
+
+	} else {
+
+		LOG_INFO("disable resampling - rates match");
+		return false;
+	}
+}
+
+void resample_flush(void) {
+	if (r.resampler) {
+		resample16_delete(r.resampler);
+		r.resampler = NULL;
+	}
+}
+
+bool resample_init(char *opt) {
+	char *filter = NULL, *interp = NULL;
+	
+	r.resampler = NULL;
+	r.max_rate = false;
+	r.exception = false;
+
+	if (opt) {
+		filter = next_param(opt, ':');
+		interp = next_param(NULL, ':');
+	}
+
+	if (filter) {
+		if (*filter == 'm') r.filter = RESAMPLE16_SMALL;
+		else r.filter = RESAMPLE16_FAST;
+	}
+
+	if (interp && *interp == 'i') {
+		r.interp = true;	
+	}
+	
+	LOG_INFO("Resampling with filter %d %s", r.filter, r.interp ? "(interpolated)" : "");
+
+	return true;
+}
+
+#endif // #if RESAMPLE16

+ 5 - 1
main/squeezelite.h

@@ -132,6 +132,10 @@
 #undef  RESAMPLE
 #define RESAMPLE  1 // resampling
 #define PROCESS   1 // any sample processing (only resampling at present)
+#elif defined(RESAMPLE16)
+#undef RESAMPLE16
+#define RESAMPLE16	1
+#define PROCESS		1
 #else
 #define RESAMPLE  0
 #define PROCESS   0
@@ -647,7 +651,7 @@ unsigned process_newstream(bool *direct, unsigned raw_sample_rate, unsigned supp
 void process_init(char *opt);
 #endif
 
-#if RESAMPLE
+#if RESAMPLE || RESAMPLE16
 // resample.c
 void resample_samples(struct processstate *process);
 bool resample_drain(struct processstate *process);

+ 1 - 1
main/vorbis.c

@@ -348,7 +348,7 @@ struct codec *register_vorbis(void) {
 	static struct codec ret = {
 		'o',          // id
 		"ogg",        // types
-		2048,         // min read
+		4096,         // min read
 		20480,        // min space
 		vorbis_open,  // open
 		vorbis_close, // close