mirror of
				https://gitlab.freedesktop.org/pipewire/pipewire.git
				synced 2025-11-03 09:01:54 -05:00 
			
		
		
		
	audioconvert: add support for filter-graphs
Load multiple graphs with audioconvert.filter-graph.N where N is the order where the graph is inserted/replaced. Run the graphs before the channelmixer. Graphs can be added and removed at runtime.
This commit is contained in:
		
							parent
							
								
									1f4e8b96c2
								
							
						
					
					
						commit
						3a65472e9e
					
				
					 1 changed files with 352 additions and 12 deletions
				
			
		| 
						 | 
					@ -11,6 +11,7 @@
 | 
				
			||||||
#include <spa/support/cpu.h>
 | 
					#include <spa/support/cpu.h>
 | 
				
			||||||
#include <spa/support/loop.h>
 | 
					#include <spa/support/loop.h>
 | 
				
			||||||
#include <spa/support/log.h>
 | 
					#include <spa/support/log.h>
 | 
				
			||||||
 | 
					#include <spa/support/plugin-loader.h>
 | 
				
			||||||
#include <spa/utils/result.h>
 | 
					#include <spa/utils/result.h>
 | 
				
			||||||
#include <spa/utils/list.h>
 | 
					#include <spa/utils/list.h>
 | 
				
			||||||
#include <spa/utils/json.h>
 | 
					#include <spa/utils/json.h>
 | 
				
			||||||
| 
						 | 
					@ -30,6 +31,7 @@
 | 
				
			||||||
#include <spa/pod/dynamic.h>
 | 
					#include <spa/pod/dynamic.h>
 | 
				
			||||||
#include <spa/debug/types.h>
 | 
					#include <spa/debug/types.h>
 | 
				
			||||||
#include <spa/control/ump-utils.h>
 | 
					#include <spa/control/ump-utils.h>
 | 
				
			||||||
 | 
					#include <spa/filter-graph/filter-graph.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "volume-ops.h"
 | 
					#include "volume-ops.h"
 | 
				
			||||||
#include "fmt-ops.h"
 | 
					#include "fmt-ops.h"
 | 
				
			||||||
| 
						 | 
					@ -49,6 +51,7 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.audioconvert");
 | 
				
			||||||
#define MAX_DATAS	SPA_AUDIO_MAX_CHANNELS
 | 
					#define MAX_DATAS	SPA_AUDIO_MAX_CHANNELS
 | 
				
			||||||
#define MAX_PORTS	(SPA_AUDIO_MAX_CHANNELS+1)
 | 
					#define MAX_PORTS	(SPA_AUDIO_MAX_CHANNELS+1)
 | 
				
			||||||
#define MAX_STAGES	64
 | 
					#define MAX_STAGES	64
 | 
				
			||||||
 | 
					#define MAX_GRAPH	9	/* 8 active + 1 replacement slot */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define DEFAULT_MUTE		false
 | 
					#define DEFAULT_MUTE		false
 | 
				
			||||||
#define DEFAULT_VOLUME		VOLUME_NORM
 | 
					#define DEFAULT_VOLUME		VOLUME_NORM
 | 
				
			||||||
| 
						 | 
					@ -96,6 +99,7 @@ struct props {
 | 
				
			||||||
	double rate;
 | 
						double rate;
 | 
				
			||||||
	char wav_path[512];
 | 
						char wav_path[512];
 | 
				
			||||||
	unsigned int lock_volumes:1;
 | 
						unsigned int lock_volumes:1;
 | 
				
			||||||
 | 
						unsigned int filter_graph_disabled:1;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void props_reset(struct props *props)
 | 
					static void props_reset(struct props *props)
 | 
				
			||||||
| 
						 | 
					@ -117,6 +121,7 @@ static void props_reset(struct props *props)
 | 
				
			||||||
	props->rate = 1.0;
 | 
						props->rate = 1.0;
 | 
				
			||||||
	spa_zero(props->wav_path);
 | 
						spa_zero(props->wav_path);
 | 
				
			||||||
	props->lock_volumes = false;
 | 
						props->lock_volumes = false;
 | 
				
			||||||
 | 
						props->filter_graph_disabled = false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct buffer {
 | 
					struct buffer {
 | 
				
			||||||
| 
						 | 
					@ -219,6 +224,17 @@ struct stage {
 | 
				
			||||||
	void (*run) (struct stage *stage, struct stage_context *c);
 | 
						void (*run) (struct stage *stage, struct stage_context *c);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct filter_graph {
 | 
				
			||||||
 | 
						struct impl *impl;
 | 
				
			||||||
 | 
						int order;
 | 
				
			||||||
 | 
						struct spa_handle *handle;
 | 
				
			||||||
 | 
						struct spa_filter_graph *graph;
 | 
				
			||||||
 | 
						struct spa_hook listener;
 | 
				
			||||||
 | 
						uint32_t n_inputs;
 | 
				
			||||||
 | 
						uint32_t n_outputs;
 | 
				
			||||||
 | 
						bool active;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct impl {
 | 
					struct impl {
 | 
				
			||||||
	struct spa_handle handle;
 | 
						struct spa_handle handle;
 | 
				
			||||||
	struct spa_node node;
 | 
						struct spa_node node;
 | 
				
			||||||
| 
						 | 
					@ -226,6 +242,14 @@ struct impl {
 | 
				
			||||||
	struct spa_log *log;
 | 
						struct spa_log *log;
 | 
				
			||||||
	struct spa_cpu *cpu;
 | 
						struct spa_cpu *cpu;
 | 
				
			||||||
	struct spa_loop *data_loop;
 | 
						struct spa_loop *data_loop;
 | 
				
			||||||
 | 
						struct spa_plugin_loader *loader;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						uint32_t n_graph;
 | 
				
			||||||
 | 
						uint32_t graph_index[MAX_GRAPH];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct filter_graph filter_graph[MAX_GRAPH];
 | 
				
			||||||
 | 
						int in_filter_props;
 | 
				
			||||||
 | 
						int filter_props_count;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	struct stage stages[MAX_STAGES];
 | 
						struct stage stages[MAX_STAGES];
 | 
				
			||||||
	uint32_t n_stages;
 | 
						uint32_t n_stages;
 | 
				
			||||||
| 
						 | 
					@ -446,6 +470,7 @@ static int impl_node_enum_params(void *object, int seq,
 | 
				
			||||||
	uint8_t buffer[4096];
 | 
						uint8_t buffer[4096];
 | 
				
			||||||
	struct spa_result_node_params result;
 | 
						struct spa_result_node_params result;
 | 
				
			||||||
	uint32_t count = 0;
 | 
						uint32_t count = 0;
 | 
				
			||||||
 | 
						int res;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	spa_return_val_if_fail(this != NULL, -EINVAL);
 | 
						spa_return_val_if_fail(this != NULL, -EINVAL);
 | 
				
			||||||
	spa_return_val_if_fail(num != 0, -EINVAL);
 | 
						spa_return_val_if_fail(num != 0, -EINVAL);
 | 
				
			||||||
| 
						 | 
					@ -776,8 +801,30 @@ static int impl_node_enum_params(void *object, int seq,
 | 
				
			||||||
				SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->lock_volumes),
 | 
									SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->lock_volumes),
 | 
				
			||||||
				SPA_PROP_INFO_params, SPA_POD_Bool(true));
 | 
									SPA_PROP_INFO_params, SPA_POD_Bool(true));
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
 | 
							case 28:
 | 
				
			||||||
 | 
								param = spa_pod_builder_add_object(&b,
 | 
				
			||||||
 | 
									SPA_TYPE_OBJECT_PropInfo, id,
 | 
				
			||||||
 | 
									SPA_PROP_INFO_name, SPA_POD_String("audioconvert.filter-graph.disable"),
 | 
				
			||||||
 | 
									SPA_PROP_INFO_description, SPA_POD_String("Disable Filter graph updates"),
 | 
				
			||||||
 | 
									SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->filter_graph_disabled),
 | 
				
			||||||
 | 
									SPA_PROP_INFO_params, SPA_POD_Bool(true));
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case 29:
 | 
				
			||||||
 | 
								param = spa_pod_builder_add_object(&b,
 | 
				
			||||||
 | 
									SPA_TYPE_OBJECT_PropInfo, id,
 | 
				
			||||||
 | 
									SPA_PROP_INFO_name, SPA_POD_String("audioconvert.filter-graph"),
 | 
				
			||||||
 | 
									SPA_PROP_INFO_description, SPA_POD_String("A filter graph to load"),
 | 
				
			||||||
 | 
									SPA_PROP_INFO_type, SPA_POD_String(""),
 | 
				
			||||||
 | 
									SPA_PROP_INFO_params, SPA_POD_Bool(true));
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
			return 0;
 | 
								if (this->filter_graph[0].graph) {
 | 
				
			||||||
 | 
									res = spa_filter_graph_enum_prop_info(this->filter_graph[0].graph,
 | 
				
			||||||
 | 
											result.index - 30, &b, ¶m);
 | 
				
			||||||
 | 
									if (res <= 0)
 | 
				
			||||||
 | 
										return res;
 | 
				
			||||||
 | 
								} else
 | 
				
			||||||
 | 
									return 0;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -856,11 +903,27 @@ static int impl_node_enum_params(void *object, int seq,
 | 
				
			||||||
			spa_pod_builder_string(&b, p->wav_path);
 | 
								spa_pod_builder_string(&b, p->wav_path);
 | 
				
			||||||
			spa_pod_builder_string(&b, "channelmix.lock-volumes");
 | 
								spa_pod_builder_string(&b, "channelmix.lock-volumes");
 | 
				
			||||||
			spa_pod_builder_bool(&b, p->lock_volumes);
 | 
								spa_pod_builder_bool(&b, p->lock_volumes);
 | 
				
			||||||
 | 
								spa_pod_builder_string(&b, "audioconvert.filter-graph.disable");
 | 
				
			||||||
 | 
								spa_pod_builder_bool(&b, p->filter_graph_disabled);
 | 
				
			||||||
 | 
								spa_pod_builder_string(&b, "audioconvert.filter-graph");
 | 
				
			||||||
 | 
								spa_pod_builder_string(&b, "");
 | 
				
			||||||
			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;
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
			return 0;
 | 
								if (result.index > MAX_GRAPH)
 | 
				
			||||||
 | 
									return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (this->filter_graph[result.index-1].graph == NULL)
 | 
				
			||||||
 | 
									goto next;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								res = spa_filter_graph_get_props(this->filter_graph[result.index-1].graph,
 | 
				
			||||||
 | 
											&b, ¶m);
 | 
				
			||||||
 | 
								if (res < 0)
 | 
				
			||||||
 | 
									return res;
 | 
				
			||||||
 | 
								if (res == 0)
 | 
				
			||||||
 | 
									goto next;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -897,8 +960,195 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size)
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void graph_info(void *object, const struct spa_filter_graph_info *info)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct filter_graph *g = object;
 | 
				
			||||||
 | 
						if (!g->active)
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						g->n_inputs = info->n_inputs;
 | 
				
			||||||
 | 
						g->n_outputs = info->n_outputs;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int apply_props(struct impl *impl, const struct spa_pod *props);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void graph_apply_props(void *object, enum spa_direction direction, const struct spa_pod *props)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct filter_graph *g = object;
 | 
				
			||||||
 | 
						struct impl *impl = g->impl;
 | 
				
			||||||
 | 
						if (!g->active)
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						if (apply_props(impl, props) > 0)
 | 
				
			||||||
 | 
							emit_node_info(impl, false);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void graph_props_changed(void *object, enum spa_direction direction)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct filter_graph *g = object;
 | 
				
			||||||
 | 
						struct impl *impl = g->impl;
 | 
				
			||||||
 | 
						if (!g->active)
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS;
 | 
				
			||||||
 | 
						impl->params[IDX_Props].user++;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct spa_filter_graph_events graph_events = {
 | 
				
			||||||
 | 
						SPA_VERSION_FILTER_GRAPH_EVENTS,
 | 
				
			||||||
 | 
						.info = graph_info,
 | 
				
			||||||
 | 
						.apply_props = graph_apply_props,
 | 
				
			||||||
 | 
						.props_changed = graph_props_changed,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int setup_filter_graph(struct impl *this, struct spa_filter_graph *graph)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						int res;
 | 
				
			||||||
 | 
						char rate_str[64];
 | 
				
			||||||
 | 
						struct dir *in;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (graph == NULL)
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						in = &this->dir[SPA_DIRECTION_INPUT];
 | 
				
			||||||
 | 
						snprintf(rate_str, sizeof(rate_str), "%d", in->format.info.raw.rate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						spa_filter_graph_deactivate(graph);
 | 
				
			||||||
 | 
						res = spa_filter_graph_activate(graph,
 | 
				
			||||||
 | 
									     &SPA_DICT_ITEMS(
 | 
				
			||||||
 | 
										     SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, rate_str)));
 | 
				
			||||||
 | 
						return res;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int do_sync_filter_graph(struct spa_loop *loop, bool async, uint32_t seq,
 | 
				
			||||||
 | 
							const void *data, size_t size, void *user_data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct impl *impl = user_data;
 | 
				
			||||||
 | 
						uint32_t i, j;
 | 
				
			||||||
 | 
						impl->n_graph = 0;
 | 
				
			||||||
 | 
						for (i = 0; i < MAX_GRAPH; i++) {
 | 
				
			||||||
 | 
							struct filter_graph *g = &impl->filter_graph[i];
 | 
				
			||||||
 | 
							if (g->graph == NULL || !g->active)
 | 
				
			||||||
 | 
								continue;
 | 
				
			||||||
 | 
							impl->graph_index[impl->n_graph++] = i;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (j = impl->n_graph-1; j > 0; j--) {
 | 
				
			||||||
 | 
								if (impl->filter_graph[impl->graph_index[j]].order >=
 | 
				
			||||||
 | 
								    impl->filter_graph[impl->graph_index[j-1]].order)
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								SPA_SWAP(impl->graph_index[j], impl->graph_index[j-1]);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						impl->recalc = true;
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void clean_filter_handles(struct impl *impl, bool force)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						uint32_t i;
 | 
				
			||||||
 | 
						for (i = 0; i < MAX_GRAPH; i++) {
 | 
				
			||||||
 | 
							struct filter_graph *g = &impl->filter_graph[i];
 | 
				
			||||||
 | 
							if (!g->active || force) {
 | 
				
			||||||
 | 
								if (g->graph)
 | 
				
			||||||
 | 
									spa_hook_remove(&g->listener);
 | 
				
			||||||
 | 
								if (g->handle)
 | 
				
			||||||
 | 
									spa_plugin_loader_unload(impl->loader, g->handle);
 | 
				
			||||||
 | 
								spa_zero(*g);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int load_filter_graph(struct impl *impl, const char *graph, int order)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						char qlimit[64];
 | 
				
			||||||
 | 
						int res;
 | 
				
			||||||
 | 
						void *iface;
 | 
				
			||||||
 | 
						struct spa_handle *new_handle = NULL;
 | 
				
			||||||
 | 
						uint32_t i, idx, n_graph;
 | 
				
			||||||
 | 
						struct filter_graph *pending;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (impl->props.filter_graph_disabled)
 | 
				
			||||||
 | 
							return -EPERM;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* find graph spot */
 | 
				
			||||||
 | 
						idx = SPA_ID_INVALID;
 | 
				
			||||||
 | 
						n_graph = 0;
 | 
				
			||||||
 | 
						for (i = 0; i < MAX_GRAPH; i++) {
 | 
				
			||||||
 | 
							pending = &impl->filter_graph[i];
 | 
				
			||||||
 | 
							/* find the first free spot for our new filter */
 | 
				
			||||||
 | 
							if (!pending->active && idx == SPA_ID_INVALID)
 | 
				
			||||||
 | 
								idx = i;
 | 
				
			||||||
 | 
							/* deactivate an existing filter of the same order */
 | 
				
			||||||
 | 
							if (pending->active) {
 | 
				
			||||||
 | 
								if (pending->order == order)
 | 
				
			||||||
 | 
									pending->active = false;
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
									n_graph++;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						/* we can at most have MAX_GRAPH-1 active filters */
 | 
				
			||||||
 | 
						if (n_graph >= MAX_GRAPH-1)
 | 
				
			||||||
 | 
							return -ENOSPC;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pending = &impl->filter_graph[idx];
 | 
				
			||||||
 | 
						pending->impl = impl;
 | 
				
			||||||
 | 
						pending->order = order;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (graph != NULL && graph[0] != '\0') {
 | 
				
			||||||
 | 
							snprintf(qlimit, sizeof(qlimit), "%u", impl->quantum_limit);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							new_handle = spa_plugin_loader_load(impl->loader, "filter.graph",
 | 
				
			||||||
 | 
									&SPA_DICT_ITEMS(
 | 
				
			||||||
 | 
										SPA_DICT_ITEM(SPA_KEY_LIBRARY_NAME, "filter-graph/libspa-filter-graph"),
 | 
				
			||||||
 | 
										SPA_DICT_ITEM("clock.quantum-limit", qlimit),
 | 
				
			||||||
 | 
										SPA_DICT_ITEM("filter.graph", graph)));
 | 
				
			||||||
 | 
							if (new_handle == NULL)
 | 
				
			||||||
 | 
								goto error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							res = spa_handle_get_interface(new_handle, SPA_TYPE_INTERFACE_FilterGraph, &iface);
 | 
				
			||||||
 | 
							if (res < 0 || iface == NULL)
 | 
				
			||||||
 | 
								goto error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/* prepare new filter and swap it */
 | 
				
			||||||
 | 
							res = setup_filter_graph(impl, iface);
 | 
				
			||||||
 | 
							if (res < 0)
 | 
				
			||||||
 | 
								goto error;
 | 
				
			||||||
 | 
							pending->graph = iface;
 | 
				
			||||||
 | 
							pending->active = true;
 | 
				
			||||||
 | 
							spa_log_info(impl->log, "loading filter-graph order:%d in %d active:%d",
 | 
				
			||||||
 | 
									order, idx, n_graph + 1);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							pending->active = false;
 | 
				
			||||||
 | 
							spa_log_info(impl->log, "removing filter-graph order:%d active:%d",
 | 
				
			||||||
 | 
									order, n_graph);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* we call this here on the pending_graph so that the n_input/n_output is updated
 | 
				
			||||||
 | 
						 * before we switch */
 | 
				
			||||||
 | 
						if (pending->active)
 | 
				
			||||||
 | 
							spa_filter_graph_add_listener(pending->graph,
 | 
				
			||||||
 | 
									&pending->listener, &graph_events, pending);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						spa_loop_invoke(impl->data_loop, do_sync_filter_graph, 0, NULL, 0, true, impl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (pending->active)
 | 
				
			||||||
 | 
							pending->handle = new_handle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (impl->in_filter_props == 0)
 | 
				
			||||||
 | 
							clean_filter_handles(impl, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS;
 | 
				
			||||||
 | 
						impl->params[IDX_PropInfo].user++;
 | 
				
			||||||
 | 
						impl->params[IDX_Props].user++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					error:
 | 
				
			||||||
 | 
						if (new_handle != NULL)
 | 
				
			||||||
 | 
							spa_plugin_loader_unload(impl->loader, new_handle);
 | 
				
			||||||
 | 
						return -ENOTSUP;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int audioconvert_set_param(struct impl *this, const char *k, const char *s)
 | 
					static int audioconvert_set_param(struct impl *this, const char *k, const char *s)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
						int res;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (spa_streq(k, "monitor.channel-volumes"))
 | 
						if (spa_streq(k, "monitor.channel-volumes"))
 | 
				
			||||||
		this->monitor_channel_volumes = spa_atob(s);
 | 
							this->monitor_channel_volumes = spa_atob(s);
 | 
				
			||||||
	else if (spa_streq(k, "channelmix.disable"))
 | 
						else if (spa_streq(k, "channelmix.disable"))
 | 
				
			||||||
| 
						 | 
					@ -939,6 +1189,13 @@ static int audioconvert_set_param(struct impl *this, const char *k, const char *
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	else if (spa_streq(k, "channelmix.lock-volumes"))
 | 
						else if (spa_streq(k, "channelmix.lock-volumes"))
 | 
				
			||||||
		this->props.lock_volumes = spa_atob(s);
 | 
							this->props.lock_volumes = spa_atob(s);
 | 
				
			||||||
 | 
						else if (spa_strstartswith(k, "audioconvert.filter-graph")) {
 | 
				
			||||||
 | 
							int order = atoi(k+ strlen("audioconvert.filter-graph."));
 | 
				
			||||||
 | 
							if ((res = load_filter_graph(this, s, order)) < 0) {
 | 
				
			||||||
 | 
								spa_log_warn(this->log, "Can't load filter-graph %d: %s",
 | 
				
			||||||
 | 
										order, spa_strerror(res));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	else
 | 
						else
 | 
				
			||||||
		return 0;
 | 
							return 0;
 | 
				
			||||||
	return 1;
 | 
						return 1;
 | 
				
			||||||
| 
						 | 
					@ -1262,7 +1519,8 @@ static int apply_props(struct impl *this, const struct spa_pod *param)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		case SPA_PROP_params:
 | 
							case SPA_PROP_params:
 | 
				
			||||||
			changed += parse_prop_params(this, &prop->value);
 | 
								if (this->filter_props_count == 0)
 | 
				
			||||||
 | 
									changed += parse_prop_params(this, &prop->value);
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
| 
						 | 
					@ -1456,9 +1714,29 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	case SPA_PARAM_Props:
 | 
						case SPA_PARAM_Props:
 | 
				
			||||||
		if (apply_props(this, param) > 0)
 | 
						{
 | 
				
			||||||
 | 
							uint32_t i;
 | 
				
			||||||
 | 
							bool have_graph = false;
 | 
				
			||||||
 | 
							this->filter_props_count = 0;
 | 
				
			||||||
 | 
							for (i = 0; i < MAX_GRAPH; i++) {
 | 
				
			||||||
 | 
								struct filter_graph *g = &this->filter_graph[i];
 | 
				
			||||||
 | 
								if (!g->active)
 | 
				
			||||||
 | 
									continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								have_graph = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								this->in_filter_props++;
 | 
				
			||||||
 | 
								spa_filter_graph_set_props(g->graph,
 | 
				
			||||||
 | 
										SPA_DIRECTION_INPUT, param);
 | 
				
			||||||
 | 
								this->filter_props_count++;
 | 
				
			||||||
 | 
								this->in_filter_props--;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (!have_graph && apply_props(this, param) > 0)
 | 
				
			||||||
			emit_node_info(this, false);
 | 
								emit_node_info(this, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							clean_filter_handles(this, false);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		return -ENOENT;
 | 
							return -ENOENT;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -1975,6 +2253,13 @@ static int setup_convert(struct impl *this)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if ((res = setup_in_convert(this)) < 0)
 | 
						if ((res = setup_in_convert(this)) < 0)
 | 
				
			||||||
		return res;
 | 
							return res;
 | 
				
			||||||
 | 
						for (i = 0; i < MAX_GRAPH; i++) {
 | 
				
			||||||
 | 
							struct filter_graph *g = &this->filter_graph[i];
 | 
				
			||||||
 | 
							if (!g->active)
 | 
				
			||||||
 | 
								continue;
 | 
				
			||||||
 | 
							if ((res = setup_filter_graph(this, g->graph)) < 0)
 | 
				
			||||||
 | 
								return res;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	if ((res = setup_channelmix(this)) < 0)
 | 
						if ((res = setup_channelmix(this)) < 0)
 | 
				
			||||||
		return res;
 | 
							return res;
 | 
				
			||||||
	if ((res = setup_resample(this)) < 0)
 | 
						if ((res = setup_resample(this)) < 0)
 | 
				
			||||||
| 
						 | 
					@ -2007,6 +2292,12 @@ static int setup_convert(struct impl *this)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void reset_node(struct impl *this)
 | 
					static void reset_node(struct impl *this)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
						uint32_t i;
 | 
				
			||||||
 | 
						for (i = 0; i < MAX_GRAPH; i++) {
 | 
				
			||||||
 | 
							struct filter_graph *g = &this->filter_graph[i];
 | 
				
			||||||
 | 
							if (g->graph)
 | 
				
			||||||
 | 
								spa_filter_graph_deactivate(g->graph);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	if (this->resample.reset)
 | 
						if (this->resample.reset)
 | 
				
			||||||
		resample_reset(&this->resample);
 | 
							resample_reset(&this->resample);
 | 
				
			||||||
	this->in_offset = 0;
 | 
						this->in_offset = 0;
 | 
				
			||||||
| 
						 | 
					@ -3059,6 +3350,30 @@ static void run_channelmix_stage(struct stage *s, struct stage_context *c)
 | 
				
			||||||
		channelmix_process(&impl->mix, out_datas, in_datas, c->n_samples);
 | 
							channelmix_process(&impl->mix, out_datas, in_datas, c->n_samples);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void run_filter_stage(struct stage *s, struct stage_context *c)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct filter_graph *fg = s->data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						spa_log_trace_fp(s->impl->log, "%p: filter-graph %d", s->impl, c->n_samples);
 | 
				
			||||||
 | 
						spa_filter_graph_process(fg->graph, (const void **)c->datas[s->in_idx],
 | 
				
			||||||
 | 
								c->datas[s->out_idx], c->n_samples);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					static void add_filter_stage(struct impl *impl, uint32_t i, struct filter_graph *fg, struct stage_context *ctx)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct stage *s = &impl->stages[impl->n_stages];
 | 
				
			||||||
 | 
						s->impl = impl;
 | 
				
			||||||
 | 
						s->passthrough = false;
 | 
				
			||||||
 | 
						s->in_idx = ctx->src_idx;
 | 
				
			||||||
 | 
						s->out_idx = ctx->dst_idx;
 | 
				
			||||||
 | 
						s->n_in = ctx->n_datas;
 | 
				
			||||||
 | 
						s->n_out = ctx->n_datas;
 | 
				
			||||||
 | 
						s->data = fg;
 | 
				
			||||||
 | 
						s->run = run_filter_stage;
 | 
				
			||||||
 | 
						impl->n_stages++;
 | 
				
			||||||
 | 
						ctx->src_idx = ctx->dst_idx;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void add_channelmix_stage(struct impl *impl, struct stage_context *ctx)
 | 
					static void add_channelmix_stage(struct impl *impl, struct stage_context *ctx)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct stage *s = &impl->stages[impl->n_stages];
 | 
						struct stage *s = &impl->stages[impl->n_stages];
 | 
				
			||||||
| 
						 | 
					@ -3111,10 +3426,11 @@ static void add_dst_convert_stage(struct impl *impl, struct stage_context *ctx)
 | 
				
			||||||
static void recalc_stages(struct impl *this, struct stage_context *ctx)
 | 
					static void recalc_stages(struct impl *this, struct stage_context *ctx)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct dir *dir;
 | 
						struct dir *dir;
 | 
				
			||||||
	bool in_passthrough, mix_passthrough, resample_passthrough, out_passthrough;
 | 
						bool filter_passthrough, in_passthrough, mix_passthrough, resample_passthrough, out_passthrough;
 | 
				
			||||||
	int tmp = 0;
 | 
						int tmp = 0;
 | 
				
			||||||
	struct port *ctrlport = ctx->ctrlport;
 | 
						struct port *ctrlport = ctx->ctrlport;
 | 
				
			||||||
	bool in_need_remap, out_need_remap;
 | 
						bool in_need_remap, out_need_remap;
 | 
				
			||||||
 | 
						uint32_t i;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	this->recalc = false;
 | 
						this->recalc = false;
 | 
				
			||||||
	this->n_stages = 0;
 | 
						this->n_stages = 0;
 | 
				
			||||||
| 
						 | 
					@ -3128,11 +3444,12 @@ static void recalc_stages(struct impl *this, struct stage_context *ctx)
 | 
				
			||||||
	out_need_remap = dir->need_remap;
 | 
						out_need_remap = dir->need_remap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	resample_passthrough = resample_is_passthrough(this);
 | 
						resample_passthrough = resample_is_passthrough(this);
 | 
				
			||||||
 | 
						filter_passthrough = this->n_graph == 0;
 | 
				
			||||||
	this->resample_passthrough = resample_passthrough;
 | 
						this->resample_passthrough = resample_passthrough;
 | 
				
			||||||
	mix_passthrough = SPA_FLAG_IS_SET(this->mix.flags, CHANNELMIX_FLAG_IDENTITY) &&
 | 
						mix_passthrough = SPA_FLAG_IS_SET(this->mix.flags, CHANNELMIX_FLAG_IDENTITY) &&
 | 
				
			||||||
		(ctrlport == NULL || ctrlport->ctrl == NULL) && (this->vol_ramp_sequence == NULL);
 | 
							(ctrlport == NULL || ctrlport->ctrl == NULL) && (this->vol_ramp_sequence == NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (in_passthrough && mix_passthrough && resample_passthrough)
 | 
						if (in_passthrough && filter_passthrough && mix_passthrough && resample_passthrough)
 | 
				
			||||||
		out_passthrough = false;
 | 
							out_passthrough = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (out_passthrough && out_need_remap)
 | 
						if (out_passthrough && out_need_remap)
 | 
				
			||||||
| 
						 | 
					@ -3142,7 +3459,7 @@ static void recalc_stages(struct impl *this, struct stage_context *ctx)
 | 
				
			||||||
		add_wav_stage(this, ctx);
 | 
							add_wav_stage(this, ctx);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!in_passthrough) {
 | 
						if (!in_passthrough) {
 | 
				
			||||||
		if (mix_passthrough && resample_passthrough && out_passthrough)
 | 
							if (filter_passthrough && mix_passthrough && resample_passthrough && out_passthrough)
 | 
				
			||||||
			ctx->dst_idx = ctx->final_idx;
 | 
								ctx->dst_idx = ctx->final_idx;
 | 
				
			||||||
		else
 | 
							else
 | 
				
			||||||
			ctx->dst_idx = CTX_DATA_TMP_0 + ((tmp++) & 1);
 | 
								ctx->dst_idx = CTX_DATA_TMP_0 + ((tmp++) & 1);
 | 
				
			||||||
| 
						 | 
					@ -3155,7 +3472,7 @@ static void recalc_stages(struct impl *this, struct stage_context *ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (this->direction == SPA_DIRECTION_INPUT) {
 | 
						if (this->direction == SPA_DIRECTION_INPUT) {
 | 
				
			||||||
		if (!resample_passthrough) {
 | 
							if (!resample_passthrough) {
 | 
				
			||||||
			if (mix_passthrough && out_passthrough)
 | 
								if (filter_passthrough && mix_passthrough && out_passthrough)
 | 
				
			||||||
				ctx->dst_idx = ctx->final_idx;
 | 
									ctx->dst_idx = ctx->final_idx;
 | 
				
			||||||
			else
 | 
								else
 | 
				
			||||||
				ctx->dst_idx = CTX_DATA_TMP_0 + ((tmp++) & 1);
 | 
									ctx->dst_idx = CTX_DATA_TMP_0 + ((tmp++) & 1);
 | 
				
			||||||
| 
						 | 
					@ -3164,6 +3481,19 @@ static void recalc_stages(struct impl *this, struct stage_context *ctx)
 | 
				
			||||||
			resample_passthrough = true;
 | 
								resample_passthrough = true;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if (!filter_passthrough) {
 | 
				
			||||||
 | 
							for (i = 0; i < this->n_graph; i++) {
 | 
				
			||||||
 | 
								struct filter_graph *fg = &this->filter_graph[this->graph_index[i]];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (mix_passthrough && resample_passthrough && out_passthrough &&
 | 
				
			||||||
 | 
								    i + 1 == this->n_graph)
 | 
				
			||||||
 | 
									ctx->dst_idx = ctx->final_idx;
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
									ctx->dst_idx = CTX_DATA_TMP_0 + ((tmp++) & 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								add_filter_stage(this, i, fg, ctx);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	if (!mix_passthrough) {
 | 
						if (!mix_passthrough) {
 | 
				
			||||||
		if (resample_passthrough && out_passthrough)
 | 
							if (resample_passthrough && out_passthrough)
 | 
				
			||||||
			ctx->dst_idx = ctx->final_idx;
 | 
								ctx->dst_idx = ctx->final_idx;
 | 
				
			||||||
| 
						 | 
					@ -3630,6 +3960,8 @@ static int impl_clear(struct spa_handle *handle)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	free_tmp(this);
 | 
						free_tmp(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						clean_filter_handles(this, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (this->resample.free)
 | 
						if (this->resample.free)
 | 
				
			||||||
		resample_free(&this->resample);
 | 
							resample_free(&this->resample);
 | 
				
			||||||
	if (this->wav_file != NULL)
 | 
						if (this->wav_file != NULL)
 | 
				
			||||||
| 
						 | 
					@ -3654,6 +3986,8 @@ impl_init(const struct spa_handle_factory *factory,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct impl *this;
 | 
						struct impl *this;
 | 
				
			||||||
	uint32_t i;
 | 
						uint32_t i;
 | 
				
			||||||
 | 
						const char *str;
 | 
				
			||||||
 | 
						bool filter_graph_disabled;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	spa_return_val_if_fail(factory != NULL, -EINVAL);
 | 
						spa_return_val_if_fail(factory != NULL, -EINVAL);
 | 
				
			||||||
	spa_return_val_if_fail(handle != NULL, -EINVAL);
 | 
						spa_return_val_if_fail(handle != NULL, -EINVAL);
 | 
				
			||||||
| 
						 | 
					@ -3672,7 +4006,10 @@ impl_init(const struct spa_handle_factory *factory,
 | 
				
			||||||
		this->cpu_flags = spa_cpu_get_flags(this->cpu);
 | 
							this->cpu_flags = spa_cpu_get_flags(this->cpu);
 | 
				
			||||||
		this->max_align = SPA_MIN(MAX_ALIGN, spa_cpu_get_max_align(this->cpu));
 | 
							this->max_align = SPA_MIN(MAX_ALIGN, spa_cpu_get_max_align(this->cpu));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						this->loader = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_PluginLoader);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	props_reset(&this->props);
 | 
						props_reset(&this->props);
 | 
				
			||||||
 | 
						filter_graph_disabled = this->props.filter_graph_disabled;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	this->rate_limit.interval = 2 * SPA_NSEC_PER_SEC;
 | 
						this->rate_limit.interval = 2 * SPA_NSEC_PER_SEC;
 | 
				
			||||||
	this->rate_limit.burst = 1;
 | 
						this->rate_limit.burst = 1;
 | 
				
			||||||
| 
						 | 
					@ -3685,12 +4022,13 @@ impl_init(const struct spa_handle_factory *factory,
 | 
				
			||||||
	this->mix.rear_delay = 0.0f;
 | 
						this->mix.rear_delay = 0.0f;
 | 
				
			||||||
	this->mix.widen = 0.0f;
 | 
						this->mix.widen = 0.0f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (info && (str = spa_dict_lookup(info, "clock.quantum-limit")) != NULL)
 | 
				
			||||||
 | 
							spa_atou32(str, &this->quantum_limit, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	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;
 | 
				
			||||||
		if (spa_streq(k, "clock.quantum-limit"))
 | 
							if (spa_streq(k, "resample.peaks"))
 | 
				
			||||||
			spa_atou32(s, &this->quantum_limit, 0);
 | 
					 | 
				
			||||||
		else if (spa_streq(k, "resample.peaks"))
 | 
					 | 
				
			||||||
			this->resample_peaks = spa_atob(s);
 | 
								this->resample_peaks = spa_atob(s);
 | 
				
			||||||
		else if (spa_streq(k, "resample.prefill"))
 | 
							else if (spa_streq(k, "resample.prefill"))
 | 
				
			||||||
			SPA_FLAG_UPDATE(this->resample.options,
 | 
								SPA_FLAG_UPDATE(this->resample.options,
 | 
				
			||||||
| 
						 | 
					@ -3706,10 +4044,12 @@ impl_init(const struct spa_handle_factory *factory,
 | 
				
			||||||
			spa_scnprintf(this->group_name, sizeof(this->group_name), "%s", s);
 | 
								spa_scnprintf(this->group_name, sizeof(this->group_name), "%s", s);
 | 
				
			||||||
		else if (spa_streq(k, "monitor.passthrough"))
 | 
							else if (spa_streq(k, "monitor.passthrough"))
 | 
				
			||||||
			this->monitor_passthrough = spa_atob(s);
 | 
								this->monitor_passthrough = spa_atob(s);
 | 
				
			||||||
 | 
							else if (spa_streq(k, "audioconvert.filter-graph.disable"))
 | 
				
			||||||
 | 
								filter_graph_disabled = spa_atob(s);
 | 
				
			||||||
		else
 | 
							else
 | 
				
			||||||
			audioconvert_set_param(this, k, s);
 | 
								audioconvert_set_param(this, k, s);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						this->props.filter_graph_disabled = filter_graph_disabled;
 | 
				
			||||||
	this->props.channel.n_volumes = this->props.n_channels;
 | 
						this->props.channel.n_volumes = this->props.n_channels;
 | 
				
			||||||
	this->props.soft.n_volumes = this->props.n_channels;
 | 
						this->props.soft.n_volumes = this->props.n_channels;
 | 
				
			||||||
	this->props.monitor.n_volumes = this->props.n_channels;
 | 
						this->props.monitor.n_volumes = this->props.n_channels;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue