diff --git a/components/codecs/component.mk b/components/codecs/component.mk index 3c921edc..19181656 100644 --- a/components/codecs/component.mk +++ b/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 diff --git a/components/codecs/inc/soxr/soxr.h b/components/codecs/inc/soxr/soxr.h new file mode 100644 index 00000000..8d9622df --- /dev/null +++ b/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 + extern "C" { +#else + #include +#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 diff --git a/components/codecs/lib/libsoxr.a b/components/codecs/lib/libsoxr.a new file mode 100644 index 00000000..02c5e2d5 Binary files /dev/null and b/components/codecs/lib/libsoxr.a differ diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index c10c36b7..0321d426 100644 --- a/main/Kconfig.projbuild +++ b/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 diff --git a/main/alac.c b/main/alac.c index 2d3c8761..60eb0fa7 100644 --- a/main/alac.c +++ b/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; diff --git a/main/component.mk b/main/component.mk index 03f04ab5..8ea865c5 100644 --- a/main/component.mk +++ b/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 diff --git a/main/process.c b/main/process.c new file mode 100644 index 00000000..7c2cf94f --- /dev/null +++ b/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 . + * + */ + +// 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 diff --git a/main/resample.c b/main/resample.c new file mode 100644 index 00000000..43dc20b3 --- /dev/null +++ b/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 . + * + */ + +// upsampling using libsoxr - only included if RESAMPLE set + +#include "squeezelite.h" + +#if RESAMPLE + +#include +#include + +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