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).
This commit is contained in:
parent
38498d3ee2
commit
9aa432c078
@ -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:
|
||||
|
||||
|
@ -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,19 +90,115 @@ 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 = soxr_quality_spec(recipe, 0);
|
||||
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);
|
||||
}
|
||||
|
||||
FormatDebug(soxr_domain,
|
||||
"soxr converter '%s'",
|
||||
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user