From 9dc20765e115a942ecad4dce900aec77b91ec8c1 Mon Sep 17 00:00:00 2001 From: Frank Krick Date: Sun, 10 Aug 2025 19:31:22 -0400 Subject: [PATCH 01/26] Add stream for control - 1 --- src/modules/module-filter-chain.c | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 6c2f16200..8381e18a1 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1176,6 +1176,10 @@ struct impl { struct spa_hook playback_listener; struct spa_audio_info_raw playback_info; + struct pw_properties *control_props; + struct pw_stream *control; + struct spa_hook control_listener; + struct spa_audio_info_raw info; struct spa_io_position *position; @@ -1219,6 +1223,23 @@ static void capture_process(void *d) } } +static void control_process(void *d) +{ + struct impl *impl = d; + int res; + if ((res = pw_stream_trigger_process(impl->playback)) < 0) { + pw_log_debug("playback trigger error: %s", spa_strerror(res)); + while (true) { + struct pw_buffer *t; + if ((t = pw_stream_dequeue_buffer(impl->control)) == NULL) + break; + /* playback part is not ready, consume, discard and recycle + * the capture buffers */ + pw_stream_queue_buffer(impl->control, t); + } + } +} + static void playback_process(void *d) { struct impl *impl = d; @@ -1578,6 +1599,15 @@ static const struct pw_stream_events out_stream_events = { .param_changed = playback_param_changed, }; +static const struct pw_stream_events control_stream_events = { + PW_VERSION_STREAM_EVENTS, + .destroy = NULL, + .process = control_process, + .io_changed = NULL, + .state_changed = NULL, + .param_changed = NULL, +}; + static int setup_streams(struct impl *impl) { int res; @@ -1587,6 +1617,16 @@ static int setup_streams(struct impl *impl) struct spa_pod_dynamic_builder b; struct spa_filter_graph *graph = impl->graph; + impl->control = pw_stream_new(impl->core, + "filter control", impl->capture_props); + impl->control_props = NULL; + if (impl->control == NULL) + return -errno; + + pw_stream_add_listener(impl->control, + &impl->control_listener, + &control_stream_events, impl); + impl->capture = pw_stream_new(impl->core, "filter capture", impl->capture_props); impl->capture_props = NULL; From 61a570149322baef6d3e9de1e7d3fa5eaaa0493a Mon Sep 17 00:00:00 2001 From: Frank Krick Date: Mon, 11 Aug 2025 08:05:44 -0400 Subject: [PATCH 02/26] Add stream for control - 2 --- src/modules/module-filter-chain.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 8381e18a1..70224caec 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1243,7 +1243,7 @@ static void control_process(void *d) static void playback_process(void *d) { struct impl *impl = d; - struct pw_buffer *in, *out; + struct pw_buffer *in, *out, *control; uint32_t i, data_size = 0; int32_t stride = 0; struct spa_data *bd; @@ -1262,6 +1262,18 @@ static void playback_process(void *d) if (in == NULL) pw_log_debug("%p: out of capture buffers: %m", impl); + control = NULL; + while (true) { + struct pw_buffer *t; + if ((t = pw_stream_dequeue_buffer(impl->control)) == NULL) + break; + if (control) + pw_stream_queue_buffer(impl->control, control); + control = t; + } + if (control == NULL) + pw_log_debug("%p: out of control buffers: %m", impl); + if ((out = pw_stream_dequeue_buffer(impl->playback)) == NULL) pw_log_debug("%p: out of playback buffers: %m", impl); @@ -1309,6 +1321,8 @@ done: pw_stream_queue_buffer(impl->capture, in); if (out != NULL) pw_stream_queue_buffer(impl->playback, out); + if (control != NULL) + pw_stream_queue_buffer(impl->control, control); } static int activate_graph(struct impl *impl) From 1c53fdbfc0b5723510c2301d4b236acbd4087060 Mon Sep 17 00:00:00 2001 From: Frank Krick Date: Fri, 15 Aug 2025 19:28:16 -0400 Subject: [PATCH 03/26] first working version --- src/modules/module-filter-chain.c | 118 ++++++++++++++++++++++-------- 1 file changed, 89 insertions(+), 29 deletions(-) diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 70224caec..9491d628f 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -21,6 +21,9 @@ #include +#include "spa/control/control.h" +#include "spa/param/props.h" + #define NAME "filter-chain" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -1223,27 +1226,78 @@ static void capture_process(void *d) } } +static int apply_props(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct impl *impl = user_data; + const struct spa_pod *props = data; + if (props == NULL) { + fprintf(stdout, "no props\n"); + } + + //spa_filter_graph_set_props(impl->graph, SPA_DIRECTION_INPUT, props); + spa_filter_graph_set_props(impl->graph, SPA_DIRECTION_OUTPUT, props); + + /* props was allocated with spa_pod_copy before invoking */ + return 0; +} + static void control_process(void *d) { + fprintf(stdout, "control process\n"); + struct impl *impl = d; - int res; - if ((res = pw_stream_trigger_process(impl->playback)) < 0) { - pw_log_debug("playback trigger error: %s", spa_strerror(res)); - while (true) { - struct pw_buffer *t; - if ((t = pw_stream_dequeue_buffer(impl->control)) == NULL) - break; - /* playback part is not ready, consume, discard and recycle - * the capture buffers */ - pw_stream_queue_buffer(impl->control, t); - } - } + + struct pw_buffer *control = pw_stream_dequeue_buffer(impl->control); + /* + while (true) { + struct pw_buffer *t; + if ((t = pw_stream_dequeue_buffer(impl->control)) == NULL) + break; + if (control) + pw_stream_queue_buffer(impl->control, control); + control = t; + } + */ + if (control == NULL) { + fprintf(stdout, "out of control buffers\n"); + goto done; + } + fprintf(stdout, "\n\nprocessing buffer\n"); + if (control->buffer->n_datas > 0) { + fprintf(stdout, "creating pod\n"); + struct spa_pod *pod = spa_pod_from_data( + control->buffer->datas[0].data, + control->buffer->datas[0].maxsize, + control->buffer->datas[0].chunk->offset, + control->buffer->datas[0].chunk->size); + + if (spa_pod_is_sequence(pod)) { + fprintf(stdout, "processing sequence\n"); + struct spa_pod_sequence *sequence = (struct spa_pod_sequence *)pod; + + struct spa_pod_control *pod_control; + SPA_POD_SEQUENCE_FOREACH(sequence, pod_control) { + fprintf(stdout, "processing sequence item\n"); + if (pod_control->type == SPA_CONTROL_Properties) { + fprintf(stdout, "processing properties\n"); + struct pw_loop *loop = pw_context_get_main_loop(impl->context); + pw_loop_invoke(loop, apply_props, 0, &pod_control->value, SPA_POD_SIZE(&pod_control->value), false, impl); + } else if (pod_control->type != SPA_CONTROL_OSC) {} + } + } else { + fprintf(stdout, "not a sequence\n"); + } + } + + done: + pw_stream_queue_buffer(impl->control, control); } static void playback_process(void *d) { + pw_log_error("playback process"); struct impl *impl = d; - struct pw_buffer *in, *out, *control; + struct pw_buffer *in, *out; uint32_t i, data_size = 0; int32_t stride = 0; struct spa_data *bd; @@ -1262,18 +1316,6 @@ static void playback_process(void *d) if (in == NULL) pw_log_debug("%p: out of capture buffers: %m", impl); - control = NULL; - while (true) { - struct pw_buffer *t; - if ((t = pw_stream_dequeue_buffer(impl->control)) == NULL) - break; - if (control) - pw_stream_queue_buffer(impl->control, control); - control = t; - } - if (control == NULL) - pw_log_debug("%p: out of control buffers: %m", impl); - if ((out = pw_stream_dequeue_buffer(impl->playback)) == NULL) pw_log_debug("%p: out of playback buffers: %m", impl); @@ -1321,8 +1363,6 @@ done: pw_stream_queue_buffer(impl->capture, in); if (out != NULL) pw_stream_queue_buffer(impl->playback, out); - if (control != NULL) - pw_stream_queue_buffer(impl->control, control); } static int activate_graph(struct impl *impl) @@ -1632,7 +1672,7 @@ static int setup_streams(struct impl *impl) struct spa_filter_graph *graph = impl->graph; impl->control = pw_stream_new(impl->core, - "filter control", impl->capture_props); + "filter control", impl->control_props); impl->control_props = NULL; if (impl->control == NULL) return -errno; @@ -1641,6 +1681,24 @@ static int setup_streams(struct impl *impl) &impl->control_listener, &control_stream_events, impl); + uint8_t buffer[256]; + struct spa_pod_builder bt = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + const struct spa_pod *param[1]; + + param[0] = spa_pod_builder_add_object(&bt, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control) + ); + + pw_stream_connect(impl->control, + PW_DIRECTION_INPUT, + PW_ID_ANY, + PW_STREAM_FLAG_AUTOCONNECT | + PW_STREAM_FLAG_MAP_BUFFERS | + PW_STREAM_FLAG_RT_PROCESS, + param, 1); + impl->capture = pw_stream_new(impl->core, "filter capture", impl->capture_props); impl->capture_props = NULL; @@ -1908,6 +1966,8 @@ static void copy_props(struct impl *impl, struct pw_properties *props, const cha SPA_EXPORT int pipewire__module_init(struct pw_impl_module *module, const char *args) { + fprintf(stdout, "module init reached\n"); + struct pw_context *context = pw_impl_module_get_context(module); const struct pw_properties *p; struct pw_properties *props; @@ -1924,7 +1984,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (impl == NULL) return -errno; - pw_log_debug("module %p: new %s", impl, args); + pw_log_debug("module fk1 %p: new %s", impl, args); if (args) props = pw_properties_new_string(args); From 0a6d1ce7ef0902b4b27d9997c86ab4077f2ad83b Mon Sep 17 00:00:00 2001 From: Frank Krick Date: Fri, 15 Aug 2025 19:38:17 -0400 Subject: [PATCH 04/26] cleanup --- src/modules/module-filter-chain.c | 34 ++++--------------------------- 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 9491d628f..e68a3fec9 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1228,16 +1228,8 @@ static void capture_process(void *d) static int apply_props(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { - struct impl *impl = user_data; const struct spa_pod *props = data; - if (props == NULL) { - fprintf(stdout, "no props\n"); - } - - //spa_filter_graph_set_props(impl->graph, SPA_DIRECTION_INPUT, props); spa_filter_graph_set_props(impl->graph, SPA_DIRECTION_OUTPUT, props); - - /* props was allocated with spa_pod_copy before invoking */ return 0; } @@ -1248,23 +1240,9 @@ static void control_process(void *d) struct impl *impl = d; struct pw_buffer *control = pw_stream_dequeue_buffer(impl->control); - /* - while (true) { - struct pw_buffer *t; - if ((t = pw_stream_dequeue_buffer(impl->control)) == NULL) - break; - if (control) - pw_stream_queue_buffer(impl->control, control); - control = t; - } - */ - if (control == NULL) { - fprintf(stdout, "out of control buffers\n"); + if (control == NULL) goto done; - } - fprintf(stdout, "\n\nprocessing buffer\n"); if (control->buffer->n_datas > 0) { - fprintf(stdout, "creating pod\n"); struct spa_pod *pod = spa_pod_from_data( control->buffer->datas[0].data, control->buffer->datas[0].maxsize, @@ -1272,20 +1250,16 @@ static void control_process(void *d) control->buffer->datas[0].chunk->size); if (spa_pod_is_sequence(pod)) { - fprintf(stdout, "processing sequence\n"); struct spa_pod_sequence *sequence = (struct spa_pod_sequence *)pod; struct spa_pod_control *pod_control; SPA_POD_SEQUENCE_FOREACH(sequence, pod_control) { - fprintf(stdout, "processing sequence item\n"); if (pod_control->type == SPA_CONTROL_Properties) { - fprintf(stdout, "processing properties\n"); struct pw_loop *loop = pw_context_get_main_loop(impl->context); - pw_loop_invoke(loop, apply_props, 0, &pod_control->value, SPA_POD_SIZE(&pod_control->value), false, impl); - } else if (pod_control->type != SPA_CONTROL_OSC) {} + pw_loop_invoke(loop, apply_props, 0, &pod_control->value, + SPA_POD_SIZE(&pod_control->value), false, impl); + } } - } else { - fprintf(stdout, "not a sequence\n"); } } From 8e2d02c08c77c82db28a26a03ab278f90eccd687 Mon Sep 17 00:00:00 2001 From: Frank Krick Date: Fri, 15 Aug 2025 19:47:53 -0400 Subject: [PATCH 05/26] cleanup --- src/modules/module-filter-chain.c | 36 +++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index e68a3fec9..ed564583a 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1228,6 +1228,7 @@ static void capture_process(void *d) static int apply_props(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { + const struct impl *impl = user_data; const struct spa_pod *props = data; spa_filter_graph_set_props(impl->graph, SPA_DIRECTION_OUTPUT, props); return 0; @@ -1235,8 +1236,6 @@ static int apply_props(struct spa_loop *loop, bool async, uint32_t seq, const vo static void control_process(void *d) { - fprintf(stdout, "control process\n"); - struct impl *impl = d; struct pw_buffer *control = pw_stream_dequeue_buffer(impl->control); @@ -1269,7 +1268,6 @@ static void control_process(void *d) static void playback_process(void *d) { - pw_log_error("playback process"); struct impl *impl = d; struct pw_buffer *in, *out; uint32_t i, data_size = 0; @@ -1627,12 +1625,38 @@ static const struct pw_stream_events out_stream_events = { .param_changed = playback_param_changed, }; +static void control_destroy(void *d) +{ + struct impl *impl = d; + spa_hook_remove(&impl->control_listener); + impl->control = NULL; +} + +static void control_state_changed(void *data, enum pw_stream_state old, + enum pw_stream_state state, const char *error) +{ + struct impl *impl = data; + + switch (state) { + case PW_STREAM_STATE_PAUSED: + pw_stream_flush(impl->control, false); + break; + case PW_STREAM_STATE_ERROR: + pw_log_info("module %p: error: %s", impl, error); + break; + case PW_STREAM_STATE_STREAMING: + default: + break; + } + return; +} + static const struct pw_stream_events control_stream_events = { PW_VERSION_STREAM_EVENTS, - .destroy = NULL, + .destroy = control_destroy, .process = control_process, .io_changed = NULL, - .state_changed = NULL, + .state_changed = control_state_changed, .param_changed = NULL, }; @@ -1940,8 +1964,6 @@ static void copy_props(struct impl *impl, struct pw_properties *props, const cha SPA_EXPORT int pipewire__module_init(struct pw_impl_module *module, const char *args) { - fprintf(stdout, "module init reached\n"); - struct pw_context *context = pw_impl_module_get_context(module); const struct pw_properties *p; struct pw_properties *props; From 6a154a0be0a6155990293973f0dfb9f577021b4a Mon Sep 17 00:00:00 2001 From: Frank Krick Date: Fri, 15 Aug 2025 19:52:25 -0400 Subject: [PATCH 06/26] cleanup --- src/modules/module-filter-chain.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index ed564583a..70d81d1be 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1980,7 +1980,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (impl == NULL) return -errno; - pw_log_debug("module fk1 %p: new %s", impl, args); + pw_log_debug("module %p: new %s", impl, args); if (args) props = pw_properties_new_string(args); From 4c65f04337625708879b4766122cee481c2d0335 Mon Sep 17 00:00:00 2001 From: Frank Krick Date: Fri, 15 Aug 2025 20:36:26 -0400 Subject: [PATCH 07/26] Attribute in config to enable control port. Disable by default --- src/modules/module-filter-chain.c | 61 +++++++++++++++++++------------ 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 70d81d1be..85bcccbde 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1182,6 +1182,7 @@ struct impl { struct pw_properties *control_props; struct pw_stream *control; struct spa_hook control_listener; + bool control_stream_active; struct spa_audio_info_raw info; @@ -1669,33 +1670,35 @@ static int setup_streams(struct impl *impl) struct spa_pod_dynamic_builder b; struct spa_filter_graph *graph = impl->graph; - impl->control = pw_stream_new(impl->core, - "filter control", impl->control_props); - impl->control_props = NULL; - if (impl->control == NULL) - return -errno; + if (impl->control_stream_active) { + impl->control = pw_stream_new(impl->core, + "filter control", impl->control_props); + impl->control_props = NULL; + if (impl->control == NULL) + return -errno; - pw_stream_add_listener(impl->control, - &impl->control_listener, - &control_stream_events, impl); + pw_stream_add_listener(impl->control, + &impl->control_listener, + &control_stream_events, impl); - uint8_t buffer[256]; - struct spa_pod_builder bt = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - const struct spa_pod *param[1]; + uint8_t buffer[256]; + struct spa_pod_builder bt = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + const struct spa_pod *param[1]; - param[0] = spa_pod_builder_add_object(&bt, - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control) - ); + param[0] = spa_pod_builder_add_object(&bt, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control) + ); - pw_stream_connect(impl->control, - PW_DIRECTION_INPUT, - PW_ID_ANY, - PW_STREAM_FLAG_AUTOCONNECT | - PW_STREAM_FLAG_MAP_BUFFERS | - PW_STREAM_FLAG_RT_PROCESS, - param, 1); + pw_stream_connect(impl->control, + PW_DIRECTION_INPUT, + PW_ID_ANY, + PW_STREAM_FLAG_AUTOCONNECT | + PW_STREAM_FLAG_MAP_BUFFERS | + PW_STREAM_FLAG_RT_PROCESS, + param, 1); + } impl->capture = pw_stream_new(impl->core, "filter capture", impl->capture_props); @@ -2119,7 +2122,17 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) &impl->core_listener, &core_events, impl); - setup_streams(impl); + if (pw_properties_get(props, "controlStream.enabled") == NULL) { + impl->control_stream_active = false; + } else if (strcmp(pw_properties_get(props, "controlStream.enabled"), "true") + == 0) { + impl->control_stream_active = true; + } else if (strcmp(pw_properties_get(props, "controlStream.enabled"), "false") + == 0) { + impl->control_stream_active = false; + } + + setup_streams(impl); pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); From 255bf4cdb4fc8953d50adb80e78950ca96a078d0 Mon Sep 17 00:00:00 2001 From: Frank Krick Date: Sat, 16 Aug 2025 21:10:06 -0400 Subject: [PATCH 08/26] Configure the control stream like the other streams using control.props --- src/modules/module-filter-chain.c | 1445 ++++++++++++++--------------- 1 file changed, 680 insertions(+), 765 deletions(-) diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 85bcccbde..108a6f35b 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1106,38 +1106,26 @@ extern struct spa_handle_factory spa_filter_graph_factory; *\endcode */ static const struct spa_dict_item module_props[] = { - { PW_KEY_MODULE_AUTHOR, "Wim Taymans " }, - { PW_KEY_MODULE_DESCRIPTION, "Create filter chain streams" }, - { PW_KEY_MODULE_USAGE, " ( remote.name= ) " - "( node.latency= ) " - "( node.description= ) " - "( audio.rate= ) " - "( audio.channels= ) " - "( audio.position= ) " - "filter.graph = [ " - " nodes = [ " - " { " - " type = " - " name = " - " plugin = " - " label =