From 87d1206fb85a076845a69925cd1bc224f9691e97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Tue, 24 Feb 2026 14:34:11 +0100 Subject: [PATCH 01/28] spa: libcamera: source: fix stop sequence Currently it is possible for the request completion handler (`impl::requestComplete`) to observe `impl::source.fd` while it is being modified in `impl::stop()`. Fix that by closing the eventfd after the camera has been stopped. Fixes: 3e28f3e8594efa ("spa: libcamera: source: rework startup sequence") (cherry picked from commit 848ac24490683a38d15e3e921d1dd2181fccdc8c) --- spa/plugins/libcamera/libcamera-source.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index f0eaa68f0..c23a6fb9d 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -184,9 +184,6 @@ struct impl { 0, nullptr, 0, this ); - if (source.fd >= 0) - spa_system_close(system, std::exchange(source.fd, -1)); - camera->requestCompleted.disconnect(this, &impl::requestComplete); if (int res = camera->stop(); res < 0) { @@ -194,6 +191,9 @@ struct impl { camera->id().c_str(), spa_strerror(res)); } + if (source.fd >= 0) + spa_system_close(system, std::exchange(source.fd, -1)); + completed_requests_rb = SPA_RINGBUFFER_INIT(); active = false; From 5cd734e8c097680eca0bfe7b46e4f93747b04fc3 Mon Sep 17 00:00:00 2001 From: Jonas Holmberg Date: Mon, 2 Mar 2026 10:28:26 +0100 Subject: [PATCH 02/28] module-protocol-native: Fix socket activation Fix path comparison in is_socket_unix() and don't unset LISTEN_FDS since the function that uses it is called more than once and it was not unset when sd_listen_fds() was used. Fixes #5140 --- src/modules/module-protocol-native.c | 2 +- src/modules/module-protocol-pulse/server.c | 2 +- src/modules/network-utils.h | 14 +++++--------- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/modules/module-protocol-native.c b/src/modules/module-protocol-native.c index 2be92a847..98a43829b 100644 --- a/src/modules/module-protocol-native.c +++ b/src/modules/module-protocol-native.c @@ -907,7 +907,7 @@ static int add_socket(struct pw_protocol *protocol, struct server *s, struct soc bool activated = false; { - int i, n = listen_fd(); + int i, n = listen_fds(); for (i = 0; i < n; ++i) { if (is_socket_unix(LISTEN_FDS_START + i, SOCK_STREAM, s->addr.sun_path) > 0) { diff --git a/src/modules/module-protocol-pulse/server.c b/src/modules/module-protocol-pulse/server.c index aeab710b0..637757dfd 100644 --- a/src/modules/module-protocol-pulse/server.c +++ b/src/modules/module-protocol-pulse/server.c @@ -576,7 +576,7 @@ static bool is_stale_socket(int fd, const struct sockaddr_un *addr_un) static int check_socket_activation(const char *path) { - const int n = listen_fd(); + const int n = listen_fds(); for (int i = 0; i < n; i++) { const int fd = LISTEN_FDS_START + i; diff --git a/src/modules/network-utils.h b/src/modules/network-utils.h index a89b7d3bd..6ff80dd7a 100644 --- a/src/modules/network-utils.h +++ b/src/modules/network-utils.h @@ -143,7 +143,7 @@ static inline bool pw_net_addr_is_any(struct sockaddr_storage *addr) /* Returns the number of file descriptors passed for socket activation. * Returns 0 if none, -1 on error. */ -static inline int listen_fd(void) +static inline int listen_fds(void) { uint32_t n; int i, flags; @@ -161,8 +161,6 @@ static inline int listen_fd(void) return -1; } - unsetenv("LISTEN_FDS"); - return (int)n; } @@ -192,12 +190,10 @@ static inline int is_socket_unix(int fd, int type, const char *path) if (addr.sun_family != AF_UNIX) return 0; size_t length = strlen(path); - if (length > 0) { - if (len < offsetof(struct sockaddr_un, sun_path) + length) - return 0; - if (memcmp(addr.sun_path, path, length) != 0) - return 0; - } + if (len < offsetof(struct sockaddr_un, sun_path) + length + 1) + return 0; + if (memcmp(addr.sun_path, path, length + 1) != 0) + return 0; } return 1; From 0d14f44f47e660aacff108ebe64a30d49cb1285d Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 2 Mar 2026 11:41:21 +0100 Subject: [PATCH 03/28] jack: never return NULL from jack_port_by_id() JACK will never return NULL from jack_port_by_id() because the id and the port_t are the same for JACK. In PipeWire however we use the serial number as the id and so it can be removed and become invalid. In this case, return a dummy port from the client that can be used for some of the basic operations you can do on a port_t, like get the name etc. Also make sure that port_name() doesn't return NULL in case we use the dummy port (which has the client set to NULL). Fixes #3512 --- pipewire-jack/src/pipewire-jack.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index 1bef74283..a7f4882ab 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -492,6 +492,8 @@ struct client { jack_position_t jack_position; jack_transport_state_t jack_state; struct frame_times jack_times; + + struct object dummy_port; }; #define return_val_if_fail(expr, val) \ @@ -4468,6 +4470,11 @@ jack_client_t * jack_client_open (const char *client_name, 0, NULL, &client->info); client->info.change_mask = 0; + client->dummy_port.type = INTERFACE_Port; + snprintf(client->dummy_port.port.name, sizeof(client->dummy_port.port.name), "%s:dummy", client_name); + snprintf(client->dummy_port.port.alias1, sizeof(client->dummy_port.port.alias1), "%s:dummy", client_name); + snprintf(client->dummy_port.port.alias2, sizeof(client->dummy_port.port.alias2), "%s:dummy", client_name); + client->show_monitor = pw_properties_get_bool(client->props, "jack.show-monitor", true); client->show_midi = pw_properties_get_bool(client->props, "jack.show-midi", true); client->merge_monitor = pw_properties_get_bool(client->props, "jack.merge-monitor", true); @@ -5951,9 +5958,7 @@ static const char *port_name(struct object *o) { const char *name; struct client *c = o->client; - if (c == NULL) - return NULL; - if (c->default_as_system && is_port_default(c, o)) + if (c != NULL && c->default_as_system && is_port_default(c, o)) name = o->port.system; else name = o->port.name; @@ -6999,13 +7004,11 @@ jack_port_t * jack_port_by_id (jack_client_t *client, pthread_mutex_lock(&c->context.lock); res = find_by_serial(c, port_id); - if (res && res->type != INTERFACE_Port) - res = NULL; - pw_log_debug("%p: port %d -> %p", c, port_id, res); pthread_mutex_unlock(&c->context.lock); + if (res == NULL || res->type != INTERFACE_Port) + res = &c->dummy_port; - if (res == NULL) - pw_log_info("%p: port %d not found", c, port_id); + pw_log_debug("%p: port %d -> %p", c, port_id, res); return object_to_port(res); } From e208a465eaa88984758dc5fcbfaf42f5227cd64e Mon Sep 17 00:00:00 2001 From: Robert Mader Date: Tue, 3 Mar 2026 20:51:58 +0100 Subject: [PATCH 04/28] pipewiresrc: Take a copy instead of a reference for last_buffer Buffer timestamps get adjusted by the base class, GstBaseSrc, even if we take an additional ref. Arguably the base class should check if buffers are writable (gst_buffer_make_writable()), which would trigger a buffer copy. That is currently not the case, though, thus do so on our side. Notes: 1. Usually a buffer copy doesn't copy the underlying memory, i.e. copying is cheap. 2. The copy holds a reference to the copied buffer, preventing the buffer from getting recycled as before. (cherry picked from commit 49300d8ee0e997ecd67de73d9bfecaf0b952bf5b) --- src/gst/gstpipewiresrc.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index b0de17dfd..0bcbe21a3 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -1592,22 +1592,23 @@ gst_pipewire_src_create (GstPushSrc * psrc, GstBuffer ** buffer) if (pwsrc->eos) { if (pwsrc->last_buffer == NULL) goto streaming_eos; - buf = pwsrc->last_buffer; - pwsrc->last_buffer = NULL; + buf = gst_buffer_steal (&pwsrc->last_buffer); update_time = TRUE; GST_LOG_OBJECT (pwsrc, "EOS, send last buffer"); break; } else if (timeout && pwsrc->last_buffer != NULL) { + buf = gst_buffer_copy (pwsrc->last_buffer); update_time = TRUE; - buf = gst_buffer_ref(pwsrc->last_buffer); GST_LOG_OBJECT (pwsrc, "timeout, send keepalive buffer"); break; } else { buf = dequeue_buffer (pwsrc); GST_LOG_OBJECT (pwsrc, "popped buffer %p", buf); if (buf != NULL) { - if (pwsrc->resend_last || pwsrc->keepalive_time > 0) - gst_buffer_replace (&pwsrc->last_buffer, buf); + if (pwsrc->resend_last || pwsrc->keepalive_time > 0) { + gst_buffer_take (&pwsrc->last_buffer, gst_buffer_copy (buf)); + gst_buffer_add_parent_buffer_meta (pwsrc->last_buffer, buf); + } break; } } From 86da3e11830f10358ddb6eac21cba72c645b34f3 Mon Sep 17 00:00:00 2001 From: Robert Mader Date: Tue, 3 Mar 2026 01:52:55 +0100 Subject: [PATCH 05/28] pipewiresrc: Use clock time difference to update last_buffer time Setting the current clock time when resending buffers is often wrong. Especially for pseudo-live sources - the default mode - it discards the original buffer time, which again is used by the base-class to adjust the timestamps further, ultimately resulting in very wrong timestamps. Instead, try to calculate the delta between when we originally got the buffer and now. (cherry picked from commit efd15264230b5b644a18a5f1a0c330dcdf5fb969) --- src/gst/gstpipewiresrc.c | 35 +++++++++++++++++++++++++++++------ src/gst/gstpipewiresrc.h | 1 + 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 0bcbe21a3..7394b7272 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -531,6 +531,7 @@ gst_pipewire_src_init (GstPipeWireSrc * src) src->autoconnect = DEFAULT_AUTOCONNECT; src->min_latency = 0; src->max_latency = GST_CLOCK_TIME_NONE; + src->last_buffer_clock_time = GST_CLOCK_TIME_NONE; src->n_buffers = 0; src->flushing_on_remove_buffer = FALSE; src->on_disconnect = DEFAULT_ON_DISCONNECT; @@ -1606,8 +1607,18 @@ gst_pipewire_src_create (GstPushSrc * psrc, GstBuffer ** buffer) GST_LOG_OBJECT (pwsrc, "popped buffer %p", buf); if (buf != NULL) { if (pwsrc->resend_last || pwsrc->keepalive_time > 0) { + GstClock *clock; + gst_buffer_take (&pwsrc->last_buffer, gst_buffer_copy (buf)); gst_buffer_add_parent_buffer_meta (pwsrc->last_buffer, buf); + + clock = gst_element_get_clock (GST_ELEMENT_CAST (pwsrc)); + if (clock != NULL) { + pwsrc->last_buffer_clock_time = gst_clock_get_time (clock); + gst_object_unref (clock); + } else { + pwsrc->last_buffer_clock_time = GST_CLOCK_TIME_NONE; + } } break; } @@ -1633,21 +1644,33 @@ gst_pipewire_src_create (GstPushSrc * psrc, GstBuffer ** buffer) if (update_time) { GstClock *clock; - GstClockTime pts, dts; + GstClockTime current_clock_time; clock = gst_element_get_clock (GST_ELEMENT_CAST (pwsrc)); if (clock != NULL) { - pts = dts = gst_clock_get_time (clock); + current_clock_time = gst_clock_get_time (clock); gst_object_unref (clock); } else { - pts = dts = GST_CLOCK_TIME_NONE; + current_clock_time = GST_CLOCK_TIME_NONE; } - GST_BUFFER_PTS (*buffer) = pts; - GST_BUFFER_DTS (*buffer) = dts; + if (GST_CLOCK_TIME_IS_VALID (current_clock_time) && + GST_CLOCK_TIME_IS_VALID (pwsrc->last_buffer_clock_time) && + GST_CLOCK_TIME_IS_VALID (GST_BUFFER_PTS (*buffer)) && + GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DTS (*buffer))) { + GstClockTime diff; + + diff = current_clock_time - pwsrc->last_buffer_clock_time; + + GST_BUFFER_PTS (*buffer) += diff; + GST_BUFFER_DTS (*buffer) += diff; + } else { + GST_BUFFER_PTS (*buffer) = GST_BUFFER_DTS (*buffer) = current_clock_time; + } GST_LOG_OBJECT (pwsrc, "Sending keepalive buffer pts/dts: %" GST_TIME_FORMAT - " (%" G_GUINT64_FORMAT ")", GST_TIME_ARGS (pts), pts); + " (%" G_GUINT64_FORMAT ")", GST_TIME_ARGS (current_clock_time), + current_clock_time); } return GST_FLOW_OK; diff --git a/src/gst/gstpipewiresrc.h b/src/gst/gstpipewiresrc.h index 869877fcb..4b0f57e0e 100644 --- a/src/gst/gstpipewiresrc.h +++ b/src/gst/gstpipewiresrc.h @@ -83,6 +83,7 @@ struct _GstPipeWireSrc { GstClockTime max_latency; GstBuffer *last_buffer; + GstClockTime last_buffer_clock_time; enum spa_meta_videotransform_value transform_value; From 8daf4ba6b6075c3933a02af6b02e340e601c6ee1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 4 Mar 2026 17:55:53 +0100 Subject: [PATCH 06/28] gst: fix compilation on older GStreamer These functions are since 1.28, which is a little too new. --- src/gst/gstpipewiresrc.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 7394b7272..6ca9599d6 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -1593,7 +1593,8 @@ gst_pipewire_src_create (GstPushSrc * psrc, GstBuffer ** buffer) if (pwsrc->eos) { if (pwsrc->last_buffer == NULL) goto streaming_eos; - buf = gst_buffer_steal (&pwsrc->last_buffer); + buf = pwsrc->last_buffer; + pwsrc->last_buffer = NULL; update_time = TRUE; GST_LOG_OBJECT (pwsrc, "EOS, send last buffer"); break; @@ -1608,8 +1609,11 @@ gst_pipewire_src_create (GstPushSrc * psrc, GstBuffer ** buffer) if (buf != NULL) { if (pwsrc->resend_last || pwsrc->keepalive_time > 0) { GstClock *clock; + GstBuffer *old; - gst_buffer_take (&pwsrc->last_buffer, gst_buffer_copy (buf)); + old = pwsrc->last_buffer; + pwsrc->last_buffer = gst_buffer_copy (buf); + gst_buffer_unref (old); gst_buffer_add_parent_buffer_meta (pwsrc->last_buffer, buf); clock = gst_element_get_clock (GST_ELEMENT_CAST (pwsrc)); From d3946c0b1044aad7cf1c3bd6617850be075b9191 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 5 Mar 2026 14:32:41 +0100 Subject: [PATCH 07/28] node: remove node.link-group from drivers Sink/Source pairs should not have the same link-group otherwise the session manager will not be able to autoconnect them with a loopback or some other internally linked stream. --- spa/plugins/alsa/acp/acp.c | 2 -- src/modules/module-ffado-driver.c | 2 -- src/modules/module-netjack2-manager.c | 2 -- 3 files changed, 6 deletions(-) diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index bee8d1ef4..8f5ea18c2 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -485,13 +485,11 @@ static int add_pro_profile(pa_card *impl, uint32_t index) if ((n_capture == 1 && n_playback == 1) || is_firewire) { PA_IDXSET_FOREACH(m, ap->output_mappings, idx) { pa_proplist_setf(m->output_proplist, "node.group", "pro-audio-%u", index); - pa_proplist_setf(m->output_proplist, "node.link-group", "pro-audio-%u", index); pa_proplist_setf(m->output_proplist, "api.alsa.auto-link", "true"); pa_proplist_setf(m->output_proplist, "api.alsa.disable-tsched", "true"); } PA_IDXSET_FOREACH(m, ap->input_mappings, idx) { pa_proplist_setf(m->input_proplist, "node.group", "pro-audio-%u", index); - pa_proplist_setf(m->input_proplist, "node.link-group", "pro-audio-%u", index); pa_proplist_setf(m->input_proplist, "api.alsa.auto-link", "true"); pa_proplist_setf(m->input_proplist, "api.alsa.disable-tsched", "true"); } diff --git a/src/modules/module-ffado-driver.c b/src/modules/module-ffado-driver.c index f6a76bed0..05b9218ce 100644 --- a/src/modules/module-ffado-driver.c +++ b/src/modules/module-ffado-driver.c @@ -1556,8 +1556,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); if (pw_properties_get(props, PW_KEY_NODE_GROUP) == NULL) pw_properties_set(props, PW_KEY_NODE_GROUP, "ffado-group"); - if (pw_properties_get(props, PW_KEY_NODE_LINK_GROUP) == NULL) - pw_properties_set(props, PW_KEY_NODE_LINK_GROUP, "ffado-group"); if (pw_properties_get(props, PW_KEY_NODE_PAUSE_ON_IDLE) == NULL) pw_properties_set(props, PW_KEY_NODE_PAUSE_ON_IDLE, "false"); diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index 0ff62d93b..a9af6a9b0 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -1393,8 +1393,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); if (pw_properties_get(props, PW_KEY_NODE_NETWORK) == NULL) pw_properties_set(props, PW_KEY_NODE_NETWORK, "true"); - if (pw_properties_get(props, PW_KEY_NODE_LINK_GROUP) == NULL) - pw_properties_set(props, PW_KEY_NODE_LINK_GROUP, "jack-group"); if (pw_properties_get(props, PW_KEY_NODE_ALWAYS_PROCESS) == NULL) pw_properties_set(props, PW_KEY_NODE_ALWAYS_PROCESS, "true"); if (pw_properties_get(props, PW_KEY_NODE_LOCK_QUANTUM) == NULL) From 5f77a7ae2b911601a78714f3bcd3af70a97eb878 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 6 Mar 2026 17:03:20 +0100 Subject: [PATCH 08/28] pw-cat: fix encoded format playback We keep a mapping between the sndfile formats and the format we would like to decode them to for encoded formats. Make sure we don't mix up the sample widths between them. Make sure we don't send encoded formats as raw. Debug the uncompressed format name correctly. Fixes #5155 --- src/tools/pw-cat.c | 96 ++++++++++++++++++++++++---------------------- 1 file changed, 51 insertions(+), 45 deletions(-) diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index f90ebffc9..6b1916d55 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -196,55 +196,60 @@ struct data { }; static const struct format_info { - const char *name; + const char *sf_name; int sf_format; + uint32_t sf_width; + const char *spa_name; uint32_t spa_format; - uint32_t width; + uint32_t spa_width; +#define FORMAT_ENCODED (1<<0) + uint32_t flags; } format_info[] = { - { "ulaw", SF_FORMAT_ULAW, SPA_AUDIO_FORMAT_ULAW, 1 }, - { "alaw", SF_FORMAT_ULAW, SPA_AUDIO_FORMAT_ALAW, 1 }, - { "s8", SF_FORMAT_PCM_S8, SPA_AUDIO_FORMAT_S8, 1 }, - { "u8", SF_FORMAT_PCM_U8, SPA_AUDIO_FORMAT_U8, 1 }, - { "s16", SF_FORMAT_PCM_16, SPA_AUDIO_FORMAT_S16, 2 }, - { "s24", SF_FORMAT_PCM_24, SPA_AUDIO_FORMAT_S24, 3 }, - { "s32", SF_FORMAT_PCM_32, SPA_AUDIO_FORMAT_S32, 4 }, - { "f32", SF_FORMAT_FLOAT, SPA_AUDIO_FORMAT_F32, 4 }, - { "f64", SF_FORMAT_DOUBLE, SPA_AUDIO_FORMAT_F32, 8 }, + { "ulaw", SF_FORMAT_ULAW, 1, "ulaw", SPA_AUDIO_FORMAT_ULAW, 1, 0 }, + { "alaw", SF_FORMAT_ULAW, 1, "alaw", SPA_AUDIO_FORMAT_ALAW, 1, 0 }, + { "s8", SF_FORMAT_PCM_S8, 1, "s8", SPA_AUDIO_FORMAT_S8, 1, 0 }, + { "u8", SF_FORMAT_PCM_U8, 1, "u8", SPA_AUDIO_FORMAT_U8, 1, 0 }, + { "s16", SF_FORMAT_PCM_16, 2, "s16", SPA_AUDIO_FORMAT_S16, 2, 0 }, + /* we read and write S24 as S32 with sndfile */ + { "s24", SF_FORMAT_PCM_24, 3, "s32", SPA_AUDIO_FORMAT_S32, 4, 0 }, + { "s32", SF_FORMAT_PCM_32, 4, "s32", SPA_AUDIO_FORMAT_S32, 4, 0 }, + { "f32", SF_FORMAT_FLOAT, 4, "f32", SPA_AUDIO_FORMAT_F32, 4, 0 }, + { "f64", SF_FORMAT_DOUBLE, 8, "f64", SPA_AUDIO_FORMAT_F32, 8, 0 }, - { "mp1", SF_FORMAT_MPEG_LAYER_I, SPA_AUDIO_FORMAT_F32, 1 }, - { "mp2", SF_FORMAT_MPEG_LAYER_II, SPA_AUDIO_FORMAT_F32, 1 }, - { "mp3", SF_FORMAT_MPEG_LAYER_III, SPA_AUDIO_FORMAT_F32, 1 }, - { "vorbis", SF_FORMAT_VORBIS, SPA_AUDIO_FORMAT_F32, 1 }, - { "opus", SF_FORMAT_OPUS, SPA_AUDIO_FORMAT_F32, 1 }, + { "mp1", SF_FORMAT_MPEG_LAYER_I, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, + { "mp2", SF_FORMAT_MPEG_LAYER_II, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, + { "mp3", SF_FORMAT_MPEG_LAYER_III, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, + { "vorbis", SF_FORMAT_VORBIS, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, + { "opus", SF_FORMAT_OPUS, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "ima-adpcm", SF_FORMAT_IMA_ADPCM, SPA_AUDIO_FORMAT_F32, 1 }, - { "ms-adpcm", SF_FORMAT_MS_ADPCM, SPA_AUDIO_FORMAT_F32, 1 }, - { "nms-adpcm-16", SF_FORMAT_NMS_ADPCM_16, SPA_AUDIO_FORMAT_F32, 1 }, - { "nms-adpcm-24", SF_FORMAT_NMS_ADPCM_24, SPA_AUDIO_FORMAT_F32, 1 }, - { "nms-adpcm-32", SF_FORMAT_NMS_ADPCM_32, SPA_AUDIO_FORMAT_F32, 1 }, + { "ima-adpcm", SF_FORMAT_IMA_ADPCM, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, + { "ms-adpcm", SF_FORMAT_MS_ADPCM, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, + { "nms-adpcm-16", SF_FORMAT_NMS_ADPCM_16, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, + { "nms-adpcm-24", SF_FORMAT_NMS_ADPCM_24, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, + { "nms-adpcm-32", SF_FORMAT_NMS_ADPCM_32, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "alac-16", SF_FORMAT_ALAC_16, SPA_AUDIO_FORMAT_F32, 1 }, - { "alac-20", SF_FORMAT_ALAC_20, SPA_AUDIO_FORMAT_F32, 1 }, - { "alac-24", SF_FORMAT_ALAC_24, SPA_AUDIO_FORMAT_F32, 1 }, - { "alac-32", SF_FORMAT_ALAC_32, SPA_AUDIO_FORMAT_F32, 1 }, + { "alac-16", SF_FORMAT_ALAC_16, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, + { "alac-20", SF_FORMAT_ALAC_20, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, + { "alac-24", SF_FORMAT_ALAC_24, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, + { "alac-32", SF_FORMAT_ALAC_32, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "gsm610", SF_FORMAT_GSM610, SPA_AUDIO_FORMAT_F32, 1 }, - { "g721-32", SF_FORMAT_G721_32, SPA_AUDIO_FORMAT_F32, 1 }, - { "g723-24", SF_FORMAT_G723_24, SPA_AUDIO_FORMAT_F32, 1 }, - { "g723-40", SF_FORMAT_G723_40, SPA_AUDIO_FORMAT_F32, 1 }, - { "dwvw-12", SF_FORMAT_DWVW_12, SPA_AUDIO_FORMAT_F32, 1 }, - { "dwvw-16", SF_FORMAT_DWVW_16, SPA_AUDIO_FORMAT_F32, 1 }, - { "dwvw-24", SF_FORMAT_DWVW_24, SPA_AUDIO_FORMAT_F32, 1 }, - { "vox", SF_FORMAT_VOX_ADPCM, SPA_AUDIO_FORMAT_F32, 1 }, - { "dpcm-16", SF_FORMAT_DPCM_16, SPA_AUDIO_FORMAT_F32, 1 }, - { "dpcm-8", SF_FORMAT_DPCM_8, SPA_AUDIO_FORMAT_F32, 1 }, + { "gsm610", SF_FORMAT_GSM610, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, + { "g721-32", SF_FORMAT_G721_32, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, + { "g723-24", SF_FORMAT_G723_24, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, + { "g723-40", SF_FORMAT_G723_40, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, + { "dwvw-12", SF_FORMAT_DWVW_12, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, + { "dwvw-16", SF_FORMAT_DWVW_16, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, + { "dwvw-24", SF_FORMAT_DWVW_24, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, + { "vox", SF_FORMAT_VOX_ADPCM, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, + { "dpcm-16", SF_FORMAT_DPCM_16, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, + { "dpcm-8", SF_FORMAT_DPCM_8, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, }; static const struct format_info *format_info_by_name(const char *str) { SPA_FOR_EACH_ELEMENT_VAR(format_info, i) - if (spa_streq(str, i->name)) + if (spa_streq(str, i->sf_name)) return i; return NULL; } @@ -263,7 +268,7 @@ static void list_formats(struct data *d) fprintf(stdout, _("Supported formats:\n")); SPA_FOR_EACH_ELEMENT_VAR(format_info, i) - fprintf(stdout, " %s\n", i->name); + fprintf(stdout, " %s\n", i->sf_name); } static int sf_playback_fill_x8(struct data *d, void *dest, unsigned int n_frames, bool *null_frame) @@ -1676,8 +1681,13 @@ static int setup_raw(struct data *data) if (info == NULL) return -EINVAL; + if (info->flags & FORMAT_ENCODED) { + fprintf(stderr, "raw: raw encoded format %s not supported\n", info->sf_name); + return -ENOTSUP; + } + data->spa_format = info->spa_format; - data->stride = info->width * data->channels; + data->stride = info->spa_width * data->channels; data->fill = data->mode == mode_playback ? raw_play : raw_record; if (spa_streq(data->filename, "-")) { @@ -1696,7 +1706,7 @@ static int setup_raw(struct data *data) if (data->verbose) fprintf(stderr, "raw: rate=%u channels=%u fmt=%s samplesize=%u stride=%u\n", data->rate, data->channels, - info->name, info->width, data->stride); + info->spa_name, info->spa_width, data->stride); return 0; } @@ -2034,14 +2044,10 @@ static int setup_sndfile(struct data *data) if (data->verbose) fprintf(stderr, "PCM: fmt:%s rate:%u channels:%u width:%u\n", - fi->name, data->rate, data->channels, fi->width); - - /* we read and write S24 as S32 with sndfile */ - if (fi->spa_format == SPA_AUDIO_FORMAT_S24) - fi = format_info_by_sf_format(SF_FORMAT_PCM_32); + fi->spa_name, data->rate, data->channels, fi->spa_width); data->spa_format = fi->spa_format; - data->stride = fi->width * data->channels; + data->stride = fi->spa_width * data->channels; data->fill = data->mode == mode_playback ? playback_fill_fn(data->spa_format) : record_fill_fn(data->spa_format); From b7341d068947225fcdf62d39277606e8516d7f52 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 6 Mar 2026 14:09:06 +0100 Subject: [PATCH 09/28] 1.6.1 --- NEWS | 34 ++++++++++++++++++++++++++++++++-- meson.build | 2 +- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 1f0a39a28..4e97bb525 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,35 @@ +# PipeWire 1.6.1 (2026-03-09) + +This is a bugfix release that is API and ABI compatible with the previous +1.6.x releases. + +## Highlights + - Fix socket activation, which could cause a failure to start PipeWire in + some setups. + - Fix crashes in many JACK apps when nodes/ports are quickly added/removed + such as when there are notifications (like when changing the volume in + KDE). + - Fix playback of encoded formats in pw-cat again. + - Some other smaller fixes and improvements. + +## Modules + - Fix socket activation. (#5140) + - Remove node.link-group from driver nodes. + +## SPA + - Fix the libcamera stop sequence. + +## JACK + - Never return NULL from jack_port_by_id(). (#3512) + +## GStreamer + - Improve the timestamps on buffers. + +## Tools + - Fix playback of encoded formats. (#5155) + +Older versions: + # PipeWire 1.6.0 (2026-02-19) This is the 1.6 release that is API and ABI compatible with previous @@ -95,8 +127,6 @@ the 1.4 release last year, including: - Add some more options to pw-cat to list supported containers and formats. (#5117) -Older versions: - # PipeWire 1.5.85 (2026-01-19) This is the fifth and hopefully last 1.6 release candidate that diff --git a/meson.build b/meson.build index c954a644c..a1f6e1880 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('pipewire', ['c' ], - version : '1.6.0', + version : '1.6.1', license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ], meson_version : '>= 0.61.1', default_options : [ 'warning_level=3', From d99a932b9c303f6cedb08960c72363bcb22bf074 Mon Sep 17 00:00:00 2001 From: qaqland Date: Sat, 7 Mar 2026 13:25:28 +0800 Subject: [PATCH 10/28] alsa-udev: support alsa.ignore-dB Some sound cards are only adapted for Android/macOS and other systems, without considering Linux. The hardware-reported dB volume is incorrect (while the percentage volume is normal). Add support for the ignore-dB option to simplify compatibility. For example, the 3206:0798 HP SIMGOT GEW1 Sound Card reports: numid=4,iface=MIXER,name='PCM Playback Volume' ; type=INTEGER,access=rw---R--,values=2,min=0,max=100,step=0 : values=100,100 | dBminmax-min=0.00dB,max=0.39dB This dB value does not match actual audio perception, and the vendor attributed this issue to non-target system compatibility. --- spa/plugins/alsa/alsa-udev.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spa/plugins/alsa/alsa-udev.c b/spa/plugins/alsa/alsa-udev.c index 8537c9760..fdc255864 100644 --- a/spa/plugins/alsa/alsa-udev.c +++ b/spa/plugins/alsa/alsa-udev.c @@ -538,6 +538,9 @@ static int emit_added_object_info(struct impl *this, struct card *card) if ((str = udev_device_get_property_value(udev_device, "ACP_PROFILE_SET")) && *str) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PROFILE_SET, str); + if ((str = udev_device_get_property_value(udev_device, "ACP_IGNORE_DB")) && *str) + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_IGNORE_DB, str); + if ((str = udev_device_get_property_value(udev_device, "SOUND_CLASS")) && *str) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_CLASS, str); From 299902bd86cc1e355e891d0dfb8c5d5d519a5843 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 9 Mar 2026 13:50:38 +0100 Subject: [PATCH 11/28] v4l2: use 0x as the prefix for hex values fixes #5161 --- spa/plugins/v4l2/v4l2-device.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spa/plugins/v4l2/v4l2-device.c b/spa/plugins/v4l2/v4l2-device.c index 2379d2105..f29252d16 100644 --- a/spa/plugins/v4l2/v4l2-device.c +++ b/spa/plugins/v4l2/v4l2-device.c @@ -98,9 +98,9 @@ static int emit_info(struct impl *this, bool full) (this->dev.cap.version >> 8) & 0xFF, (this->dev.cap.version) & 0xFF); ADD_ITEM(SPA_KEY_API_V4L2_CAP_VERSION, version); - snprintf(capabilities, sizeof(capabilities), "%08x", this->dev.cap.capabilities); + snprintf(capabilities, sizeof(capabilities), "0x%08x", this->dev.cap.capabilities); ADD_ITEM(SPA_KEY_API_V4L2_CAP_CAPABILITIES, capabilities); - snprintf(device_caps, sizeof(device_caps), "%08x", this->dev.cap.device_caps); + snprintf(device_caps, sizeof(device_caps), "0x%08x", this->dev.cap.device_caps); ADD_ITEM(SPA_KEY_API_V4L2_CAP_DEVICE_CAPS, device_caps); #undef ADD_ITEM info.props = &SPA_DICT_INIT(items, n_items); From 4c2692342efa7852cadd8535548a30745709e083 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 9 Mar 2026 16:18:58 +0100 Subject: [PATCH 12/28] impl-link: fix shared mem test We can only use non-shared memory when both nodes live in the same process _and_ we can be sure the output port is never going to be linked to a remote node because it is exclusive. This fixes the case where a null-sink is loaded inside the process space of the server and linked to the ALSA sink. This would create a link without shared mem and then as soon as something else (out of process) wants to link to the null-sink output, it would get a -22 EINVAL negotiation error because the memory can't be shared. Fixes #5159 --- src/pipewire/impl-link.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c index a77dcf35f..a7ea65cb2 100644 --- a/src/pipewire/impl-link.c +++ b/src/pipewire/impl-link.c @@ -701,7 +701,10 @@ static int do_allocation(struct pw_impl_link *this) /* always enable async mode */ alloc_flags = PW_BUFFERS_FLAG_ASYNC; - if (output->node->remote || input->node->remote) + /* shared mem can only be used if both nodes are in the same process + * and we are sure that the buffers are never going to be shared + * because of the exclusive flag */ + if (output->node->remote || input->node->remote || !output->exclusive) alloc_flags |= PW_BUFFERS_FLAG_SHARED; if (output->node->driver) From c3d7373cf981129f84536a2a811a6cfa8080d0c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Thu, 19 Feb 2026 20:56:36 +0100 Subject: [PATCH 13/28] treewide: fix some `-Wdiscarded-qualifiers` Newer glibc versions have made certain `str*()` functions into macros that ensure that the const-ness of the argument is propagated to the return type. (cherry picked from commit e46bfe67b60458e444ee2495209144b49e97d2f1) --- pipewire-jack/src/pipewire-jack.c | 2 +- spa/plugins/bluez5/bluez5-dbus.c | 7 ++----- spa/plugins/support/logger.c | 4 ++-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index a7f4882ab..906fc96f1 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -5325,7 +5325,7 @@ int jack_set_freewheel(jack_client_t* client, int onoff) pw_thread_loop_lock(c->context.loop); str = pw_properties_get(c->props, PW_KEY_NODE_GROUP); if (str != NULL) { - char *p = strstr(str, ",pipewire.freewheel"); + const char *p = strstr(str, ",pipewire.freewheel"); if (p == NULL) p = strstr(str, "pipewire.freewheel"); if (p == NULL && onoff) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 21a5e53de..7dfe45911 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -720,14 +720,12 @@ static const char *bap_features_get_uuid(struct bap_features *feat, size_t i) /** Get feature name at \a i, or NULL if uuid doesn't match */ static const char *bap_features_get_name(struct bap_features *feat, size_t i, const char *uuid) { - char *pos; - if (i >= feat->dict.n_items) return NULL; if (!spa_streq(feat->dict.items[i].value, uuid)) return NULL; - pos = strchr(feat->dict.items[i].key, ':'); + const char *pos = strchr(feat->dict.items[i].key, ':'); if (!pos) return NULL; return pos + 1; @@ -1336,7 +1334,6 @@ static struct spa_bt_adapter *adapter_find(struct spa_bt_monitor *monitor, const static int parse_modalias(const char *modalias, uint16_t *source, uint16_t *vendor, uint16_t *product, uint16_t *version) { - char *pos; unsigned int src, i, j, k; if (spa_strstartswith(modalias, "bluetooth:")) @@ -1346,7 +1343,7 @@ static int parse_modalias(const char *modalias, uint16_t *source, uint16_t *vend else return -EINVAL; - pos = strchr(modalias, ':'); + const char *pos = strchr(modalias, ':'); if (pos == NULL) return -EINVAL; diff --git a/spa/plugins/support/logger.c b/spa/plugins/support/logger.c index c6e6ca4b8..6ea5f31b5 100644 --- a/spa/plugins/support/logger.c +++ b/spa/plugins/support/logger.c @@ -73,7 +73,7 @@ impl_log_logtv(void *object, char timestamp[18] = {0}; char topicstr[32] = {0}; char filename[64] = {0}; - char location[1000 + RESERVED_LENGTH], *p, *s; + char location[1000 + RESERVED_LENGTH], *p; static const char * const levels[] = { "-", "E", "W", "I", "D", "T", "*T*" }; const char *prefix = "", *suffix = ""; int size, len; @@ -118,7 +118,7 @@ impl_log_logtv(void *object, if (impl->line && line != 0) { - s = strrchr(file, '/'); + const char *s = strrchr(file, '/'); spa_scnprintf(filename, sizeof(filename), "[%16.16s:%5i %s()]", s ? s + 1 : file, line, func); } From 92f8e16f11a0922ebb5f9d3bbafca1f30ffffa65 Mon Sep 17 00:00:00 2001 From: Ripley Tom Date: Sat, 21 Feb 2026 20:19:04 +0100 Subject: [PATCH 14/28] meson.build: Add -Werror=discarded-qualifiers (cherry picked from commit ff04b47942809e910d07858d5bd9c937e5c48bba) --- meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/meson.build b/meson.build index a1f6e1880..4816834bb 100644 --- a/meson.build +++ b/meson.build @@ -116,6 +116,7 @@ cc_flags = common_flags + [ '-Werror=old-style-definition', '-Werror=missing-parameter-type', '-Werror=strict-prototypes', + '-Werror=discarded-qualifiers', ] add_project_arguments(cc.get_supported_arguments(cc_flags), language: 'c') add_project_arguments(cc_native.get_supported_arguments(cc_flags), From f26eb9501e20e5cb3446b57424cb6583651587a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Mon, 9 Mar 2026 16:22:42 +0100 Subject: [PATCH 15/28] docs/dma-buf: Document the correct device ID negotation key The correct key is PW_CAPABILITY_DEVICE_ID_NEGOTIATION which contains a number. --- doc/dox/internals/dma-buf.dox | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/dox/internals/dma-buf.dox b/doc/dox/internals/dma-buf.dox index 5042f285c..ec02b9da7 100644 --- a/doc/dox/internals/dma-buf.dox +++ b/doc/dox/internals/dma-buf.dox @@ -349,10 +349,10 @@ rectangles. For example params[n_params++] = spa_pod_builder_pop(&b, &f); ``` -After having received the first \ref SPA_PARAM_PeerCapability param, if it contained the \ref -PW_CAPABILITY_DEVICE_ID set to `true`, the full set of formats can be sent using \ref -pw_stream_update_params following by activating the stream using -`pw_stream_set_active(stream, true)`. +After having received the first \ref SPA_PARAM_PeerCapability param, if it contained the +\ref PW_CAPABILITY_DEVICE_ID_NEGOTIATION set to a supported API version number, the full +set of formats can be sent using \ref pw_stream_update_params following by activating the +stream usina supported API version numberstream_set_active(stream, true)`. Note that the first \ref SPA_PARAM_Format received may be the result of the initial format negotian with bare minimum parameters, and will be superseded by the result of the format From 3a62ea0217bfc08c1e8519e85b4f2501c60d389c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Mon, 9 Mar 2026 16:23:35 +0100 Subject: [PATCH 16/28] pipewire/capabilities: Update device ID negotation according to API docs It was was documenting a previous iteration of the protocol which used a boolean and a base64 encoded list. --- src/pipewire/capabilities.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pipewire/capabilities.h b/src/pipewire/capabilities.h index d3040b761..b8431ac85 100644 --- a/src/pipewire/capabilities.h +++ b/src/pipewire/capabilities.h @@ -21,11 +21,14 @@ extern "C" { * \{ */ -/**< Link capable of device ID negotiation. The value is either "true" or "false" */ +/**< Link capable of device ID negotiation. The value is to the version of the + * API specification. */ #define PW_CAPABILITY_DEVICE_ID_NEGOTIATION "pipewire.device-id-negotiation" /**< Link with device ID negotition capability supports negotiating with - * provided list of devices. The value consists of a JSON encoded string array - * of base64 encoded dev_t values. */ + * a specific set of devices. The value of API version 1 consists of a JSON + * object containing a single key "available-devices" that contain a list of + * hexadecimal encoded `dev_t` device IDs. + */ #define PW_CAPABILITY_DEVICE_IDS "pipewire.device-ids" #define PW_CAPABILITY_DEVICE_ID "pipewire.device-id" /**< Link capable of device Id negotation */ From 55f6c35e7801a16cdd789f40515c69ba6beec622 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 10 Mar 2026 14:25:07 +0100 Subject: [PATCH 17/28] client-node: avoid using invalid fd or mem in clear_data Don't close an -1 fd in clear_data. If we let the client allocate buffer, set our fd and data to invalid values. If the client decides to renegotiate before we get the buffer data we might otherwise try to clear the mem_id (default 0) or close the fd (also default 0). Fixes #5162 --- src/modules/module-client-node/client-node.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/modules/module-client-node/client-node.c b/src/modules/module-client-node/client-node.c index 0253591cf..9740e1c89 100644 --- a/src/modules/module-client-node/client-node.c +++ b/src/modules/module-client-node/client-node.c @@ -264,7 +264,8 @@ static void clear_data(struct impl *impl, struct spa_data *d) case SPA_DATA_DmaBuf: case SPA_DATA_SyncObj: pw_log_debug("%p: close fd:%d", impl, (int)d->fd); - close(d->fd); + if (d->fd != -1) + close(d->fd); break; } } @@ -864,8 +865,11 @@ do_port_use_buffers(struct impl *impl, memcpy(&b->datas[j], d, sizeof(struct spa_data)); - if (flags & SPA_NODE_BUFFERS_FLAG_ALLOC) + if (flags & SPA_NODE_BUFFERS_FLAG_ALLOC) { + b->datas[j].fd = -1; + b->datas[j].data = SPA_UINT32_TO_PTR(SPA_ID_INVALID); continue; + } switch (d->type) { case SPA_DATA_DmaBuf: From 17f423b8f6d467f60fcba2d67ad10e8e688f9912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Mon, 9 Mar 2026 21:40:23 +0100 Subject: [PATCH 18/28] spa: libcamera: source: `SPA_PARAM_Props` is write-only There are no readable `SPA_PARAM_Props` on the node, so mark it write-only. (cherry picked from commit 810617997b0374b63b501c91f960848b1770139e) --- spa/plugins/libcamera/libcamera-source.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index c23a6fb9d..fba3d8b0b 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -2163,7 +2163,7 @@ impl::impl(spa_log *log, spa_loop *data_loop, spa_system *system, &impl_node, this); params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); - params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); + params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_WRITE); params[NODE_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); params[NODE_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); From cb9b3861cefe366cb721e493a6f0c8f732cfbee1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 11 Mar 2026 12:36:39 +0100 Subject: [PATCH 19/28] audiomixer: only add the input port to mix_list Adding the output port is not a problem because there is never a buffer to consume and mix but it wastes cycles. --- spa/plugins/audiomixer/audiomixer.c | 2 +- spa/plugins/audiomixer/mixer-dsp.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spa/plugins/audiomixer/audiomixer.c b/spa/plugins/audiomixer/audiomixer.c index 99b0658a0..54549bce8 100644 --- a/spa/plugins/audiomixer/audiomixer.c +++ b/spa/plugins/audiomixer/audiomixer.c @@ -725,7 +725,7 @@ static int do_port_set_io(struct spa_loop *loop, bool async, uint32_t seq, port->io[0] = info->data; port->io[1] = info->data; } - if (!port->active) { + if (port->direction == SPA_DIRECTION_INPUT && !port->active) { spa_list_append(&info->impl->mix_list, &port->mix_link); port->active = true; } diff --git a/spa/plugins/audiomixer/mixer-dsp.c b/spa/plugins/audiomixer/mixer-dsp.c index 5bd4e1a1e..698426d21 100644 --- a/spa/plugins/audiomixer/mixer-dsp.c +++ b/spa/plugins/audiomixer/mixer-dsp.c @@ -718,7 +718,7 @@ static int do_port_set_io(struct spa_loop *loop, bool async, uint32_t seq, port->io[0] = info->data; port->io[1] = info->data; } - if (!port->active) { + if (port->direction == SPA_DIRECTION_INPUT && !port->active) { spa_list_append(&info->impl->mix_list, &port->mix_link); port->active = true; } From f4a6648aa5779d66425484963b3dd0d103bff9c0 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 11 Mar 2026 12:39:46 +0100 Subject: [PATCH 20/28] filter-graph: emit control change after loading Always do a control change to the instances when they are created. This is to make sure the internal state is synced to the control values. The sofa filter and biquads need this to correctly configure themselves after a suspend. Fixes #5152 --- spa/plugins/filter-graph/filter-graph.c | 1 + 1 file changed, 1 insertion(+) diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index 52609f2c6..7793e574f 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -1622,6 +1622,7 @@ static int impl_activate(void *object, const struct spa_dict *props) goto error; } } + node->control_changed = true; } /* then link ports */ From 45633303aab421ced53112a4b127ce3a5c4c15b4 Mon Sep 17 00:00:00 2001 From: Nedko Arnaudov Date: Wed, 11 Mar 2026 17:57:15 +0200 Subject: [PATCH 21/28] pipewire-jack: emit foreign port registration callbacks on jack_activate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The jack_activate loop was only queuing NOTIFY_TYPE_PORTREGISTRATION for the activating client's own ports. Ports belonging to other clients — including all WirePlumber-managed ports and MIDI ports — were silently skipped due to the o->port.port->client != c condition. This caused two observable bugs for clients using libjackserver (e.g. jackdbus): - JackPortRegistrationCallback was not fired for any pre-existing foreign ports at activate time, leaving the patchbay empty unless the session manager happened to start after the client. - JACK MIDI ports were never announced via callback, even though they are correctly returned by jack_get_ports(). The graph_order_callback fallback (used by jackdbus for initial port enumeration) is also ineffective here because pipewire-jack only fires it on connection events, not on activate. Fix by iterating all non-removed foreign ports in the object list and queuing registration callbacks for those whose node is active, matching the semantics already implemented in node_info() for ports of nodes that transition to running state after activate. --- pipewire-jack/src/pipewire-jack.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index 906fc96f1..56191b93c 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -4886,9 +4886,15 @@ int jack_activate (jack_client_t *client) c->activation->pending_new_pos = true; c->activation->pending_sync = true; + /* emits all foreign active ports, skips own (already announced via jack_port_register) */ spa_list_for_each(o, &c->context.objects, link) { - if (o->type != INTERFACE_Port || o->port.port == NULL || - o->port.port->client != c || !o->port.port->valid) + if (o->type != INTERFACE_Port || o->removed) + continue; + /* own ports are handled by jack_port_register */ + if (o->port.port != NULL && o->port.port->client == c) + continue; + /* only announce ports whose node is active */ + if (o->port.node != NULL && !node_is_active(c, o->port.node)) continue; o->registered = 0; queue_notify(c, NOTIFY_TYPE_PORTREGISTRATION, o, 1, NULL); From b00e8f8bb229383b36d047b4a98dc525126d9c42 Mon Sep 17 00:00:00 2001 From: Nedko Arnaudov Date: Wed, 11 Mar 2026 19:13:15 +0200 Subject: [PATCH 22/28] pipewire-jack: fix jack_port_type_id() to return jack1/jack2 compatible values pipewire-jack defines TYPE_ID_VIDEO=1 between audio and MIDI, shifting TYPE_ID_MIDI to 2. This caused jack_port_type_id() to return 2 for MIDI ports, breaking compatibility with jack1/jack2 which return 1. The jack_port_type_id() return value is part of the public JACK API and consumers such as jackdbus rely on the conventional values established by jack1/jack2: 0 for audio, 1 for MIDI. Map internal TYPE_ID_* values to their jack1/jack2 compatible equivalents before returning. All MIDI variants (MIDI, OSC, UMP) map to 1. Video has no jack1/jack2 equivalent so maps to 3, beyond the conventional range. --- pipewire-jack/src/pipewire-jack.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index 56191b93c..d34d96bd6 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -6018,7 +6018,16 @@ jack_port_type_id_t jack_port_type_id (const jack_port_t *port) return_val_if_fail(o != NULL, 0); if (o->type != INTERFACE_Port) return TYPE_ID_OTHER; - return o->port.type_id; + + /* map internal type IDs to jack1/jack2 compatible public values */ + switch (o->port.type_id) { + case TYPE_ID_AUDIO: return 0; + case TYPE_ID_MIDI: + case TYPE_ID_OSC: + case TYPE_ID_UMP: return 1; /* all MIDI variants map to 1 */ + case TYPE_ID_VIDEO: return 3; /* video maps to 3 */ + default: return o->port.type_id; + } } SPA_EXPORT From cab633b4f8968aca5d0cab95c80ad97e8ceae38e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 12 Mar 2026 15:01:25 +0100 Subject: [PATCH 23/28] Revert "pipewire-jack: emit foreign port registration callbacks on jack_activate" This reverts commit 45633303aab421ced53112a4b127ce3a5c4c15b4. --- pipewire-jack/src/pipewire-jack.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index d34d96bd6..94482dbc0 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -4886,15 +4886,9 @@ int jack_activate (jack_client_t *client) c->activation->pending_new_pos = true; c->activation->pending_sync = true; - /* emits all foreign active ports, skips own (already announced via jack_port_register) */ spa_list_for_each(o, &c->context.objects, link) { - if (o->type != INTERFACE_Port || o->removed) - continue; - /* own ports are handled by jack_port_register */ - if (o->port.port != NULL && o->port.port->client == c) - continue; - /* only announce ports whose node is active */ - if (o->port.node != NULL && !node_is_active(c, o->port.node)) + if (o->type != INTERFACE_Port || o->port.port == NULL || + o->port.port->client != c || !o->port.port->valid) continue; o->registered = 0; queue_notify(c, NOTIFY_TYPE_PORTREGISTRATION, o, 1, NULL); From aa55e4327540050fa8cd500333a38b1a0c7c62b0 Mon Sep 17 00:00:00 2001 From: Nedko Arnaudov Date: Wed, 11 Mar 2026 17:57:15 +0200 Subject: [PATCH 24/28] pipewire-jack: emit foreign port registration callbacks on jack_activate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The jack_activate loop was only queuing NOTIFY_TYPE_PORTREGISTRATION for the activating client's own ports. Ports belonging to other clients — including all WirePlumber-managed ports and MIDI ports — were silently skipped due to the o->port.port->client != c condition. This caused two observable bugs for clients using libjackserver (e.g. jackdbus): - JackPortRegistrationCallback was not fired for any pre-existing foreign ports at activate time, leaving the patchbay empty unless the session manager happened to start after the client. - JACK MIDI ports were never announced via callback, even though they are correctly returned by jack_get_ports(). The graph_order_callback fallback (used by jackdbus for initial port enumeration) is also ineffective here because pipewire-jack only fires it on connection events, not on activate. Fix by iterating all non-removed foreign ports in the object list and queuing registration callbacks for those whose node is active, matching the semantics already implemented in node_info() for ports of nodes that transition to running state after activate. The change is libjackserver.so only. libjack.so behaviour is left unmodifed, as carla is showing ports of each client twice. --- pipewire-jack/src/meson.build | 2 +- pipewire-jack/src/pipewire-jack.c | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pipewire-jack/src/meson.build b/pipewire-jack/src/meson.build index 0630d96a8..639405bb9 100644 --- a/pipewire-jack/src/meson.build +++ b/pipewire-jack/src/meson.build @@ -55,7 +55,7 @@ pipewire_jackserver = shared_library('jackserver', pipewire_jackserver_sources, soversion : soversion, version : libjackversion, - c_args : pipewire_jack_c_args, + c_args : pipewire_jack_c_args + '-DLIBJACKSERVER', include_directories : [configinc, jack_inc], dependencies : [pipewire_dep, mathlib], install : true, diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index 94482dbc0..c7017e936 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -4887,9 +4887,21 @@ int jack_activate (jack_client_t *client) c->activation->pending_sync = true; spa_list_for_each(o, &c->context.objects, link) { +#if !defined(LIBJACKSERVER) if (o->type != INTERFACE_Port || o->port.port == NULL || o->port.port->client != c || !o->port.port->valid) continue; +#else + /* emits all foreign active ports, skips own (already announced via jack_port_register) */ + if (o->type != INTERFACE_Port || o->removed) + continue; + /* own ports are handled by jack_port_register */ + if (o->port.port != NULL && o->port.port->client == c) + continue; + /* only announce ports whose node is active */ + if (o->port.node != NULL && !node_is_active(c, o->port.node)) + continue; +#endif o->registered = 0; queue_notify(c, NOTIFY_TYPE_PORTREGISTRATION, o, 1, NULL); } From 53b870934be40294776f41c5056f451b0bc85e92 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 13 Mar 2026 13:25:57 +0100 Subject: [PATCH 25/28] doc: add 1.6 link --- doc/DoxygenLayout.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/DoxygenLayout.xml b/doc/DoxygenLayout.xml index 300c8400e..10da55da5 100644 --- a/doc/DoxygenLayout.xml +++ b/doc/DoxygenLayout.xml @@ -44,6 +44,7 @@ + From 31874b376451609f62c03a0606f645330ee21850 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 16 Mar 2026 09:59:14 +0100 Subject: [PATCH 26/28] filter-graph: only use min/max when defined in LADSPA Fixes #5170 --- spa/plugins/filter-graph/filter-graph.c | 4 ++-- spa/plugins/filter-graph/plugin_ladspa.c | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index 7793e574f..05ba7b3fb 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -1035,8 +1035,8 @@ static struct descriptor *descriptor_load(struct impl *impl, const char *type, } } else if (SPA_FGA_IS_PORT_CONTROL(fp->flags)) { if (SPA_FGA_IS_PORT_INPUT(fp->flags)) { - spa_log_info(impl->log, "using port %lu ('%s') as control %d", p, - fp->name, desc->n_control); + spa_log_info(impl->log, "using port %lu ('%s') as control %d %f/%f/%f", p, + fp->name, desc->n_control, fp->def, fp->min, fp->max); desc->control[desc->n_control++] = p; } else if (SPA_FGA_IS_PORT_OUTPUT(fp->flags)) { diff --git a/spa/plugins/filter-graph/plugin_ladspa.c b/spa/plugins/filter-graph/plugin_ladspa.c index d5c8ef488..1aebafe35 100644 --- a/spa/plugins/filter-graph/plugin_ladspa.c +++ b/spa/plugins/filter-graph/plugin_ladspa.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -113,8 +114,14 @@ static void ladspa_port_update_ranges(struct descriptor *dd, struct spa_fga_port LADSPA_PortRangeHintDescriptor hint = d->PortRangeHints[p].HintDescriptor; LADSPA_Data lower, upper; - lower = d->PortRangeHints[p].LowerBound; - upper = d->PortRangeHints[p].UpperBound; + if (hint & LADSPA_HINT_BOUNDED_BELOW) + lower = d->PortRangeHints[p].LowerBound; + else + lower = -FLT_MAX; + if (hint & LADSPA_HINT_BOUNDED_ABOVE) + upper = d->PortRangeHints[p].UpperBound; + else + upper = FLT_MAX; port->hint = 0; if (hint & LADSPA_HINT_TOGGLED) From 54d0d4e55a58471076774a4c2f59b38120e1ca07 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 16 Mar 2026 12:19:57 +0100 Subject: [PATCH 27/28] filter-graph: fix up def/min/max values for lv2 lv2 filters can return NAN for the min/max and default values when not specified. Handle them so that we don't end up clamping NAN numbers. Fixes #5166 --- spa/plugins/filter-graph/plugin_lv2.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/spa/plugins/filter-graph/plugin_lv2.c b/spa/plugins/filter-graph/plugin_lv2.c index 712b728e2..b2d3fc6cc 100644 --- a/spa/plugins/filter-graph/plugin_lv2.c +++ b/spa/plugins/filter-graph/plugin_lv2.c @@ -560,6 +560,17 @@ static const struct spa_fga_descriptor *lv2_plugin_make_desc(void *plugin, const fp->min = mins[i]; fp->max = maxes[i]; fp->def = controls[i]; + + if (isnan(fp->min)) + fp->min = -FLT_MAX; + if (isnan(fp->max)) + fp->max = FLT_MAX; + if (isnan(fp->def)) + fp->def = 0.0f; + if (fp->max <= fp->min) + fp->max = FLT_MAX; + if (fp->def <= fp->min) + fp->min = -FLT_MAX; } return &desc->desc; } From 95da54a482b68475958bbc3fa572a9c20df0df74 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 16 Mar 2026 12:47:06 +0100 Subject: [PATCH 28/28] 1.6.2 --- NEWS | 38 ++++++++++++++++++++++++++++++++++++-- meson.build | 2 +- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 4e97bb525..6e8b7f6f5 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,39 @@ +# PipeWire 1.6.2 (2026-03-16) + +This is a bugfix release that is API and ABI compatible with the previous +1.6.x releases. + +## Highlights + - Fix a potential crash when the wrong memory was freed. + - Fix a optimization with shared memory over some links that could + cause errors later on. + - Fix SOFA filter and default control input in LADSPA and LV2. + - Some other small fixes and improvements. + + +## PipeWire + - Remove an optimization to skip share mem in links, it causes problems + later on. (#5159) + +## Modules + - Don't try to free invalid memory or close invalid fds when the client + aborted before allocating buffer memory. (#5162) + +## SPA + - support ACP_IGNORE_DB in udev. + - Use 0x as a prefix for hex values. + - Mark Props as write-only in libcamera. + - Small optimization in the audio mixer. + - Fix initialization of control properties for SOFA and biquads in the + filter-graph. (#5152) + - Fix min/max default values for LADSPA and LV2. + +## JACK + - Fix jack_port_type_id(). Return values that are compatible with JACK1/2. + + +Older versions: + # PipeWire 1.6.1 (2026-03-09) This is a bugfix release that is API and ABI compatible with the previous @@ -28,8 +64,6 @@ This is a bugfix release that is API and ABI compatible with the previous ## Tools - Fix playback of encoded formats. (#5155) -Older versions: - # PipeWire 1.6.0 (2026-02-19) This is the 1.6 release that is API and ABI compatible with previous diff --git a/meson.build b/meson.build index 4816834bb..536502a68 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('pipewire', ['c' ], - version : '1.6.1', + version : '1.6.2', license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ], meson_version : '>= 0.61.1', default_options : [ 'warning_level=3',