Tutorial - Part 6: Binding Objects | Index
In this tutorial we show how to use pw_filter to create a real-time audio processing filter. This is useful for implementing audio effects, equalizers, analyzers, and other DSP applications.
Let's take a look at the code before we break it down:
#include <stdio.h>
#include <errno.h>
#include <math.h>
#include <signal.h>
struct data;
struct port {
struct data *data;
};
struct data {
struct port *in_port;
struct port *out_port;
};
{
struct data *data = userdata;
float *in, *out;
if (in == NULL || out == NULL)
return;
memcpy(out, in, n_samples * sizeof(float));
}
};
static void do_quit(void *userdata, int signal_number)
{
struct data *data = userdata;
}
int main(int argc, char *argv[])
{
struct data data = { 0, };
"audio-filter",
NULL),
&filter_events,
sizeof(struct port),
NULL),
NULL, 0);
sizeof(struct port),
NULL),
NULL, 0);
));
params, n_params) < 0) {
fprintf(stderr, "can't connect\n");
return -1;
}
return 0;
}
buffer[1023]
Definition core.h:437
int pw_filter_connect(struct pw_filter *filter, enum pw_filter_flags flags, const struct spa_pod **params, uint32_t n_params)
Connect a filter for processing.
Definition filter.c:1566
void * pw_filter_add_port(struct pw_filter *filter, enum pw_direction direction, enum pw_filter_port_flags flags, size_t port_data_size, struct pw_properties *props, const struct spa_pod **params, uint32_t n_params)
add a port to the filter, returns user data of port_data_size.
Definition filter.c:1807
#define PW_VERSION_FILTER_EVENTS
Definition filter.h:67
void pw_filter_destroy(struct pw_filter *filter)
Destroy a filter
Definition filter.c:1414
void * pw_filter_get_dsp_buffer(void *port_data, uint32_t n_samples)
Get a data pointer to the buffer data.
Definition filter.c:2045
struct pw_filter * pw_filter_new_simple(struct pw_loop *loop, const char *name, struct pw_properties *props, const struct pw_filter_events *events, void *data)
Definition filter.c:1288
@ PW_FILTER_FLAG_RT_PROCESS
call process from the realtime thread.
Definition filter.h:112
@ PW_FILTER_PORT_FLAG_MAP_BUFFERS
mmap the buffers except DmaBuf that is not explicitly marked as mappable.
Definition filter.h:134
#define PW_KEY_PORT_NAME
port name
Definition keys.h:346
#define PW_KEY_MEDIA_TYPE
Media.
Definition keys.h:512
#define PW_KEY_MEDIA_ROLE
Role: Movie, Music, Camera, Screen, Communication, Game, Notification, DSP, Production,...
Definition keys.h:518
#define PW_KEY_MEDIA_CATEGORY
Media Category: Playback, Capture, Duplex, Monitor, Manager.
Definition keys.h:515
#define PW_KEY_FORMAT_DSP
format related properties
Definition keys.h:555
#define pw_log_trace(...)
Definition log.h:182
PW_API_LOOP_IMPL struct spa_source * pw_loop_add_signal(struct pw_loop *object, int signal_number, spa_source_signal_func_t func, void *data)
Definition loop.h:177
int pw_main_loop_quit(struct pw_main_loop *loop)
Quit a main loop.
Definition main-loop.c:108
void pw_main_loop_destroy(struct pw_main_loop *loop)
Destroy a loop.
Definition main-loop.c:73
int pw_main_loop_run(struct pw_main_loop *loop)
Run a main loop.
Definition main-loop.c:122
struct pw_main_loop * pw_main_loop_new(const struct spa_dict *props)
Create a new main loop.
Definition main-loop.c:63
struct pw_loop * pw_main_loop_get_loop(struct pw_main_loop *loop)
Get the loop implementation.
Definition main-loop.c:96
void pw_init(int *argc, char **argv[])
Initialize PipeWire.
Definition pipewire.c:488
void pw_deinit(void)
Deinitialize PipeWire.
Definition pipewire.c:603
#define PW_DIRECTION_OUTPUT
Definition port.h:55
#define PW_DIRECTION_INPUT
Definition port.h:53
struct pw_properties * pw_properties_new(const char *key,...)
Make a new properties object.
Definition properties.c:97
#define SPA_PROCESS_LATENCY_INFO_INIT(...)
Definition latency.h:92
SPA_API_LATENCY_UTILS struct spa_pod * spa_process_latency_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_process_latency_info *info)
Definition latency-utils.h:143
@ SPA_PARAM_ProcessLatency
processing latency, a SPA_TYPE_OBJECT_ParamProcessLatency
Definition param.h:46
#define SPA_POD_BUILDER_INIT(buffer, size)
Definition builder.h:72
#define SPA_NSEC_PER_MSEC
Definition defs.h:255
Events for a filter.
Definition filter.h:65
void(* process)(void *data, struct spa_io_position *position)
do processing.
Definition filter.h:93
uint64_t duration
Duration of current cycle, in samples @ rate.
Definition io.h:193
The position information adds extra meaning to the raw clock times.
Definition io.h:353
struct spa_io_clock clock
clock position of driver, always valid and read only
Definition io.h:354
void * data
Definition builder.h:64
uint32_t type
Definition pod.h:59
Save as tutorial7.c and compile with:
gcc -Wall tutorial7.c -o tutorial7 -lm $(pkg-config --cflags --libs libpipewire-0.3)
Overview
Unlike pw_stream which is designed for applications that produce or consume audio data, pw_filter is designed for applications that process existing audio streams. Filters have both input and output ports and operate in the DSP domain using 32-bit floating point samples.
Setting up the Filter
We start with the usual boilerplate and define our data structure:
struct data {
struct port *in_port;
struct port *out_port;
};
The filter object manages both input and output ports. Each port represents an audio channel that can be connected to other applications.
Creating the Filter
"audio-filter",
NULL),
&filter_events,
&data);
We use pw_filter_new_simple() which automatically manages the core connection for us. The properties are important:
PW_KEY_MEDIA_TYPE: "Audio" indicates this is an audio filter
PW_KEY_MEDIA_CATEGORY: "Filter" tells the session manager this processes audio
PW_KEY_MEDIA_ROLE: "DSP" indicates this is for audio processing
Adding Ports
Next we add input and output ports:
sizeof(struct port),
NULL),
NULL, 0);
sizeof(struct port),
NULL),
NULL, 0);
Key points about filter ports:
PW_DIRECTION_INPUT and PW_DIRECTION_OUTPUT specify the port direction
PW_FILTER_PORT_FLAG_MAP_BUFFERS allows direct memory access to buffers
PW_KEY_FORMAT_DSP indicates this uses 32-bit float DSP format
- DSP ports work with normalized floating-point samples (typically -1.0 to 1.0)
Setting Process Latency
This tells PipeWire that our filter adds 10 milliseconds of processing latency. This information helps the audio system maintain proper timing and latency compensation throughout the audio graph.
Connecting the Filter
params, n_params) < 0) {
fprintf(stderr, "can't connect\n");
return -1;
}
The PW_FILTER_FLAG_RT_PROCESS flag ensures our process callback runs in the real-time audio thread. This is crucial for low-latency audio processing but means our process function must be real-time safe (no allocations, file I/O, or blocking operations).
The Process Callback
The heart of the filter is the process callback:
{
struct data *data = userdata;
float *in, *out;
if (in == NULL || out == NULL)
return;
memcpy(out, in, n_samples * sizeof(float));
}
The process function is called for each audio buffer and works as follows:
- Get the number of samples to process from
position->clock.duration
- Get input and output buffer pointers using
pw_filter_get_dsp_buffer()
- Process the audio data (here we just copy input to output)
- The framework handles queueing the processed buffers
Key Points about DSP Processing:
- Float Format: DSP buffers use 32-bit float samples, typically normalized to [-1.0, 1.0]
- Real-time Safe: The process function runs in the audio thread and must be real-time safe
- Buffer Management:
pw_filter_get_dsp_buffer() handles the buffer lifecycle automatically
- Sample-accurate: Processing happens at the audio sample rate with precise timing
Advanced Usage
This example shows a simple passthrough, but you can implement any audio processing:
for (uint32_t i = 0; i < n_samples; i++) {
out[i] = in[i] * 0.5f;
}
static float last_sample = 0.0f;
float alpha = 0.99f;
for (uint32_t i = 0; i < n_samples; i++) {
out[i] = alpha * (out[i] + in[i] - last_sample);
last_sample = in[i];
}
| Feature | pw_stream | pw_filter |
| Use case | Audio playback/recording | Audio processing/effects |
| Data format | Various (S16, S32, etc.) | 32-bit float DSP |
| Ports | Single direction | Input and output |
| Buffer management | Manual queue/dequeue | Automatic via get_dsp_buffer |
| Typical apps | Media players, recorders | Equalizers, effects, analyzers |
Connecting and Linking the Filter
Manual Linking Options
Filters require manual connection by design. You can connect them using:
Using pw-link command line:
# List output ports (sources)
pw-link -o
# List input ports (sinks)
pw-link -i
# List existing connections
pw-link -l
# Connect a source to filter input
pw-link "source_app:output_FL" "audio-filter:input"
# Connect filter output to sink
pw-link "audio-filter:output" "sink_app:input_FL"
Understanding Filter Auto-Connection Behavior
Important**: Unlike audio sources and sinks, filters are not automatically connected by WirePlumber. This is by design because filters are meant to be explicitly inserted into audio chains where needed.
Why filters don't auto-connect**:
- Filters process existing audio streams rather than generate/consume them
- Auto-connecting filters could create unwanted audio processing
- Filters typically require specific placement in the audio graph
- Manual connection gives users control over when/where effects are applied
Testing the Filter
The filter requires manual connection to test. Here's the recommended workflow:
- Start an audio source (e.g.,
pw-play music.wav)
- Run your filter (
./tutorial7)
- Check available ports:
# List output ports
pw-link -o | grep -E "(pw-play|audio-filter)"
# List input ports
pw-link -i | grep -E "(audio-filter|playback)"
- Connect the audio chain manually:
# Connect source -> filter -> sink
pw-link "pw-play:output_FL" "audio-filter:input"
pw-link "audio-filter:output" "alsa_output.pci-0000_00_1f.3.analog-stereo:playback_FL"
You should hear the audio pass through your filter. Modify the process function to add effects like volume changes, filtering, or other audio processing.
Alternative: Use a patchbay tool**
- Helvum:
flatpak install flathub org.pipewire.Helvum
- qpwgraph: Available in most Linux distributions
- Carla: Full-featured audio plugin host
These tools provide graphical interfaces for connecting PipeWire nodes and are ideal for experimenting with filter placement.
Tutorial - Part 6: Binding Objects | Index