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
This commit is contained in:
Wim Taymans 2022-06-27 11:19:01 +02:00
parent abcf7cb8d8
commit 9f55708e9d
9 changed files with 107 additions and 68 deletions

View file

@ -49,7 +49,7 @@
#include "fmt-ops.h" #include "fmt-ops.h"
#include "channelmix-ops.h" #include "channelmix-ops.h"
#include "resample.h" #include "resample.h"
#include "noise-ops.h" #include "dither-ops.h"
#undef SPA_LOG_TOPIC_DEFAULT #undef SPA_LOG_TOPIC_DEFAULT
#define SPA_LOG_TOPIC_DEFAULT log_topic #define SPA_LOG_TOPIC_DEFAULT log_topic
@ -93,7 +93,7 @@ struct props {
unsigned int resample_quality; unsigned int resample_quality;
unsigned int resample_disabled:1; unsigned int resample_disabled:1;
double rate; double rate;
uint32_t empty_noise; uint32_t dither_noise;
}; };
static void props_reset(struct props *props) static void props_reset(struct props *props)
@ -110,7 +110,7 @@ static void props_reset(struct props *props)
props->rate = 1.0; props->rate = 1.0;
props->resample_quality = RESAMPLE_DEFAULT_QUALITY; props->resample_quality = RESAMPLE_DEFAULT_QUALITY;
props->resample_disabled = false; props->resample_disabled = false;
props->empty_noise = 0; props->dither_noise = 0;
} }
struct buffer { struct buffer {
@ -212,7 +212,7 @@ struct impl {
struct channelmix mix; struct channelmix mix;
struct resample resample; struct resample resample;
struct volume volume; struct volume volume;
struct noise noise; struct dither dither;
double rate_scale; double rate_scale;
uint32_t in_offset; uint32_t in_offset;
@ -612,9 +612,9 @@ static int impl_node_enum_params(void *object, int seq,
case 21: case 21:
param = spa_pod_builder_add_object(&b, param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_PropInfo, id, SPA_TYPE_OBJECT_PropInfo, id,
SPA_PROP_INFO_name, SPA_POD_String("empty.noise"), SPA_PROP_INFO_name, SPA_POD_String("dither.noise"),
SPA_PROP_INFO_description, SPA_POD_String("Fill empty buffers with noise"), SPA_PROP_INFO_description, SPA_POD_String("Add dithering noise"),
SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->empty_noise, 0, 16), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->dither_noise, 0, 16),
SPA_PROP_INFO_params, SPA_POD_Bool(true)); SPA_PROP_INFO_params, SPA_POD_Bool(true));
break; break;
default: 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_int(&b, p->resample_quality);
spa_pod_builder_string(&b, "resample.disable"); spa_pod_builder_string(&b, "resample.disable");
spa_pod_builder_bool(&b, p->resample_disabled); spa_pod_builder_bool(&b, p->resample_disabled);
spa_pod_builder_string(&b, "empty.noise"); spa_pod_builder_string(&b, "dither.noise");
spa_pod_builder_int(&b, p->empty_noise); spa_pod_builder_int(&b, p->dither_noise);
spa_pod_builder_pop(&b, &f[1]); spa_pod_builder_pop(&b, &f[1]);
param = spa_pod_builder_pop(&b, &f[0]); param = spa_pod_builder_pop(&b, &f[0]);
break; break;
@ -754,8 +754,8 @@ static int audioconvert_set_param(struct impl *this, const char *k, const char *
this->props.resample_quality = atoi(s); this->props.resample_quality = atoi(s);
else if (spa_streq(k, "resample.disable")) else if (spa_streq(k, "resample.disable"))
this->props.resample_disabled = spa_atob(s); this->props.resample_disabled = spa_atob(s);
else if (spa_streq(k, "empty.noise")) else if (spa_streq(k, "dither.noise"))
spa_atou32(s, &this->props.empty_noise, 0); spa_atou32(s, &this->props.dither_noise, 0);
else else
return 0; return 0;
return 1; 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, 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->cpu_flags, out->conv.cpu_flags, out->conv.is_passthrough);
this->noise.intensity = (calc_width(&dst_info) * 8) - 1; if (this->props.dither_noise > 0) {
this->noise.intensity -= SPA_MIN(this->noise.intensity, this->props.empty_noise); this->dither.intensity = (calc_width(&dst_info) * 8) - 1;
this->noise.n_channels = dst_info.info.raw.channels; this->dither.intensity -= SPA_MIN(this->dither.intensity, this->props.dither_noise);
this->noise.cpu_flags = this->cpu_flags; this->dither.n_channels = dst_info.info.raw.channels;
this->dither.cpu_flags = this->cpu_flags;
if ((res = noise_init(&this->noise)) < 0) if ((res = dither_init(&this->dither)) < 0)
return res; return res;
spa_log_debug(this->log, "%p: empty noise:%d intensity:%d", this,
this->props.empty_noise, this->noise.intensity);
spa_log_debug(this->log, "%p: dither noise:%d intensity:%d", this,
this->props.dither_noise, this->dither.intensity);
}
return 0; return 0;
} }
@ -2472,10 +2473,13 @@ static int impl_node_process(void *object)
} }
this->out_offset += n_samples; 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) if (out_passthrough)
out_datas = dst_datas; 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; in_empty = false;
} }
if (!out_passthrough) { if (!out_passthrough) {
@ -2657,6 +2661,8 @@ impl_init(const struct spa_handle_factory *factory,
this->mix.rear_delay = 12.0f; this->mix.rear_delay = 12.0f;
this->mix.widen = 0.0f; this->mix.widen = 0.0f;
this->dither.log = this->log;
for (i = 0; info && i < info->n_items; i++) { for (i = 0; info && i < info->n_items; i++) {
const char *k = info->items[i].key; const char *k = info->items[i].key;
const char *s = info->items[i].value; const char *s = info->items[i].value;

View file

@ -22,18 +22,21 @@
* DEALINGS IN THE SOFTWARE. * DEALINGS IN THE SOFTWARE.
*/ */
#include "noise-ops.h" #include "dither-ops.h"
void void dither_f32_c(struct dither *dt, void * SPA_RESTRICT dst[],
noise_f32_c(struct noise *ns, void * SPA_RESTRICT dst[], uint32_t n_samples) const void * SPA_RESTRICT src[], uint32_t n_samples)
{ {
uint32_t i, n; uint32_t i, n;
const float **s = (const float**)src;
float **d = (float**)dst; float **d = (float**)dst;
const float *t = ns->tab; const float *t = dt->tab;
int tab_idx = ns->tab_idx; 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++) for (n = 0; n < n_samples; n++)
d[i][n] = t[tab_idx++ & NOISE_MOD]; d[i][n] = s[i][n] + t[tab_idx++ & DITHER_MOD];
ns->tab_idx = (tab_idx + 23) & NOISE_MOD; tab_idx += 61;
}
dt->tab_idx = tab_idx & DITHER_MOD;
} }

View file

@ -32,52 +32,53 @@
#include <spa/support/log.h> #include <spa/support/log.h>
#include <spa/utils/defs.h> #include <spa/utils/defs.h>
#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 { static const struct dither_info {
noise_func_t process; dither_func_t process;
uint32_t cpu_flags; 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) #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; size_t i;
for (i = 0; i < SPA_N_ELEMENTS(noise_table); i++) { for (i = 0; i < SPA_N_ELEMENTS(dither_table); i++) {
if (!MATCH_CPU_FLAGS(noise_table[i].cpu_flags, cpu_flags)) if (!MATCH_CPU_FLAGS(dither_table[i].cpu_flags, cpu_flags))
continue; continue;
return &noise_table[i]; return &dither_table[i];
} }
return NULL; 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; size_t i;
info = find_noise_info(ns->cpu_flags); info = find_dither_info(d->cpu_flags);
if (info == NULL) if (info == NULL)
return -ENOTSUP; return -ENOTSUP;
if (ns->intensity >= 64) if (d->intensity >= 64)
return -EINVAL; return -EINVAL;
for (i = 0; i < SPA_N_ELEMENTS(ns->tab); i++) for (i = 0; i < SPA_N_ELEMENTS(d->tab); i++)
ns->tab[i] = (drand48() - 0.5) / (UINT64_C(1) << ns->intensity); d->tab[i] = (drand48() - 0.5) / (UINT64_C(1) << d->intensity);
ns->free = impl_noise_free; d->free = impl_dither_free;
ns->process = info->process; d->process = info->process;
return 0; return 0;
} }

View file

@ -26,36 +26,65 @@
#include <stdio.h> #include <stdio.h>
#include <spa/utils/defs.h> #include <spa/utils/defs.h>
#include <spa/utils/string.h>
#include <spa/param/audio/raw.h> #include <spa/param/audio/raw.h>
#define NOISE_SIZE (1<<8) #define DITHER_SIZE (1<<8)
#define NOISE_MOD (NOISE_SIZE-1) #define DITHER_MOD (DITHER_SIZE-1)
struct noise { struct dither {
uint32_t intensity; 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 n_channels;
uint32_t cpu_flags; uint32_t cpu_flags;
struct spa_log *log; struct spa_log *log;
void (*process) (struct noise *ns, void * SPA_RESTRICT dst[], uint32_t n_samples); void (*process) (struct dither *d, void * SPA_RESTRICT dst[],
void (*free) (struct noise *ns); 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 tab_idx;
}; };
int noise_init(struct noise *ns); int dither_init(struct dither *d);
#define noise_process(ns,...) (ns)->process(ns, __VA_ARGS__) static const struct dither_method_info {
#define noise_free(ns) (ns)->free(ns) 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) \ #define DEFINE_FUNCTION(name,arch) \
void noise_##name##_##arch(struct noise *ns, \ void dither_##name##_##arch(struct dither *d, \
void * SPA_RESTRICT dst[], \ void * SPA_RESTRICT dst[], \
const void * SPA_RESTRICT src[], \
uint32_t n_samples); uint32_t n_samples);
#define NOISE_OPS_MAX_ALIGN 16 #define DITHER_OPS_MAX_ALIGN 16
DEFINE_FUNCTION(f32, c); DEFINE_FUNCTION(f32, c);

View file

@ -95,8 +95,8 @@ audioconvert_lib = static_library('audioconvert',
'fmt-ops-c.c', 'fmt-ops-c.c',
'volume-ops.c', 'volume-ops.c',
'volume-ops-c.c', 'volume-ops-c.c',
'noise-ops.c', 'dither-ops.c',
'noise-ops-c.c' ], 'dither-ops-c.c' ],
c_args : [ simd_cargs, '-O3'], c_args : [ simd_cargs, '-O3'],
link_with : simd_dependencies, link_with : simd_dependencies,
include_directories : [configinc], include_directories : [configinc],

View file

@ -90,5 +90,5 @@ stream.properties = {
#channelmix.rear-delay = 12.0 #channelmix.rear-delay = 12.0
#channelmix.stereo-widen = 0.0 #channelmix.stereo-widen = 0.0
#channelmix.hilbert-taps = 0 #channelmix.hilbert-taps = 0
#empty.noise = 0 #dither.noise = 0
} }

View file

@ -80,5 +80,5 @@ stream.properties = {
#channelmix.rear-delay = 12.0 #channelmix.rear-delay = 12.0
#channelmix.stereo-widen = 0.0 #channelmix.stereo-widen = 0.0
#channelmix.hilbert-taps = 0 #channelmix.hilbert-taps = 0
#empty.noise = 0 #dither.noise = 0
} }

View file

@ -212,7 +212,7 @@ context.objects = [
#channelmix.stereo-widen = 0.0 #channelmix.stereo-widen = 0.0
#channelmix.hilbert-taps = 0 #channelmix.hilbert-taps = 0
channelmix.disable = true channelmix.disable = true
#empty.noise = 0 #dither.noise = 0
#node.param.Props = { #node.param.Props = {
# params = [ # params = [
# audio.channels 6 # audio.channels 6
@ -274,7 +274,7 @@ context.objects = [
#channelmix.stereo-widen = 0.0 #channelmix.stereo-widen = 0.0
#channelmix.hilbert-taps = 0 #channelmix.hilbert-taps = 0
channelmix.disable = true channelmix.disable = true
#empty.noise = 0 #dither.noise = 0
#node.param.Props = { #node.param.Props = {
# params = [ # params = [
# audio.format S16 # audio.format S16

View file

@ -66,7 +66,7 @@ stream.properties = {
#channelmix.rear-delay = 12.0 #channelmix.rear-delay = 12.0
#channelmix.stereo-widen = 0.0 #channelmix.stereo-widen = 0.0
#channelmix.hilbert-taps = 0 #channelmix.hilbert-taps = 0
#empty.noise = 0 #dither.noise = 0
} }
pulse.properties = { pulse.properties = {