diff options
author | bitkeeper <bitkeeper@users.noreply.github.com> | 2020-07-29 23:07:16 +0200 |
---|---|---|
committer | Max Kellermann <max@musicpd.org> | 2020-09-04 18:32:03 +0200 |
commit | 9aa432c07838256536c39b00d14c04462862b4fc (patch) | |
tree | f47d72870fbffdd4040bd3e18133deb1af30a3be /src/pcm | |
parent | 38498d3ee268e044081ce6dd3d9f6e254817d069 (diff) |
Support soxr custom recipes.
MPD uses soxr with prefined resample recipes. Soxr also support defining a recipe your self.
This commit will support a custom recipe by changing the existing quality setting to "custom".
The same structs as the predefined recipes uses can now set by hand.
This will make the following settings available:
- precision 16|20|24|28|32 bits, example "28"
- phase_response - 0-100, example "45"
- passband_end - used bandwidth of source 80-99.7%, example "99.7.0"
- stopband_begin - anti aliasing 100.0+%, example "100".
- attenuation - signal reduciton in dB's, 0-30. example "3.0".
- flags "0" - additional bitmask with extra settings
The data is set in the structs soxr_quality_spec and soxr_io_spec (found in soxr.h).
Diffstat (limited to 'src/pcm')
-rw-r--r-- | src/pcm/SoxrResampler.cxx | 131 |
1 files changed, 127 insertions, 4 deletions
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; |