From 9f55708e9de287a3fa8a6edb416b591a8f64adf3 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 27 Jun 2022 11:19:01 +0200 Subject: [PATCH] audioconvert: Always apply noise when asked Rename empty.noise -> dither.noise and always add this amount of noise when > 0. This also adds the noise to silent sounds, not only when nothing is connected because that would also be a problem when an amp needs to be kept alive with an non-0 signal. Rename noise -> dither because we can use this also for dithering later. See #705 --- spa/plugins/audioconvert/audioconvert.c | 50 ++++++++++-------- .../{noise-ops-c.c => dither-ops-c.c} | 19 ++++--- .../{noise-ops.c => dither-ops.c} | 41 +++++++-------- .../{noise-ops.h => dither-ops.h} | 51 +++++++++++++++---- spa/plugins/audioconvert/meson.build | 4 +- src/daemon/client-rt.conf.in | 2 +- src/daemon/client.conf.in | 2 +- src/daemon/minimal.conf.in | 4 +- src/daemon/pipewire-pulse.conf.in | 2 +- 9 files changed, 107 insertions(+), 68 deletions(-) rename spa/plugins/audioconvert/{noise-ops-c.c => dither-ops-c.c} (76%) rename spa/plugins/audioconvert/{noise-ops.c => dither-ops.c} (65%) rename spa/plugins/audioconvert/{noise-ops.h => dither-ops.h} (50%) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 994908715..5f0b53426 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -49,7 +49,7 @@ #include "fmt-ops.h" #include "channelmix-ops.h" #include "resample.h" -#include "noise-ops.h" +#include "dither-ops.h" #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT log_topic @@ -93,7 +93,7 @@ struct props { unsigned int resample_quality; unsigned int resample_disabled:1; double rate; - uint32_t empty_noise; + uint32_t dither_noise; }; static void props_reset(struct props *props) @@ -110,7 +110,7 @@ static void props_reset(struct props *props) props->rate = 1.0; props->resample_quality = RESAMPLE_DEFAULT_QUALITY; props->resample_disabled = false; - props->empty_noise = 0; + props->dither_noise = 0; } struct buffer { @@ -212,7 +212,7 @@ struct impl { struct channelmix mix; struct resample resample; struct volume volume; - struct noise noise; + struct dither dither; double rate_scale; uint32_t in_offset; @@ -612,9 +612,9 @@ static int impl_node_enum_params(void *object, int seq, case 21: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("empty.noise"), - SPA_PROP_INFO_description, SPA_POD_String("Fill empty buffers with noise"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->empty_noise, 0, 16), + SPA_PROP_INFO_name, SPA_POD_String("dither.noise"), + SPA_PROP_INFO_description, SPA_POD_String("Add dithering noise"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->dither_noise, 0, 16), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; default: @@ -683,8 +683,8 @@ static int impl_node_enum_params(void *object, int seq, spa_pod_builder_int(&b, p->resample_quality); spa_pod_builder_string(&b, "resample.disable"); spa_pod_builder_bool(&b, p->resample_disabled); - spa_pod_builder_string(&b, "empty.noise"); - spa_pod_builder_int(&b, p->empty_noise); + spa_pod_builder_string(&b, "dither.noise"); + spa_pod_builder_int(&b, p->dither_noise); spa_pod_builder_pop(&b, &f[1]); param = spa_pod_builder_pop(&b, &f[0]); break; @@ -754,8 +754,8 @@ static int audioconvert_set_param(struct impl *this, const char *k, const char * this->props.resample_quality = atoi(s); else if (spa_streq(k, "resample.disable")) this->props.resample_disabled = spa_atob(s); - else if (spa_streq(k, "empty.noise")) - spa_atou32(s, &this->props.empty_noise, 0); + else if (spa_streq(k, "dither.noise")) + spa_atou32(s, &this->props.dither_noise, 0); else return 0; return 1; @@ -1395,17 +1395,18 @@ static int setup_out_convert(struct impl *this) spa_log_debug(this->log, "%p: got converter features %08x:%08x passthrough:%d", this, this->cpu_flags, out->conv.cpu_flags, out->conv.is_passthrough); - this->noise.intensity = (calc_width(&dst_info) * 8) - 1; - this->noise.intensity -= SPA_MIN(this->noise.intensity, this->props.empty_noise); - this->noise.n_channels = dst_info.info.raw.channels; - this->noise.cpu_flags = this->cpu_flags; + if (this->props.dither_noise > 0) { + this->dither.intensity = (calc_width(&dst_info) * 8) - 1; + this->dither.intensity -= SPA_MIN(this->dither.intensity, this->props.dither_noise); + this->dither.n_channels = dst_info.info.raw.channels; + this->dither.cpu_flags = this->cpu_flags; - if ((res = noise_init(&this->noise)) < 0) - return res; - - spa_log_debug(this->log, "%p: empty noise:%d intensity:%d", this, - this->props.empty_noise, this->noise.intensity); + if ((res = dither_init(&this->dither)) < 0) + return res; + spa_log_debug(this->log, "%p: dither noise:%d intensity:%d", this, + this->props.dither_noise, this->dither.intensity); + } return 0; } @@ -2472,10 +2473,13 @@ static int impl_node_process(void *object) } this->out_offset += n_samples; - if (in_empty && this->props.empty_noise > 0) { + if (this->props.dither_noise > 0) { + in_datas = (const void**)out_datas; if (out_passthrough) out_datas = dst_datas; - noise_process(&this->noise, out_datas, n_samples); + else + out_datas = (void **)this->tmp_datas[(tmp++) & 1]; + dither_process(&this->dither, out_datas, in_datas, n_samples); in_empty = false; } if (!out_passthrough) { @@ -2657,6 +2661,8 @@ impl_init(const struct spa_handle_factory *factory, this->mix.rear_delay = 12.0f; this->mix.widen = 0.0f; + this->dither.log = this->log; + for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; diff --git a/spa/plugins/audioconvert/noise-ops-c.c b/spa/plugins/audioconvert/dither-ops-c.c similarity index 76% rename from spa/plugins/audioconvert/noise-ops-c.c rename to spa/plugins/audioconvert/dither-ops-c.c index 41563ec60..57f731dfc 100644 --- a/spa/plugins/audioconvert/noise-ops-c.c +++ b/spa/plugins/audioconvert/dither-ops-c.c @@ -22,18 +22,21 @@ * DEALINGS IN THE SOFTWARE. */ -#include "noise-ops.h" +#include "dither-ops.h" -void -noise_f32_c(struct noise *ns, void * SPA_RESTRICT dst[], uint32_t n_samples) +void dither_f32_c(struct dither *dt, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t i, n; + const float **s = (const float**)src; float **d = (float**)dst; - const float *t = ns->tab; - int tab_idx = ns->tab_idx; + const float *t = dt->tab; + int tab_idx = dt->tab_idx; - for (i = 0; i < ns->n_channels; i++) + for (i = 0; i < dt->n_channels; i++) { for (n = 0; n < n_samples; n++) - d[i][n] = t[tab_idx++ & NOISE_MOD]; - ns->tab_idx = (tab_idx + 23) & NOISE_MOD; + d[i][n] = s[i][n] + t[tab_idx++ & DITHER_MOD]; + tab_idx += 61; + } + dt->tab_idx = tab_idx & DITHER_MOD; } diff --git a/spa/plugins/audioconvert/noise-ops.c b/spa/plugins/audioconvert/dither-ops.c similarity index 65% rename from spa/plugins/audioconvert/noise-ops.c rename to spa/plugins/audioconvert/dither-ops.c index 1fd4081e4..0428ce35a 100644 --- a/spa/plugins/audioconvert/noise-ops.c +++ b/spa/plugins/audioconvert/dither-ops.c @@ -32,52 +32,53 @@ #include #include -#include "noise-ops.h" +#include "dither-ops.h" -typedef void (*noise_func_t) (struct noise *ns, void * SPA_RESTRICT dst[], uint32_t n_samples); +typedef void (*dither_func_t) (struct dither *d, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples); -static const struct noise_info { - noise_func_t process; +static const struct dither_info { + dither_func_t process; uint32_t cpu_flags; -} noise_table[] = +} dither_table[] = { - { noise_f32_c, 0 }, + { dither_f32_c, 0 }, }; #define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a) -static const struct noise_info *find_noise_info(uint32_t cpu_flags) +static const struct dither_info *find_dither_info(uint32_t cpu_flags) { size_t i; - for (i = 0; i < SPA_N_ELEMENTS(noise_table); i++) { - if (!MATCH_CPU_FLAGS(noise_table[i].cpu_flags, cpu_flags)) + for (i = 0; i < SPA_N_ELEMENTS(dither_table); i++) { + if (!MATCH_CPU_FLAGS(dither_table[i].cpu_flags, cpu_flags)) continue; - return &noise_table[i]; + return &dither_table[i]; } return NULL; } -static void impl_noise_free(struct noise *ns) +static void impl_dither_free(struct dither *d) { - ns->process = NULL; + d->process = NULL; } -int noise_init(struct noise *ns) +int dither_init(struct dither *d) { - const struct noise_info *info; + const struct dither_info *info; size_t i; - info = find_noise_info(ns->cpu_flags); + info = find_dither_info(d->cpu_flags); if (info == NULL) return -ENOTSUP; - if (ns->intensity >= 64) + if (d->intensity >= 64) return -EINVAL; - for (i = 0; i < SPA_N_ELEMENTS(ns->tab); i++) - ns->tab[i] = (drand48() - 0.5) / (UINT64_C(1) << ns->intensity); + for (i = 0; i < SPA_N_ELEMENTS(d->tab); i++) + d->tab[i] = (drand48() - 0.5) / (UINT64_C(1) << d->intensity); - ns->free = impl_noise_free; - ns->process = info->process; + d->free = impl_dither_free; + d->process = info->process; return 0; } diff --git a/spa/plugins/audioconvert/noise-ops.h b/spa/plugins/audioconvert/dither-ops.h similarity index 50% rename from spa/plugins/audioconvert/noise-ops.h rename to spa/plugins/audioconvert/dither-ops.h index 5f9cee027..19545472f 100644 --- a/spa/plugins/audioconvert/noise-ops.h +++ b/spa/plugins/audioconvert/dither-ops.h @@ -26,36 +26,65 @@ #include #include +#include #include -#define NOISE_SIZE (1<<8) -#define NOISE_MOD (NOISE_SIZE-1) +#define DITHER_SIZE (1<<8) +#define DITHER_MOD (DITHER_SIZE-1) -struct noise { +struct dither { uint32_t intensity; +#define DITHER_METHOD_NONE 0 +#define DITHER_METHOD_RECTANGULAR 2 +#define DITHER_METHOD_TRIANGULAR 3 +#define DITHER_METHOD_SHAPED_5 4 + uint32_t method; uint32_t n_channels; uint32_t cpu_flags; struct spa_log *log; - void (*process) (struct noise *ns, void * SPA_RESTRICT dst[], uint32_t n_samples); - void (*free) (struct noise *ns); + void (*process) (struct dither *d, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples); + void (*free) (struct dither *d); - float tab[NOISE_SIZE]; + float tab[DITHER_SIZE]; int tab_idx; }; -int noise_init(struct noise *ns); +int dither_init(struct dither *d); -#define noise_process(ns,...) (ns)->process(ns, __VA_ARGS__) -#define noise_free(ns) (ns)->free(ns) +static const struct dither_method_info { + const char *label; + const char *description; + uint32_t method; +} dither_method_info[] = { + [DITHER_METHOD_NONE] = { "none", "Disabled", DITHER_METHOD_NONE }, + [DITHER_METHOD_RECTANGULAR] = { "rectangular", "Rectangular dithering", DITHER_METHOD_RECTANGULAR }, + [DITHER_METHOD_TRIANGULAR] = { "triangular", "Triangular dithering", DITHER_METHOD_TRIANGULAR }, + [DITHER_METHOD_SHAPED_5] = { "shaped5", "Shaped 5 dithering", DITHER_METHOD_SHAPED_5 } +}; + +static inline uint32_t dither_method_from_label(const char *label) +{ + uint32_t i; + for (i = 0; i < SPA_N_ELEMENTS(dither_method_info); i++) { + if (spa_streq(dither_method_info[i].label, label)) + return dither_method_info[i].method; + } + return DITHER_METHOD_NONE; +} + +#define dither_process(d,...) (d)->process(d, __VA_ARGS__) +#define dither_free(d) (d)->free(d) #define DEFINE_FUNCTION(name,arch) \ -void noise_##name##_##arch(struct noise *ns, \ +void dither_##name##_##arch(struct dither *d, \ void * SPA_RESTRICT dst[], \ + const void * SPA_RESTRICT src[], \ uint32_t n_samples); -#define NOISE_OPS_MAX_ALIGN 16 +#define DITHER_OPS_MAX_ALIGN 16 DEFINE_FUNCTION(f32, c); diff --git a/spa/plugins/audioconvert/meson.build b/spa/plugins/audioconvert/meson.build index b4d824f30..e966a47d0 100644 --- a/spa/plugins/audioconvert/meson.build +++ b/spa/plugins/audioconvert/meson.build @@ -95,8 +95,8 @@ audioconvert_lib = static_library('audioconvert', 'fmt-ops-c.c', 'volume-ops.c', 'volume-ops-c.c', - 'noise-ops.c', - 'noise-ops-c.c' ], + 'dither-ops.c', + 'dither-ops-c.c' ], c_args : [ simd_cargs, '-O3'], link_with : simd_dependencies, include_directories : [configinc], diff --git a/src/daemon/client-rt.conf.in b/src/daemon/client-rt.conf.in index 9e7f00fa2..f62b1e228 100644 --- a/src/daemon/client-rt.conf.in +++ b/src/daemon/client-rt.conf.in @@ -90,5 +90,5 @@ stream.properties = { #channelmix.rear-delay = 12.0 #channelmix.stereo-widen = 0.0 #channelmix.hilbert-taps = 0 - #empty.noise = 0 + #dither.noise = 0 } diff --git a/src/daemon/client.conf.in b/src/daemon/client.conf.in index 0a0da4a48..d406b2478 100644 --- a/src/daemon/client.conf.in +++ b/src/daemon/client.conf.in @@ -80,5 +80,5 @@ stream.properties = { #channelmix.rear-delay = 12.0 #channelmix.stereo-widen = 0.0 #channelmix.hilbert-taps = 0 - #empty.noise = 0 + #dither.noise = 0 } diff --git a/src/daemon/minimal.conf.in b/src/daemon/minimal.conf.in index 1e36767e5..8de1f21e1 100644 --- a/src/daemon/minimal.conf.in +++ b/src/daemon/minimal.conf.in @@ -212,7 +212,7 @@ context.objects = [ #channelmix.stereo-widen = 0.0 #channelmix.hilbert-taps = 0 channelmix.disable = true - #empty.noise = 0 + #dither.noise = 0 #node.param.Props = { # params = [ # audio.channels 6 @@ -274,7 +274,7 @@ context.objects = [ #channelmix.stereo-widen = 0.0 #channelmix.hilbert-taps = 0 channelmix.disable = true - #empty.noise = 0 + #dither.noise = 0 #node.param.Props = { # params = [ # audio.format S16 diff --git a/src/daemon/pipewire-pulse.conf.in b/src/daemon/pipewire-pulse.conf.in index a3b43946e..1ee3494a0 100644 --- a/src/daemon/pipewire-pulse.conf.in +++ b/src/daemon/pipewire-pulse.conf.in @@ -66,7 +66,7 @@ stream.properties = { #channelmix.rear-delay = 12.0 #channelmix.stereo-widen = 0.0 #channelmix.hilbert-taps = 0 - #empty.noise = 0 + #dither.noise = 0 } pulse.properties = {