mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-11-02 09:01:50 -05:00
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:
parent
ab20cc5f28
commit
673352893a
1 changed files with 73 additions and 191 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue