module-filter-chain.c 62.7 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* PipeWire
 *
 * Copyright © 2021 Wim Taymans
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dlfcn.h>
#include <unistd.h>

#include "config.h"

36
#include "module-filter-chain/plugin.h"
37
38

#include <spa/utils/result.h>
39
#include <spa/utils/string.h>
40
41
#include <spa/utils/json.h>
#include <spa/param/profiler.h>
Wim Taymans's avatar
Wim Taymans committed
42
#include <spa/pod/dynamic.h>
43
44
#include <spa/debug/pod.h>

45
#include <pipewire/utils.h>
46
47
#include <pipewire/private.h>
#include <pipewire/impl.h>
48
#include <pipewire/extensions/profiler.h>
49

Wim Taymans's avatar
Wim Taymans committed
50
#define NAME "filter-chain"
51

52
53
54
PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
#define PW_LOG_TOPIC_DEFAULT mod_topic

55
56
57
/**
 * \page page_module_filter_chain PipeWire Module: Filter-Chain
 *
Wim Taymans's avatar
Wim Taymans committed
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
 * The filter-chain allows you to create an arbitrary processing graph
 * from LADSPA, LV2 and builtin filters. This filter can be made into a
 * virtual sink/source or between any 2 nodes in the graph.
 *
 * The filter chain is built with 2 streams, a capture stream providing
 * the input to the filter chain and a playback stream sending out the
 * filtered stream to the next nodes in the graph.
 *
 * Because both ends of the filter-chain are built with streams, the session
 * manager can manage the configuration and connection with the sinks and
 * sources automatically.
 *
 * ## Module Options
 *
 * - `node.description`: a human readable name for the filter chain
 * - `filter.graph = []`: a description of the filter graph to run, see below
 * - `capture.props = {}`: properties to be passed to the input stream
 * - `playback.props = {}`: properties to be passed to the output stream
 *
 * ## Filter graph description
 *
 * The general structure of the graph description is as follows:
 *
 *\code{.unparsed}
 *     filter.graph = {
 *         nodes = [
 *             {
 *                 type = <ladspa | lv2 | builtin>
 *                 name = <name>
 *                 plugin = <plugin>
 *                 label = <label>
 *                 config = {
 *                     <configkey> = <value> ...
 *                 }
 *                 control = {
 *                     <controlname|controlindex> = <value> ...
 *                 }
 *             }
 *             ...
 *         ]
 *         links = [
 *             { output = <portname> input = <portname> }
 *             ...
 *         ]
 *         inputs = [ <portname> ... ]
 *         outputs = [ <portname> ... ]
 *    }
 *\endcode
 *
 * ### Nodes
 *
 * Nodes describe the processing filters in the graph. Use a tool like lv2ls
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
 * or listplugins to get a list of available plugins, labels and the port names.
 *
 * - `type` is one of `ladspa`, `lv2` or `builtin`
 * - `name` is the name for this node, you might need this later to refer to this node
 *    and its ports when setting controls or making links.
 * - `plugin` is the type specific plugin name.
 *    - For LADSPA plugins it will append `.so` to find the shared object with that
 *       name in the LADSPA plugin path.
 *    - For LV2, this is the plugin URI obtained with lv2ls.
 *    - For builtin this is ignored
 * - `label` is the type specific filter inside the plugin.
 *    - For LADSPA this is the label
 *    - For LV2 this is unused
 *    - For builtin this is the name of the filter to use
 *
 * - `config` contains a filter specific configuration section. The convolver
 *            plugin needs this.
 * - `control` contains the initial values for the control ports of the filter.
Wim Taymans's avatar
Wim Taymans committed
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
 *
 * ### Links
 *
 * Links can be made between ports of nodes. The `portname` is given as
 * `<node_name>:<port_name>`.
 *
 * You can tee the output of filters to multiple other filters. You need to
 * use a mixer if you want the output of multiple filters to go into one
 * filter input port.
 *
 * links can be omited when the graph has just 1 filter.
 *
 * ### Inputs and Outputs
 *
 * These are the entry and exit ports into the graph definition. Their number
 * defines the number of channels used by the filter-chain.
 *
 * The `<portname>` can be `null` when a channel is to be ignored.
 *
 * Each input/output in the graph can only be linked to one filter input/output.
 * You need to use the copy builtin filter if the stream signal needs to be routed
 * to multiple filters. You need to use the mixer builtin plugin if multiple graph
 * outputs need to go to one output stream.
 *
 * inputs and outputs can be omitted, in which case the filter-chain will use all
 * inputs from the first filter and all outputs from the last filter node. The
 * graph will then be duplicated as many times to match the number of input/output
 * channels of the streams.
 *
 * ## Builtin filters
 *
159
160
 * There are some useful builtin filters available. You select them with the label
 * of the filter node.
Wim Taymans's avatar
Wim Taymans committed
161
162
163
 *
 * ### Mixer
 *
164
 * Use the `mixer` plugin if you have multiple input signals that need to be mixed together.
Wim Taymans's avatar
Wim Taymans committed
165
166
167
168
169
170
171
 *
 * The mixer plugin has up to 8 input ports labeled "In 1" to "In 8" and each with
 * a gain control labeled "Gain 1" to "Gain 8". There is an output port labeled
 * "Out". Unused input ports will be ignoded and not cause overhead.
 *
 * ### Copy
 *
172
 * Use the `copy` plugin if you need to copy a stream input signal to multiple filters.
Wim Taymans's avatar
Wim Taymans committed
173
174
175
176
177
178
179
180
181
182
 *
 * It has one input port "In" and one output port "Out".
 *
 * ### Biquads
 *
 * Biquads can be used to do all kinds of filtering. They are also used when creating
 * equalizers.
 *
 * All biquad filters have an input port "In" and an output port "Out". They have
 * a "Freq", "Q" and "Gain" control. Their meaning depends on the particular biquad that
183
 * is used. The following labels can be used:
Wim Taymans's avatar
Wim Taymans committed
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
 *
 * - `bq_lowpass` a lowpass filter.
 * - `bq_highpass` a highpass filter.
 * - `bq_bandpass` a bandpass filter.
 * - `bq_lowshelf` a low shelf filter.
 * - `bq_highshelf` a high shelf filter.
 * - `bq_peaking` a peaking filter.
 * - `bq_notch` a notch filter.
 * - `bq_allpass` an allpass filter.
 *
 * ### Convolver
 *
 * The convolver can be used to apply an impulse response to a signal. It is usually used
 * for reverbs or virtual surround. The convolver is implemented with a fast FFT
 * implementation.
 *
 * The convolver has an input port "In" and an output port "Out". It requires a config
 * section in the node declaration in this format:
 *
 *\code{.unparsed}
 * filter.graph = {
 *     nodes = [
 *         {
 *             type   = builtin
 *             name   = ...
 *             label  = convolver
 *             config = {
 *                 blocksize = ...
 *                 tailsize = ...
 *                 gain = ...
 *                 delay = ...
 *                 filename = ...
 *                 offset = ...
 *                 length = ...
 *                 channel = ...
 *             }
 *             ...
 *         }
 *     }
 *     ...
 * }
 *\endcode
 *
 * - `blocksize` specifies the size of the blocks to use in the FFT. It is a value
 *               between 64 and 256. When not specified, this value is
 *               computed automatically from the number of samples in the file.
 * - `tailsize` specifies the size of the tail blocks to use in the FFT.
 * - `gain`     the overall gain to apply to the IR file.
 * - `delay`    The extra delay (in samples) to add to the IR.
 * - `filename` The IR to load or create. Possible values are:
 *     - `/hilbert` creates a [hilbert function](https://en.wikipedia.org/wiki/Hilbert_transform)
 *                that can be used to phase shift the signal by +/-90 degrees. The
 *                `length` will be used as the number of coefficients.
 *     - `/dirac` creates a [Dirac function](https://en.wikipedia.org/wiki/Dirac_delta_function) that
 *                 can be used as gain.
 *     - A filename to load as the IR. This needs to be a file format supported
 *               by sndfile.
 * - `offset`  The sample offset in the file as the start of the IR.
 * - `length`  The number of samples to use as the IR.
 * - `channel` The channel to use from the file as the IR.
 *
Wim Taymans's avatar
Wim Taymans committed
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
 * ### Delay
 *
 * The delay can be used to delay a signal in time.
 *
 * The delay has an input port "In" and an output port "Out". It also has
 * a "Delay (s)" control port. It requires a config section in the node declaration
 * in this format:
 *
 *\code{.unparsed}
 * filter.graph = {
 *     nodes = [
 *         {
 *             type   = builtin
 *             name   = ...
 *             label  = delay
 *             config = {
 *                 "max-delay" = ...
 *             }
 *             control = {
 *                 "Delay (s)" = ...
 *             }
 *             ...
 *         }
 *     }
 *     ...
 * }
 *\endcode
 *
 * - `max-delay` the maximum delay in seconds. The "Delay (s)" parameter will
 *              be clamped to this value.
 *
Wim Taymans's avatar
Wim Taymans committed
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
 * ## General options
 *
 * Options with well-known behavior. Most options can be added to the global
 * configuration or the individual streams:
 *
 * - \ref PW_KEY_REMOTE_NAME
 * - \ref PW_KEY_AUDIO_RATE
 * - \ref PW_KEY_AUDIO_CHANNELS
 * - \ref SPA_KEY_AUDIO_POSITION
 * - \ref PW_KEY_MEDIA_NAME
 * - \ref PW_KEY_NODE_LATENCY
 * - \ref PW_KEY_NODE_DESCRIPTION
 * - \ref PW_KEY_NODE_GROUP
 * - \ref PW_KEY_NODE_LINK_GROUP
 * - \ref PW_KEY_NODE_VIRTUAL
Wim Taymans's avatar
Wim Taymans committed
291
292
 * - \ref PW_KEY_NODE_NAME: See notes below. If not specified, defaults to
 *   	'filter-chain-<pid>-<module-id>'.
Wim Taymans's avatar
Wim Taymans committed
293
294
295
296
 *
 * Stream only properties:
 *
 * - \ref PW_KEY_MEDIA_CLASS
Wim Taymans's avatar
Wim Taymans committed
297
298
299
 * - \ref PW_KEY_NODE_NAME:  if not given per stream, the global node.name will be
 *         prefixed with 'input.' and 'output.' to generate a capture and playback
 *         stream node.name respectively.
Wim Taymans's avatar
Wim Taymans committed
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
 *
 * ## Example configuration of a virtual source
 *
 * This example uses the rnnoise LADSPA plugin to create a new
 * virtual source.
 *
 *\code{.unparsed}
 * context.modules = [
 * {   name = libpipewire-module-filter-chain
 *     args = {
 *         node.description =  "Noise Canceling source"
 *         media.name =  "Noise Canceling source"
 *         filter.graph = {
 *             nodes = [
 *                 {
 *                     type = ladspa
 *                     name = rnnoise
 *                     plugin = ladspa/librnnoise_ladspa
 *                     label = noise_suppressor_stereo
 *                     control = {
 *                         "VAD Threshold (%)" 50.0
 *                     }
 *                 }
 *             ]
 *         }
 *         capture.props = {
 *             node.name =  "capture.rnnoise_source"
 *             node.passive = true
 *         }
 *         playback.props = {
 *             node.name =  "rnnoise_source"
 *             media.class = Audio/Source
 *         }
 *     }
 * }
 * ]
 *\endcode
 *
 * ## Example configuration of a Dolby Surround encoder virtual Sink
 *
Wim Taymans's avatar
Wim Taymans committed
340
 * This example uses the ladpsa surround encoder to encode a 5.1 signal
Wim Taymans's avatar
Wim Taymans committed
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
 * to a stereo Dolby Surround signal.
 *
 *\code{.unparsed}
 *
 *\code{.unparsed}
 * context.modules = [
 * {   name = libpipewire-module-filter-chain
 *     args = {
 *         node.description = "Dolby Surround Sink"
 *         media.name       = "Dolby Surround Sink"
 *         filter.graph = {
 *             nodes = [
 *                 {
 *                     type  = builtin
 *                     name  = mixer
 *                     label = mixer
 *                     control = { "Gain 1" = 0.5 "Gain 2" = 0.5 }
 *                 }
 *                 {
 *                     type   = ladspa
 *                     name   = enc
 *                     plugin = surround_encoder_1401
 *                     label  = surroundEncoder
 *                 }
 *             ]
 *             links = [
 *                 { output = "mixer:Out" input = "enc:S" }
 *             ]
 *             inputs  = [ "enc:L" "enc:R" "enc:C" null "mixer:In 1" "mixer:In 2" ]
 *             outputs = [ "enc:Lt" "enc:Rt" ]
 *         }
 *         capture.props = {
 *             node.name      = "effect_input.dolby_surround"
 *             media.class    = Audio/Sink
 *             audio.channels = 6
 *             audio.position = [ FL FR FC LFE SL SR ]
 *         }
 *         playback.props = {
 *             node.name      = "effect_output.dolby_surround"
 *             node.passive   = true
 *             audio.channels = 2
 *             audio.position = [ FL FR ]
 *         }
 *     }
 * }
 * ]
 *\endcode
388
 */
389
390
static const struct spa_dict_item module_props[] = {
	{ PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
Wim Taymans's avatar
Wim Taymans committed
391
	{ PW_KEY_MODULE_DESCRIPTION, "Create filter chain streams" },
392
393
394
395
396
397
	{ PW_KEY_MODULE_USAGE, " [ remote.name=<remote> ] "
				"[ node.latency=<latency as fraction> ] "
				"[ node.description=<description of the nodes> ] "
				"[ audio.rate=<sample rate> ] "
				"[ audio.channels=<number of channels> ] "
				"[ audio.position=<channel map> ] "
Wim Taymans's avatar
Wim Taymans committed
398
399
400
				"filter.graph = [ "
				"    nodes = [ "
				"        { "
Wim Taymans's avatar
Wim Taymans committed
401
				"          type = <ladspa | lv2 | builtin> "
Wim Taymans's avatar
Wim Taymans committed
402
403
404
				"          name = <name> "
				"          plugin = <plugin> "
				"          label = <label> "
405
406
407
				"          config = { "
				"             <configkey> = <value> ... "
				"          } "
Wim Taymans's avatar
Wim Taymans committed
408
				"          control = { "
Wim Taymans's avatar
Wim Taymans committed
409
				"             <controlname|controlindex> = <value> ... "
Wim Taymans's avatar
Wim Taymans committed
410
411
412
413
414
415
416
417
418
				"          } "
				"        } "
				"    ] "
				"    links = [ "
				"        { output = <portname> input = <portname> } ... "
				"    ] "
				"    inputs = [ <portname> ... ] "
				"    outputs = [ <portname> ... ] "
				"] "
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
				"[ capture.props=<properties> ] "
				"[ playback.props=<properties> ] " },
	{ PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
};

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <getopt.h>
#include <limits.h>
#include <math.h>

#include <spa/utils/result.h>
#include <spa/pod/builder.h>
#include <spa/param/audio/format-utils.h>
#include <spa/param/audio/raw.h>

#include <pipewire/pipewire.h>

439
#define MAX_HNDL 64
440
441
#define MAX_SAMPLES 8192

442
443
444
static float silence_data[MAX_SAMPLES];
static float discard_data[MAX_SAMPLES];

445
struct plugin {
446
447
	struct spa_list link;
	int ref;
448
	char type[64];
449
	char path[PATH_MAX];
450
451

	struct fc_plugin *plugin;
452
453
454
	struct spa_list descriptor_list;
};

455
struct descriptor {
456
457
	struct spa_list link;
	int ref;
458
	struct plugin *plugin;
459
	char label[256];
460
461

	const struct fc_descriptor *desc;
462
463
464
465
466

	uint32_t n_input;
	uint32_t n_output;
	uint32_t n_control;
	uint32_t n_notify;
467
468
469
470
471
	unsigned long *input;
	unsigned long *output;
	unsigned long *control;
	unsigned long *notify;
	float *default_control;
472
473
};

474
struct port {
475
	struct spa_list link;
476
	struct node *node;
477
478

	uint32_t idx;
479
480
481
482
	unsigned long p;

	struct spa_list link_list;
	uint32_t n_links;
483
	uint32_t external;
484

485
486
	float control_data;
	float *audio_data[MAX_HNDL];
487
488
};

489
490
struct node {
	struct spa_list link;
491
492
	struct graph *graph;

493
	struct descriptor *desc;
494
495

	char name[256];
496
	char *config;
497

498
499
500
501
	struct port *input_port;
	struct port *output_port;
	struct port *control_port;
	struct port *notify_port;
502
503

	uint32_t n_hndl;
504
	void *hndl[MAX_HNDL];
505
506
507

	unsigned int n_deps;
	unsigned int visited:1;
508
509
510
511
};

struct link {
	struct spa_list link;
512

513
	struct spa_list input_link;
514
515
	struct spa_list output_link;

516
517
	struct port *output;
	struct port *input;
518
519
};

520
struct graph_port {
521
	const struct fc_descriptor *desc;
522
	void **hndl;
523
524
525
526
	uint32_t port;
};

struct graph_hndl {
527
	const struct fc_descriptor *desc;
528
	void **hndl;
529
530
};

531
532
533
534
535
536
537
struct graph {
	struct impl *impl;

	struct spa_list node_list;
	struct spa_list link_list;

	uint32_t n_input;
538
	struct graph_port *input;
539
540

	uint32_t n_output;
541
	struct graph_port *output;
542
543

	uint32_t n_hndl;
544
	struct graph_hndl *hndl;
545
546

	uint32_t n_control;
547
	struct port **control_port;
548
};
549
550
551
552
553
554
555
556
557
558
559
560

struct impl {
	struct pw_context *context;

	struct pw_impl_module *module;

	struct spa_hook module_listener;

	struct pw_core *core;
	struct spa_hook core_proxy_listener;
	struct spa_hook core_listener;

561
	struct spa_list plugin_list;
562

563
564
565
566
567
568
569
570
571
572
573
574
	struct pw_properties *capture_props;
	struct pw_stream *capture;
	struct spa_hook capture_listener;
	struct spa_audio_info_raw capture_info;

	struct pw_properties *playback_props;
	struct pw_stream *playback;
	struct spa_hook playback_listener;
	struct spa_audio_info_raw playback_info;

	unsigned int do_disconnect:1;

575
	long unsigned rate;
576

577
	struct graph graph;
578
579
};

580
581
582
583
static int graph_instantiate(struct graph *graph);
static void graph_cleanup(struct graph *graph);


584
585
586
587
588
589
590
591
static void capture_destroy(void *d)
{
	struct impl *impl = d;
	spa_hook_remove(&impl->capture_listener);
	impl->capture = NULL;
}

static void capture_process(void *d)
592
593
594
595
596
597
{
	struct impl *impl = d;
	pw_stream_trigger_process(impl->playback);
}

static void playback_process(void *d)
598
599
600
{
	struct impl *impl = d;
	struct pw_buffer *in, *out;
601
	struct graph *graph = &impl->graph;
602
	uint32_t i, insize = 0, outsize = 0, n_hndl = graph->n_hndl;
603
	int32_t stride = 0;
604
605
	struct graph_port *port;
	struct spa_data *bd;
606
607

	if ((in = pw_stream_dequeue_buffer(impl->capture)) == NULL)
608
		pw_log_debug("%p: out of capture buffers: %m", impl);
609
610

	if ((out = pw_stream_dequeue_buffer(impl->playback)) == NULL)
611
		pw_log_debug("%p: out of playback buffers: %m", impl);
612
613
614
615
616

	if (in == NULL || out == NULL)
		goto done;

	for (i = 0; i < in->buffer->n_datas; i++) {
617
618
		uint32_t offs, size;

619
		bd = &in->buffer->datas[i];
620

621
622
623
624
625
626
		offs = SPA_MIN(bd->chunk->offset, bd->maxsize);
		size = SPA_MIN(bd->chunk->size, bd->maxsize - offs);

		port = i < graph->n_input ? &graph->input[i] : NULL;

		if (port && port->desc)
627
			port->desc->connect_port(*port->hndl, port->port,
628
				SPA_PTROFF(bd->data, offs, void));
629

630
		insize = i == 0 ? size : SPA_MIN(insize, size);
631
		stride = SPA_MAX(stride, bd->chunk->stride);
632
	}
633
634
	outsize = insize;

635
	for (i = 0; i < out->buffer->n_datas; i++) {
636
637
638
		bd = &out->buffer->datas[i];

		outsize = SPA_MIN(outsize, bd->maxsize);
639

640
		port = i < graph->n_output ? &graph->output[i] : NULL;
641

642
		if (port && port->desc)
643
			port->desc->connect_port(*port->hndl, port->port, bd->data);
644
		else
645
			memset(bd->data, 0, outsize);
646

647
648
649
		bd->chunk->offset = 0;
		bd->chunk->size = outsize;
		bd->chunk->stride = stride;
650
	}
651

652
	pw_log_trace_fp("%p: stride:%d in:%d out:%d requested:%"PRIu64" (%"PRIu64")", impl,
653
654
			stride, insize, outsize, out->requested, out->requested * stride);

655
656
	for (i = 0; i < n_hndl; i++) {
		struct graph_hndl *hndl = &graph->hndl[i];
657
		hndl->desc->run(*hndl->hndl, outsize / sizeof(float));
658
	}
659
660
661
662
663
664
665
666

done:
	if (in != NULL)
		pw_stream_queue_buffer(impl->capture, in);
	if (out != NULL)
		pw_stream_queue_buffer(impl->playback, out);
}

667
static float get_default(struct impl *impl, struct descriptor *desc, uint32_t p)
668
{
669
	struct fc_port *port = &desc->desc->ports[p];
670
	return port->def;
671
672
}

673
674
675
676
static struct node *find_node(struct graph *graph, const char *name)
{
	struct node *node;
	spa_list_for_each(node, &graph->node_list, link) {
677
		if (spa_streq(node->name, name))
678
679
680
681
682
			return node;
	}
	return NULL;
}

Wim Taymans's avatar
Wim Taymans committed
683
684
685
686
687
688
/* find a port by name. Valid syntax is:
 *  "<node_name>:<port_name>"
 *  "<node_name>:<port_id>"
 *  "<port_name>"
 *  "<port_id>"
 *  When no node_name is given, the port is assumed in the current node.  */
689
static struct port *find_port(struct node *node, const char *name, int descriptor)
690
691
692
{
	char *col, *node_name, *port_name, *str;
	struct port *ports;
693
	const struct fc_descriptor *d;
694
	uint32_t i, n_ports, port_id = SPA_ID_INVALID;
695
696
697
698

	str = strdupa(name);
	col = strchr(str, ':');
	if (col != NULL) {
699
		struct node *find;
700
701
702
		node_name = str;
		port_name = col + 1;
		*col = '\0';
703
704
705
706
707
708
709
710
711
712
713
		find = find_node(node->graph, node_name);
		if (find == NULL) {
			/* it's possible that the : is part of the port name,
			 * try again without splitting things up. */
			*col = ':';
			col = NULL;
		} else {
			node = find;
		}
	}
	if (col == NULL) {
714
715
716
717
718
719
		node_name = node->name;
		port_name = str;
	}
	if (node == NULL)
		return NULL;

720
721
722
	if (!spa_atou32(port_name, &port_id, 0))
		port_id = SPA_ID_INVALID;

723
724
	if (FC_IS_PORT_INPUT(descriptor)) {
		if (FC_IS_PORT_CONTROL(descriptor)) {
725
726
727
728
729
730
			ports = node->control_port;
			n_ports = node->desc->n_control;
		} else {
			ports = node->input_port;
			n_ports = node->desc->n_input;
		}
731
732
	} else if (FC_IS_PORT_OUTPUT(descriptor)) {
		if (FC_IS_PORT_CONTROL(descriptor)) {
733
734
735
736
737
738
			ports = node->notify_port;
			n_ports = node->desc->n_notify;
		} else {
			ports = node->output_port;
			n_ports = node->desc->n_output;
		}
739
740
741
742
743
744
	} else
		return NULL;

	d = node->desc->desc;
	for (i = 0; i < n_ports; i++) {
		struct port *port = &ports[i];
745
746
		if (i == port_id ||
		    spa_streq(d->ports[port->p].name, port_name))
747
748
749
750
751
			return port;
	}
	return NULL;
}

752
static struct spa_pod *get_prop_info(struct graph *graph, struct spa_pod_builder *b, uint32_t idx)
753
{
754
	struct impl *impl = graph->impl;
755
	struct spa_pod_frame f[2];
756
757
	struct port *port = graph->control_port[idx];
	struct node *node = port->node;
758
759
760
	struct descriptor *desc = node->desc;
	const struct fc_descriptor *d = desc->desc;
	struct fc_port *p = &d->ports[port->p];
761
	float def, min, max;
762
	char name[512];
763
	uint32_t rate = impl->rate ? impl->rate : 48000;
764

765
	if (p->hint & FC_HINT_SAMPLE_RATE) {
766
767
768
		def = p->def * rate;
		min = p->min * rate;
		max = p->max * rate;
769
770
771
772
773
774
	} else {
		def = p->def;
		min = p->min;
		max = p->max;
	}

775
	if (node->name[0] != '\0')
776
		snprintf(name, sizeof(name), "%s:%s", node->name, p->name);
777
	else
778
		snprintf(name, sizeof(name), "%s", p->name);
779

780
781
782
	spa_pod_builder_push_object(b, &f[0],
			SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo);
	spa_pod_builder_add (b,
783
			SPA_PROP_INFO_name, SPA_POD_String(name),
784
785
			0);
	spa_pod_builder_prop(b, SPA_PROP_INFO_type, 0);
Wim Taymans's avatar
Wim Taymans committed
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
	if (p->hint & FC_HINT_BOOLEAN) {
		if (min == max) {
			spa_pod_builder_bool(b, def <= 0.0 ? false : true);
		} else  {
			spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0);
			spa_pod_builder_bool(b, def <= 0.0 ? false : true);
			spa_pod_builder_bool(b, false);
			spa_pod_builder_bool(b, true);
			spa_pod_builder_pop(b, &f[1]);
		}
	} else if (p->hint & FC_HINT_INTEGER) {
		if (min == max) {
			spa_pod_builder_int(b, def);
		} else {
			spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Range, 0);
			spa_pod_builder_int(b, def);
			spa_pod_builder_int(b, min);
			spa_pod_builder_int(b, max);
			spa_pod_builder_pop(b, &f[1]);
		}
806
	} else {
Wim Taymans's avatar
Wim Taymans committed
807
808
809
810
811
812
813
814
815
		if (min == max) {
			spa_pod_builder_float(b, def);
		} else {
			spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Range, 0);
			spa_pod_builder_float(b, def);
			spa_pod_builder_float(b, min);
			spa_pod_builder_float(b, max);
			spa_pod_builder_pop(b, &f[1]);
		}
816
	}
817
818
	spa_pod_builder_prop(b, SPA_PROP_INFO_params, 0);
	spa_pod_builder_bool(b, true);
819
820
821
	return spa_pod_builder_pop(b, &f[0]);
}

822
static struct spa_pod *get_props_param(struct graph *graph, struct spa_pod_builder *b)
823
824
825
{
	struct spa_pod_frame f[2];
	uint32_t i;
826
	char name[512];
827
828
829

	spa_pod_builder_push_object(b, &f[0],
			SPA_TYPE_OBJECT_Props, SPA_PARAM_Props);
830
831
832
	spa_pod_builder_prop(b, SPA_PROP_params, 0);
	spa_pod_builder_push_struct(b, &f[1]);

833
	for (i = 0; i < graph->n_control; i++) {
834
		struct port *port = graph->control_port[i];
835
		struct node *node = port->node;
836
837
838
		struct descriptor *desc = node->desc;
		const struct fc_descriptor *d = desc->desc;
		struct fc_port *p = &d->ports[port->p];
839
840

		if (node->name[0] != '\0')
841
			snprintf(name, sizeof(name), "%s:%s", node->name, p->name);
842
		else
843
			snprintf(name, sizeof(name), "%s", p->name);
844
845

		spa_pod_builder_string(b, name);
Wim Taymans's avatar
Wim Taymans committed
846
847
848
849
850
851
852
		if (p->hint & FC_HINT_BOOLEAN) {
			spa_pod_builder_bool(b, port->control_data <= 0.0 ? false : true);
		} else if (p->hint & FC_HINT_INTEGER) {
			spa_pod_builder_int(b, port->control_data);
		} else {
			spa_pod_builder_float(b, port->control_data);
		}
853
	}
854
	spa_pod_builder_pop(b, &f[1]);
855
856
857
	return spa_pod_builder_pop(b, &f[0]);
}

858
static int set_control_value(struct node *node, const char *name, float *value)
Wim Taymans's avatar
Wim Taymans committed
859
{
860
	struct descriptor *desc;
861
862
	struct port *port;
	float old;
863

864
	port = find_port(node, name, FC_PORT_INPUT | FC_PORT_CONTROL);
865
	if (port == NULL)
866
		return -ENOENT;
867

868
869
870
	node = port->node;
	desc = node->desc;

871
872
	old = port->control_data;
	port->control_data = value ? *value : desc->default_control[port->idx];
873
	pw_log_info("control %d ('%s') from %f to %f", port->idx, name, old, port->control_data);
874
	return old == port->control_data ? 0 : 1;
Wim Taymans's avatar
Wim Taymans committed
875
876
}

877
878
879
880
static int parse_params(struct graph *graph, const struct spa_pod *pod)
{
	struct spa_pod_parser prs;
	struct spa_pod_frame f;
881
	int res, changed = 0;
882
883
884
885
886
887
888
889
890
891
892
	struct node *def_node;

	def_node = spa_list_first(&graph->node_list, struct node, link);

	spa_pod_parser_pod(&prs, pod);
	if (spa_pod_parser_push_struct(&prs, &f) < 0)
		return 0;

	while (true) {
		const char *name;
		float value, *val = NULL;
893
		double dbl_val;
Wim Taymans's avatar
Wim Taymans committed
894
895
		bool bool_val;
		int32_t int_val;
896
897
898

		if (spa_pod_parser_get_string(&prs, &name) < 0)
			break;
Wim Taymans's avatar
Wim Taymans committed
899
		if (spa_pod_parser_get_float(&prs, &value) >= 0) {
900
			val = &value;
901
902
903
		} else if (spa_pod_parser_get_double(&prs, &dbl_val) >= 0) {
			value = dbl_val;
			val = &value;
Wim Taymans's avatar
Wim Taymans committed
904
905
906
907
908
909
910
911
912
913
		} else if (spa_pod_parser_get_int(&prs, &int_val) >= 0) {
			value = int_val;
			val = &value;
		} else if (spa_pod_parser_get_bool(&prs, &bool_val) >= 0) {
			value = bool_val ? 1.0f : 0.0f;
			val = &value;
		} else {
			struct spa_pod *pod;
			spa_pod_parser_get_pod(&prs, &pod);
		}
914
915
		if ((res = set_control_value(def_node, name, val)) > 0)
			changed += res;
916
917
918
919
	}
	return changed;
}

920
921
922
923
924
static void graph_reset(struct graph *graph)
{
	uint32_t i;
	for (i = 0; i < graph->n_hndl; i++) {
		struct graph_hndl *hndl = &graph->hndl[i];
925
		const struct fc_descriptor *d = hndl->desc;
926
927
		if (hndl->hndl == NULL || *hndl->hndl == NULL)
			continue;
928
		if (d->deactivate)
929
			d->deactivate(*hndl->hndl);
930
		if (d->activate)
931
			d->activate(*hndl->hndl);
932
933
	}
}
934
static void param_props_changed(struct impl *impl, const struct spa_pod *param)
Wim Taymans's avatar
Wim Taymans committed
935
{
936
	struct spa_pod_object *obj = (struct spa_pod_object *) param;
937
	const struct spa_pod_prop *prop;
938
	struct graph *graph = &impl->graph;
939
	int changed = 0;
940

941
	SPA_POD_OBJECT_FOREACH(obj, prop) {
942
		if (prop->key == SPA_PROP_params)
943
			changed += parse_params(graph, &prop->value);
944
945
946
	}
	if (changed > 0) {
		uint8_t buffer[1024];
947
		struct spa_pod_dynamic_builder b;
948
949
		const struct spa_pod *params[1];

950
951
		spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096);
		params[0] = get_props_param(graph, &b.b);
952

953
		pw_stream_update_params(impl->capture, params, 1);
954
		spa_pod_dynamic_builder_clean(&b);
955
	}
Wim Taymans's avatar
Wim Taymans committed
956
957
}

958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
static void param_latency_changed(struct impl *impl, const struct spa_pod *param)
{
	struct spa_latency_info latency;
	uint8_t buffer[1024];
	struct spa_pod_builder b;
	const struct spa_pod *params[1];

	if (spa_latency_parse(param, &latency) < 0)
		return;

	spa_pod_builder_init(&b, buffer, sizeof(buffer));
	params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency);

	if (latency.direction == SPA_DIRECTION_INPUT)
		pw_stream_update_params(impl->capture, params, 1);
	else
		pw_stream_update_params(impl->playback, params, 1);
}

977
978
979
980
981
982
983
984
985
986
987
988
static void state_changed(void *data, enum pw_stream_state old,
		enum pw_stream_state state, const char *error)
{
	struct impl *impl = data;
	struct graph *graph = &impl->graph;

	switch (state) {
	case PW_STREAM_STATE_PAUSED:
		pw_stream_flush(impl->playback, false);
		pw_stream_flush(impl->capture, false);
		graph_reset(graph);
		break;
989
990
991
992
993
	case PW_STREAM_STATE_UNCONNECTED:
		pw_log_info("module %p: unconnected", impl);
		pw_impl_module_schedule_destroy(impl->module);
		break;
	case PW_STREAM_STATE_ERROR:
994
		pw_log_info("module %p: error: %s", impl, error);
995
		break;
996
997
998
999
1000
	default:
		break;
	}
}

For faster browsing, not all history is shown. View entire blame