summaryrefslogtreecommitdiff
path: root/src/pcm
diff options
context:
space:
mode:
authorbitkeeper <bitkeeper@users.noreply.github.com>2020-07-29 23:07:16 +0200
committerMax Kellermann <max@musicpd.org>2020-09-04 18:32:03 +0200
commit9aa432c07838256536c39b00d14c04462862b4fc (patch)
treef47d72870fbffdd4040bd3e18133deb1af30a3be /src/pcm
parent38498d3ee268e044081ce6dd3d9f6e254817d069 (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.cxx131
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;