modules: port parametric-equalizer to filter-chain

Simply load a filter-chain with the new param_eq node and the
given filename in the config.

This fixes a number of issues such as not copying global properties to the
streams, setting a unique node.name etc...
This commit is contained in:
Wim Taymans 2024-10-11 11:26:27 +02:00
parent ab20cc5f28
commit 673352893a

View file

@ -135,8 +135,6 @@ struct impl {
struct spa_hook module_listener;
struct spa_hook eq_module_listener;
char position[64];
uint32_t channels;
unsigned int do_disconnect:1;
};
@ -160,168 +158,82 @@ static const struct pw_impl_module_events filter_chain_module_events = {
.destroy = filter_chain_module_destroy,
};
void init_eq_node(FILE *f, const char *node_desc)
static int enhance_properties(struct pw_properties *props, const char *key, ...)
{
fprintf(f, "{\n");
fprintf(f, "node.description = \"%s\"\n", node_desc);
fprintf(f, "media.name = \"%s\"\n", node_desc);
fprintf(f, "filter.graph = {\n");
fprintf(f, "nodes = [\n");
}
void add_eq_node(FILE *f, struct eq_node_param *param, uint32_t eq_band_idx)
{
char str1[64], str2[64];
fprintf(f, "{\n");
fprintf(f, "type = builtin\n");
fprintf(f, "name = eq_band_%d\n", eq_band_idx);
if (strcmp(param->filter_type, "PK") == 0) {
fprintf(f, "label = bq_peaking\n");
} else if (strcmp(param->filter_type, "LSC") == 0) {
fprintf(f, "label = bq_lowshelf\n");
} else if (strcmp(param->filter_type, "HSC") == 0) {
fprintf(f, "label = bq_highshelf\n");
} else {
fprintf(f, "label = bq_peaking\n");
}
fprintf(f, "control = { \"Freq\" = %d \"Q\" = %s \"Gain\" = %s }\n", param->freq,
spa_json_format_float(str1, sizeof(str1), param->q_fact),
spa_json_format_float(str2, sizeof(str2), param->gain));
fprintf(f, "}\n");
}
void end_eq_node(struct impl *impl, FILE *f, uint32_t number_of_nodes)
{
struct pw_properties *capture_props, *playback_props;
const uint32_t serialize_flags = PW_PROPERTIES_FLAG_NL |
PW_PROPERTIES_FLAG_ENCLOSE | PW_PROPERTIES_FLAG_RECURSE;
const char* str = NULL;
fprintf(f, "]\n");
fprintf(f, "links = [\n");
for (uint32_t i = 1; i < number_of_nodes; i++) {
fprintf(f, "{ output = \"eq_band_%d:Out\" input = \"eq_band_%d:In\" }\n", i, i + 1);
}
fprintf(f, "]\n");
fprintf(f, "}\n");
fprintf(f, "audio.channels = %d\n", impl->channels);
fprintf(f, "audio.position = %s\n", impl->position);
capture_props = pw_properties_new("media.class", "Audio/Sink", NULL, NULL);
pw_properties_setf(capture_props, "node.name", "effect_input.eq%d", number_of_nodes);
if((str = pw_properties_get(impl->props, "capture.props")) != NULL)
pw_properties_update_string(capture_props, str, strlen(str));
fprintf(f, "capture.props = ");
pw_properties_serialize_dict(f, &capture_props->dict, serialize_flags);
fprintf(f, "\n");
playback_props = pw_properties_new("node.passive", "true", NULL, NULL);
pw_properties_setf(playback_props, "node.name", "effect_output.eq%d", number_of_nodes);
if((str = pw_properties_get(impl->props, "playback.props")) != NULL)
pw_properties_update_string(playback_props, str, strlen(str));
fprintf(f, "playback.props = ");
pw_properties_serialize_dict(f, &playback_props->dict, serialize_flags);
fprintf(f, "\n");
fprintf(f, "}\n");
pw_properties_free(capture_props);
pw_properties_free(playback_props);
}
int32_t parse_eq_filter_file(struct impl *impl, FILE *f)
{
struct eq_node_param eq_param;
FILE *memstream = NULL;
const char* str;
FILE *f;
spa_autoptr(pw_properties) p = NULL;
char *args = NULL;
char *line = NULL;
ssize_t nread;
size_t len, size;
uint32_t eq_band_idx = 1;
uint32_t eq_bands = 0;
int32_t res = 0;
const char *str;
size_t size;
va_list varargs;
int res;
if ((memstream = open_memstream(&args, &size)) == NULL) {
if ((str = pw_properties_get(props, key)) == NULL)
str = "{}";
if ((p = pw_properties_new_string(str)) == NULL)
return -errno;
va_start(varargs, key);
while (true) {
char *k, *v;
k = va_arg(varargs, char *);
if (k == NULL)
break;
v = va_arg(varargs, char *);
if (v == NULL || pw_properties_get(p, k) == NULL)
pw_properties_set(p, k, v);
}
va_end(varargs);
if ((f = open_memstream(&args, &size)) == NULL) {
res = -errno;
pw_log_error("Can't open memstream: %m");
return res;
}
pw_properties_serialize_dict(f, &p->dict, PW_PROPERTIES_FLAG_ENCLOSE);
fclose(f);
pw_properties_set(props, key, args);
free(args);
return 0;
}
static int create_eq_filter(struct impl *impl, const char *filename)
{
FILE *f = NULL;
const char* str;
char *args = NULL;
size_t size;
int32_t res = 0;
char path[PATH_MAX];
if ((str = pw_properties_get(impl->props, "equalizer.description")) != NULL) {
if (pw_properties_get(impl->props, PW_KEY_NODE_DESCRIPTION) == NULL)
pw_properties_set(impl->props, PW_KEY_NODE_DESCRIPTION, str);
if (pw_properties_get(impl->props, PW_KEY_MEDIA_NAME) == NULL)
pw_properties_set(impl->props, PW_KEY_MEDIA_NAME, str);
}
spa_json_encode_string(path, sizeof(path), filename);
pw_properties_setf(impl->props, "filter.graph",
"{"
" nodes = [ "
" { type = builtin name = eq label = param_eq "
" config = { filename = %s } "
" } "
" ] "
"}", path);
enhance_properties(impl->props, "capture.props", PW_KEY_MEDIA_CLASS, "Audio/Sink", NULL);
enhance_properties(impl->props, "playback.props", PW_KEY_NODE_PASSIVE, "true", NULL);
if ((f = open_memstream(&args, &size)) == NULL) {
res = -errno;
pw_log_error("Can't open memstream: %m");
goto done;
}
if ((str = pw_properties_get(impl->props, "equalizer.description")) == NULL)
str = DEFAULT_DESCRIPTION;
init_eq_node(memstream, str);
/*
* Read the Preamp gain line.
* Example: Preamp: -6.8 dB
*
* When a pre-amp gain is required, which is usually the case when
* applying EQ, we need to modify the first EQ band to apply a
* bq_highshelf filter at frequency 0 Hz with the provided negative
* gain.
*
* Pre-amp gain is always negative to offset the effect of possible
* clipping introduced by the amplification resulting from EQ.
*/
spa_zero(eq_param);
nread = getline(&line, &len, f);
if (nread != -1 && sscanf(line, "%*s %6f %*s", &eq_param.gain) == 1) {
memcpy(eq_param.filter, "ON", 2);
memcpy(eq_param.filter_type, "HSC", 3);
eq_param.freq = 0;
eq_param.q_fact = 1.0;
add_eq_node(memstream, &eq_param, eq_band_idx);
eq_band_idx++;
eq_bands++;
}
/* Read the filter bands */
while ((nread = getline(&line, &len, f)) != -1) {
spa_zero(eq_param);
/*
* On field widths:
* - filter can be ON or OFF
* - filter type can be PK, LSC, HSC
* - freq can be at most 5 decimal digits
* - gain can be -xy.z
* - Q can be x.y00
*
* Use a field width of 6 for gain and Q to account for any
* possible zeros.
*/
if (sscanf(line, "%*s %*d: %3s %3s %*s %5d %*s %*s %6f %*s %*c %6f",
eq_param.filter, eq_param.filter_type, &eq_param.freq,
&eq_param.gain, &eq_param.q_fact) == 5) {
if (strcmp(eq_param.filter, "ON") == 0) {
add_eq_node(memstream, &eq_param, eq_band_idx);
eq_band_idx++;
eq_bands++;
}
}
}
if (eq_bands > 0) {
end_eq_node(impl, memstream, eq_bands);
} else {
pw_log_error("failed to parse equalizer configuration");
res = -errno;
goto done;
}
fclose(memstream);
memstream = NULL;
pw_properties_serialize_dict(f, &impl->props->dict, PW_PROPERTIES_FLAG_ENCLOSE);
fclose(f);
pw_log_info("loading new module-filter-chain with args: %s", args);
impl->eq_module = pw_context_load_module(impl->context,
@ -341,10 +253,7 @@ int32_t parse_eq_filter_file(struct impl *impl, FILE *f)
res = 0;
done:
if (memstream != NULL)
fclose(memstream);
free(args);
return res;
}
@ -403,7 +312,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
struct pw_properties *props = NULL;
struct impl *impl;
const char *str;
FILE *f = NULL;
int res;
PW_LOG_TOPIC_INIT(mod_topic);
@ -452,39 +360,17 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
&impl->core_listener,
&core_events, impl);
impl->channels = pw_properties_get_uint32(impl->props, PW_KEY_AUDIO_CHANNELS, DEFAULT_CHANNELS);
if (impl->channels == 0) {
res = -EINVAL;
pw_log_error("invalid channels '%d'", impl->channels);
goto error;
}
if ((str = pw_properties_get(impl->props, SPA_KEY_AUDIO_POSITION)) == NULL)
str = DEFAULT_POSITION;
strncpy(impl->position, str, strlen(str));
if ((str = pw_properties_get(props, "equalizer.filepath")) == NULL) {
res = -errno;
pw_log_error( "missing property equalizer.filepath: %m");
res = -ENOENT;
pw_log_error( "missing property equalizer.filepath: %s", spa_strerror(res));
goto error;
}
pw_log_info("Loading equalizer file %s for parsing", str);
if ((f = fopen(str, "r")) == NULL) {
res = -errno;
pw_log_error("failed to open equalizer file: %m");
if ((res = create_eq_filter(impl, str)) < 0) {
pw_log_error("failed to parse equalizer file: %s", spa_strerror(res));
goto error;
}
if (parse_eq_filter_file(impl, f) == -1) {
res = -EINVAL;
pw_log_error("failed to parse equalizer file: %m");
goto error;
}
fclose(f);
pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
@ -492,10 +378,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
return 0;
error:
if (f != NULL)
fclose(f);
impl_destroy(impl);
return res;
}