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/loop.h>
 | 
			
		||||
#include <spa/support/log.h>
 | 
			
		||||
#include <spa/support/plugin-loader.h>
 | 
			
		||||
#include <spa/utils/result.h>
 | 
			
		||||
#include <spa/utils/list.h>
 | 
			
		||||
#include <spa/utils/json.h>
 | 
			
		||||
| 
						 | 
				
			
			@ -30,6 +31,7 @@
 | 
			
		|||
#include <spa/pod/dynamic.h>
 | 
			
		||||
#include <spa/debug/types.h>
 | 
			
		||||
#include <spa/control/ump-utils.h>
 | 
			
		||||
#include <spa/filter-graph/filter-graph.h>
 | 
			
		||||
 | 
			
		||||
#include "volume-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_PORTS	(SPA_AUDIO_MAX_CHANNELS+1)
 | 
			
		||||
#define MAX_STAGES	64
 | 
			
		||||
#define MAX_GRAPH	9	/* 8 active + 1 replacement slot */
 | 
			
		||||
 | 
			
		||||
#define DEFAULT_MUTE		false
 | 
			
		||||
#define DEFAULT_VOLUME		VOLUME_NORM
 | 
			
		||||
| 
						 | 
				
			
			@ -96,6 +99,7 @@ struct props {
 | 
			
		|||
	double rate;
 | 
			
		||||
	char wav_path[512];
 | 
			
		||||
	unsigned int lock_volumes:1;
 | 
			
		||||
	unsigned int filter_graph_disabled:1;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static void props_reset(struct props *props)
 | 
			
		||||
| 
						 | 
				
			
			@ -117,6 +121,7 @@ static void props_reset(struct props *props)
 | 
			
		|||
	props->rate = 1.0;
 | 
			
		||||
	spa_zero(props->wav_path);
 | 
			
		||||
	props->lock_volumes = false;
 | 
			
		||||
	props->filter_graph_disabled = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct buffer {
 | 
			
		||||
| 
						 | 
				
			
			@ -219,6 +224,17 @@ struct stage {
 | 
			
		|||
	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 spa_handle handle;
 | 
			
		||||
	struct spa_node node;
 | 
			
		||||
| 
						 | 
				
			
			@ -226,6 +242,14 @@ struct impl {
 | 
			
		|||
	struct spa_log *log;
 | 
			
		||||
	struct spa_cpu *cpu;
 | 
			
		||||
	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];
 | 
			
		||||
	uint32_t n_stages;
 | 
			
		||||
| 
						 | 
				
			
			@ -446,6 +470,7 @@ static int impl_node_enum_params(void *object, int seq,
 | 
			
		|||
	uint8_t buffer[4096];
 | 
			
		||||
	struct spa_result_node_params result;
 | 
			
		||||
	uint32_t count = 0;
 | 
			
		||||
	int res;
 | 
			
		||||
 | 
			
		||||
	spa_return_val_if_fail(this != NULL, -EINVAL);
 | 
			
		||||
	spa_return_val_if_fail(num != 0, -EINVAL);
 | 
			
		||||
| 
						 | 
				
			
			@ -776,7 +801,29 @@ static int impl_node_enum_params(void *object, int seq,
 | 
			
		|||
				SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->lock_volumes),
 | 
			
		||||
				SPA_PROP_INFO_params, SPA_POD_Bool(true));
 | 
			
		||||
			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:
 | 
			
		||||
			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;
 | 
			
		||||
| 
						 | 
				
			
			@ -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, "channelmix.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]);
 | 
			
		||||
			param = spa_pod_builder_pop(&b, &f[0]);
 | 
			
		||||
			break;
 | 
			
		||||
		default:
 | 
			
		||||
			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;
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -897,8 +960,195 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size)
 | 
			
		|||
	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)
 | 
			
		||||
{
 | 
			
		||||
	int res;
 | 
			
		||||
 | 
			
		||||
	if (spa_streq(k, "monitor.channel-volumes"))
 | 
			
		||||
		this->monitor_channel_volumes = spa_atob(s);
 | 
			
		||||
	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"))
 | 
			
		||||
		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
 | 
			
		||||
		return 0;
 | 
			
		||||
	return 1;
 | 
			
		||||
| 
						 | 
				
			
			@ -1262,6 +1519,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param)
 | 
			
		|||
			}
 | 
			
		||||
			break;
 | 
			
		||||
		case SPA_PROP_params:
 | 
			
		||||
			if (this->filter_props_count == 0)
 | 
			
		||||
				changed += parse_prop_params(this, &prop->value);
 | 
			
		||||
			break;
 | 
			
		||||
		default:
 | 
			
		||||
| 
						 | 
				
			
			@ -1456,9 +1714,29 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
 | 
			
		|||
		break;
 | 
			
		||||
	}
 | 
			
		||||
	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);
 | 
			
		||||
 | 
			
		||||
		clean_filter_handles(this, false);
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
	default:
 | 
			
		||||
		return -ENOENT;
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -1975,6 +2253,13 @@ static int setup_convert(struct impl *this)
 | 
			
		|||
 | 
			
		||||
	if ((res = setup_in_convert(this)) < 0)
 | 
			
		||||
		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)
 | 
			
		||||
		return res;
 | 
			
		||||
	if ((res = setup_resample(this)) < 0)
 | 
			
		||||
| 
						 | 
				
			
			@ -2007,6 +2292,12 @@ static int setup_convert(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)
 | 
			
		||||
		resample_reset(&this->resample);
 | 
			
		||||
	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);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
{
 | 
			
		||||
	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)
 | 
			
		||||
{
 | 
			
		||||
	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;
 | 
			
		||||
	struct port *ctrlport = ctx->ctrlport;
 | 
			
		||||
	bool in_need_remap, out_need_remap;
 | 
			
		||||
	uint32_t i;
 | 
			
		||||
 | 
			
		||||
	this->recalc = false;
 | 
			
		||||
	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;
 | 
			
		||||
 | 
			
		||||
	resample_passthrough = resample_is_passthrough(this);
 | 
			
		||||
	filter_passthrough = this->n_graph == 0;
 | 
			
		||||
	this->resample_passthrough = resample_passthrough;
 | 
			
		||||
	mix_passthrough = SPA_FLAG_IS_SET(this->mix.flags, CHANNELMIX_FLAG_IDENTITY) &&
 | 
			
		||||
		(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;
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
 | 
			
		||||
	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;
 | 
			
		||||
		else
 | 
			
		||||
			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 (!resample_passthrough) {
 | 
			
		||||
			if (mix_passthrough && out_passthrough)
 | 
			
		||||
			if (filter_passthrough && mix_passthrough && out_passthrough)
 | 
			
		||||
				ctx->dst_idx = ctx->final_idx;
 | 
			
		||||
			else
 | 
			
		||||
				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;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	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 (resample_passthrough && out_passthrough)
 | 
			
		||||
			ctx->dst_idx = ctx->final_idx;
 | 
			
		||||
| 
						 | 
				
			
			@ -3630,6 +3960,8 @@ static int impl_clear(struct spa_handle *handle)
 | 
			
		|||
 | 
			
		||||
	free_tmp(this);
 | 
			
		||||
 | 
			
		||||
	clean_filter_handles(this, true);
 | 
			
		||||
 | 
			
		||||
	if (this->resample.free)
 | 
			
		||||
		resample_free(&this->resample);
 | 
			
		||||
	if (this->wav_file != NULL)
 | 
			
		||||
| 
						 | 
				
			
			@ -3654,6 +3986,8 @@ impl_init(const struct spa_handle_factory *factory,
 | 
			
		|||
{
 | 
			
		||||
	struct impl *this;
 | 
			
		||||
	uint32_t i;
 | 
			
		||||
	const char *str;
 | 
			
		||||
	bool filter_graph_disabled;
 | 
			
		||||
 | 
			
		||||
	spa_return_val_if_fail(factory != 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->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);
 | 
			
		||||
	filter_graph_disabled = this->props.filter_graph_disabled;
 | 
			
		||||
 | 
			
		||||
	this->rate_limit.interval = 2 * SPA_NSEC_PER_SEC;
 | 
			
		||||
	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.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++) {
 | 
			
		||||
		const char *k = info->items[i].key;
 | 
			
		||||
		const char *s = info->items[i].value;
 | 
			
		||||
		if (spa_streq(k, "clock.quantum-limit"))
 | 
			
		||||
			spa_atou32(s, &this->quantum_limit, 0);
 | 
			
		||||
		else if (spa_streq(k, "resample.peaks"))
 | 
			
		||||
		if (spa_streq(k, "resample.peaks"))
 | 
			
		||||
			this->resample_peaks = spa_atob(s);
 | 
			
		||||
		else if (spa_streq(k, "resample.prefill"))
 | 
			
		||||
			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);
 | 
			
		||||
		else if (spa_streq(k, "monitor.passthrough"))
 | 
			
		||||
			this->monitor_passthrough = spa_atob(s);
 | 
			
		||||
		else if (spa_streq(k, "audioconvert.filter-graph.disable"))
 | 
			
		||||
			filter_graph_disabled = spa_atob(s);
 | 
			
		||||
		else
 | 
			
		||||
			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.soft.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