diff --git a/components/codecs/component.mk b/components/codecs/component.mk index 19181656..9707fc8d 100644 --- a/components/codecs/component.mk +++ b/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 diff --git a/components/codecs/inc/resample16/resample16.h b/components/codecs/inc/resample16/resample16.h new file mode 100644 index 00000000..ddf3aced --- /dev/null +++ b/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< 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 \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 ' 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); } diff --git a/main/process.c b/main/process.c index 7c2cf94f..af3457d3 100644 --- a/main/process.c +++ b/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 diff --git a/main/resample.c b/main/resample.c index d01b5e4d..5466321c 100644 --- a/main/resample.c +++ b/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; diff --git a/main/resample16.c b/main/resample16.c new file mode 100644 index 00000000..f86ac798 --- /dev/null +++ b/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 . + * + */ + +// upsampling using libsoxr - only included if RESAMPLE set + +#include "squeezelite.h" + +#if RESAMPLE16 + +#include + +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 diff --git a/main/squeezelite.h b/main/squeezelite.h index a20fb306..59ced6e4 100644 --- a/main/squeezelite.h +++ b/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); diff --git a/main/vorbis.c b/main/vorbis.c index 1326c441..0c35eb50 100644 --- a/main/vorbis.c +++ b/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