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"
|
* "medium"
|
||||||
* "low"
|
* "low"
|
||||||
* "quick"
|
* "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:
|
.. _output_plugins:
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
#include <soxr.h>
|
#include <soxr.h>
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
@ -39,8 +40,16 @@ static constexpr unsigned long SOXR_DEFAULT_RECIPE = SOXR_HQ;
|
|||||||
*/
|
*/
|
||||||
static constexpr unsigned long SOXR_INVALID_RECIPE = -1;
|
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_quality_spec_t soxr_quality;
|
||||||
static soxr_runtime_spec_t soxr_runtime;
|
static soxr_runtime_spec_t soxr_runtime;
|
||||||
|
static bool soxr_use_custom_recipe;
|
||||||
|
|
||||||
|
|
||||||
static constexpr struct {
|
static constexpr struct {
|
||||||
unsigned long recipe;
|
unsigned long recipe;
|
||||||
@ -51,6 +60,7 @@ static constexpr struct {
|
|||||||
{ SOXR_MQ, "medium" },
|
{ SOXR_MQ, "medium" },
|
||||||
{ SOXR_LQ, "low" },
|
{ SOXR_LQ, "low" },
|
||||||
{ SOXR_QQ, "quick" },
|
{ SOXR_QQ, "quick" },
|
||||||
|
{ SOXR_CUSTOM_RECIPE, "custom" },
|
||||||
{ SOXR_INVALID_RECIPE, nullptr }
|
{ SOXR_INVALID_RECIPE, nullptr }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -80,19 +90,115 @@ soxr_parse_quality(const char *quality) noexcept
|
|||||||
return SOXR_INVALID_RECIPE;
|
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
|
void
|
||||||
pcm_resample_soxr_global_init(const ConfigBlock &block)
|
pcm_resample_soxr_global_init(const ConfigBlock &block)
|
||||||
{
|
{
|
||||||
const char *quality_string = block.GetBlockValue("quality");
|
const char *quality_string = block.GetBlockValue("quality");
|
||||||
unsigned long recipe = soxr_parse_quality(quality_string);
|
unsigned long recipe = soxr_parse_quality(quality_string);
|
||||||
|
soxr_use_custom_recipe = recipe == SOXR_CUSTOM_RECIPE;
|
||||||
|
|
||||||
if (recipe == SOXR_INVALID_RECIPE) {
|
if (recipe == SOXR_INVALID_RECIPE) {
|
||||||
assert(quality_string != nullptr);
|
assert(quality_string != nullptr);
|
||||||
|
|
||||||
throw FormatRuntimeError("unknown quality setting '%s' in line %d",
|
throw FormatRuntimeError("unknown quality setting '%s' in line %d",
|
||||||
quality_string, block.line);
|
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,
|
FormatDebug(soxr_domain,
|
||||||
"soxr converter '%s'",
|
"soxr converter '%s'",
|
||||||
@ -109,14 +215,31 @@ SoxrPcmResampler::Open(AudioFormat &af, unsigned new_sample_rate)
|
|||||||
assert(audio_valid_sample_rate(new_sample_rate));
|
assert(audio_valid_sample_rate(new_sample_rate));
|
||||||
|
|
||||||
soxr_error_t e;
|
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,
|
soxr = soxr_create(af.sample_rate, new_sample_rate,
|
||||||
af.channels, &e,
|
af.channels, &e,
|
||||||
nullptr, &soxr_quality, &soxr_runtime);
|
p_soxr_io, &soxr_quality, &soxr_runtime);
|
||||||
if (soxr == nullptr)
|
if (soxr == nullptr)
|
||||||
throw FormatRuntimeError("soxr initialization has failed: %s",
|
throw FormatRuntimeError("soxr initialization has failed: %s",
|
||||||
e);
|
e);
|
||||||
|
|
||||||
FormatDebug(soxr_domain, "soxr engine '%s'", soxr_engine(soxr));
|
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;
|
channels = af.channels;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user