diff options
-rw-r--r-- | doc/plugins.rst | 19 | ||||
-rw-r--r-- | src/pcm/SoxrResampler.cxx | 131 |
2 files changed, 146 insertions, 4 deletions
diff --git a/doc/plugins.rst b/doc/plugins.rst index 2f63660df..49bc00b78 100644 --- a/doc/plugins.rst +++ b/doc/plugins.rst @@ -746,6 +746,25 @@ Valid quality values for libsoxr: * "medium" * "low" * "quick" +* "custom" + +If the quality is set to custom also the following settings are available: + + * - Name + - Description + * - **precision** + - The precision in bits. Valid values 16,20,24,28 and 32 bits. + * - **phase_response** + - Between the 0-100, Where 0=MINIMUM_PHASE and 50=LINEAR_PHASE. + * - **passband_end** + - The % of source bandwidth where to start filtering. Typical between the 90-99.7. + * - **stopband_begin** + - The % of the source bandwidth Where the anti aliasing filter start. Value 100+. + * - **attenuation** + - Reduction in dB's to prevent clipping from the resampling process. + * - **flags** + - Bitmask with additional option see soxr documentation for specific flags. + .. _output_plugins: diff --git a/src/pcm/SoxrResampler.cxx b/src/pcm/SoxrResampler.cxx index e896bef7b..35cc0d0e5 100644 --- a/src/pcm/SoxrResampler.cxx +++ b/src/pcm/SoxrResampler.cxx @@ -27,6 +27,7 @@ #include <soxr.h> #include <cassert> +#include <cmath> #include <string.h> @@ -39,8 +40,16 @@ static constexpr unsigned long SOXR_DEFAULT_RECIPE = SOXR_HQ; */ static constexpr unsigned long SOXR_INVALID_RECIPE = -1; +/** + * Special value for the recipe selection for custom recipe. + */ +static constexpr unsigned long SOXR_CUSTOM_RECIPE = -2; + +static soxr_io_spec_t soxr_io_custom_recipe; static soxr_quality_spec_t soxr_quality; static soxr_runtime_spec_t soxr_runtime; +static bool soxr_use_custom_recipe; + static constexpr struct { unsigned long recipe; @@ -51,6 +60,7 @@ static constexpr struct { { SOXR_MQ, "medium" }, { SOXR_LQ, "low" }, { SOXR_QQ, "quick" }, + { SOXR_CUSTOM_RECIPE, "custom" }, { SOXR_INVALID_RECIPE, nullptr } }; @@ -80,20 +90,116 @@ soxr_parse_quality(const char *quality) noexcept return SOXR_INVALID_RECIPE; } +static unsigned +SoxrParsePrecision(unsigned value) { + switch (value) { + case 16: + case 20: + case 24: + case 28: + case 32: + break; + default: + throw FormatInvalidArgument( + "soxr converter invalid precision : %d [16|20|24|28|32]", value); + } + return value; +} + +static double +SoxrParsePhaseResponse(unsigned value) { + if (value > 100) { + throw FormatInvalidArgument( + "soxr converter invalid phase_respons : %d (0-100)", value); + } + + return double(value); +} + +static double +SoxrParsePassbandEnd(const char *svalue) { + char *endptr; + double value = strtod(svalue, &endptr); + if (svalue == endptr || *endptr != 0) { + throw FormatInvalidArgument( + "soxr converter passband_end value not a number: %s", svalue); + } + + if (value < 1 || value > 100) { + throw FormatInvalidArgument( + "soxr converter invalid passband_end : %s (1-100%%)", svalue); + } + + return value / 100.0; +} + +static double +SoxrParseStopbandBegin(const char *svalue) { + char *endptr; + double value = strtod(svalue, &endptr); + if (svalue == endptr || *endptr != 0) { + throw FormatInvalidArgument( + "soxr converter stopband_begin value not a number: %s", svalue); + } + + if (value < 100 || value > 199) { + throw FormatInvalidArgument( + "soxr converter invalid stopband_begin : %s (100-150%%)", svalue); + } + + return value / 100.0; +} + +static double +SoxrParseAttenuation(const char *svalue) { + char *endptr; + double value = strtod(svalue, &endptr); + if (svalue == endptr || *endptr != 0) { + throw FormatInvalidArgument( + "soxr converter attenuation value not a number: %s", svalue); + } + + if (value < 0 || value > 30) { + throw FormatInvalidArgument( + "soxr converter invalid attenuation : %s (0-30dB))", svalue); + } + + return 1 / std::pow(10, value / 10.0); +} + void pcm_resample_soxr_global_init(const ConfigBlock &block) { const char *quality_string = block.GetBlockValue("quality"); unsigned long recipe = soxr_parse_quality(quality_string); + soxr_use_custom_recipe = recipe == SOXR_CUSTOM_RECIPE; + if (recipe == SOXR_INVALID_RECIPE) { assert(quality_string != nullptr); - throw FormatRuntimeError("unknown quality setting '%s' in line %d", quality_string, block.line); + } else if (recipe == SOXR_CUSTOM_RECIPE) { + // used to preset possible internal flags, like SOXR_RESET_ON_CLEAR + soxr_quality = soxr_quality_spec(SOXR_DEFAULT_RECIPE, 0); + soxr_io_custom_recipe = soxr_io_spec(SOXR_FLOAT32_I, SOXR_FLOAT32_I); + + soxr_quality.precision = + SoxrParsePrecision(block.GetBlockValue("precision", SOXR_HQ)); + soxr_quality.phase_response = + SoxrParsePhaseResponse(block.GetBlockValue("phase_response", 50)); + soxr_quality.passband_end = + SoxrParsePassbandEnd(block.GetBlockValue("passband_end", "95.0")); + soxr_quality.stopband_begin = SoxrParseStopbandBegin( + block.GetBlockValue("stopband_begin", "100.0")); + // see soxr.h soxr_quality_spec.flags + soxr_quality.flags = (soxr_quality.flags & 0xFFFFFFC0) | + (block.GetBlockValue("flags", 0) & 0x3F); + soxr_io_custom_recipe.scale = + SoxrParseAttenuation(block.GetBlockValue("attenuation", "0")); + } else { + soxr_quality = soxr_quality_spec(recipe, 0); } - soxr_quality = soxr_quality_spec(recipe, 0); - FormatDebug(soxr_domain, "soxr converter '%s'", soxr_quality_name(recipe)); @@ -109,14 +215,31 @@ SoxrPcmResampler::Open(AudioFormat &af, unsigned new_sample_rate) assert(audio_valid_sample_rate(new_sample_rate)); soxr_error_t e; + soxr_io_spec_t* p_soxr_io = nullptr; + if(soxr_use_custom_recipe) { + p_soxr_io = & soxr_io_custom_recipe; + } soxr = soxr_create(af.sample_rate, new_sample_rate, af.channels, &e, - nullptr, &soxr_quality, &soxr_runtime); + p_soxr_io, &soxr_quality, &soxr_runtime); if (soxr == nullptr) throw FormatRuntimeError("soxr initialization has failed: %s", e); FormatDebug(soxr_domain, "soxr engine '%s'", soxr_engine(soxr)); + if (soxr_use_custom_recipe) + FormatDebug(soxr_domain, + "soxr precision=%0.0f, phase_response=%0.2f, " + "passband_end=%0.2f, stopband_begin=%0.2f scale=%0.2f", + soxr_quality.precision, soxr_quality.phase_response, + soxr_quality.passband_end, soxr_quality.stopband_begin, + soxr_io_custom_recipe.scale); + else + FormatDebug(soxr_domain, + "soxr precision=%0.0f, phase_response=%0.2f, " + "passband_end=%0.2f, stopband_begin=%0.2f", + soxr_quality.precision, soxr_quality.phase_response, + soxr_quality.passband_end, soxr_quality.stopband_begin); channels = af.channels; |