diff --git a/NEWS b/NEWS
index b730ae372b1f955d4bf3fec9dc552cf16b491f1a..e6ad9e4083876695720fb77a1ff417cf7f98f893 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,72 @@
+# PipeWire 1.0.3 (2024-02-02)
+
+This is a quick bugfix release that is API and ABI compatible with previous
+1.0.x releases.
+
+## Highlights
+  - Fix ALSA version check. This should allow the alsa plugin to work again.
+  - Some small fixes and improvements.
+
+## PipeWire
+  - Escape @DEFAULT_SINK@ in the conf files.
+
+## Modules
+  - Improve logging in module-pipe-tunnel.
+
+## SPA
+  - Always recheck rate matching in ALSA when moving drivers. This fixes a
+    potential issue where the adaptive resampler would not be activated in
+    some cases.
+
+## ALSA
+  - Fix version check. This should allow the alsa plugin to work again
+    with version 1.0.2.
+
+Older versions:
+
+
+# PipeWire 1.0.2 (2024-01-31)
+
+This is a bugfix release that is API and ABI compatible with previous
+1.0.x releases.
+
+## Highlights
+  - Fix v4l2 enumeration with filter. This should fix negotiation in some
+    GStreamer pipelines with capsfilter. Also probe for EXPBUF support
+    before using it.
+  - Fix max-latency property and Buffer param when dealing with small
+    ALSA device buffers. This should fix stuttering with some AMD
+    based soundcards.
+  - More small cleanups an improvements.
+
+## Modules
+  - Improve netjack2 channel positions.
+  - Improve RAOP module state after suspend/resume. (#3778)
+  - Avoid crash in some LV2 plugins by configuring the Atom ports. (#3815)
+
+## SPA
+  - Bump libcamera requirements to 0.2.0.
+  - Try to avoid unaligned load exceptions. (#3790)
+  - Fix v4l2 enumeration with filter. (#1793)
+  - Fix max-latency property and Buffer param when dealing with small
+    ALSA device buffers. This should fix stuttering with some AMD
+    based soundcards. (#3744,#3622)
+  - Add a resync.ms option to node.driver to make it possible to resync
+    fast to clock jumps.
+  - Probe for EXPBUF support in v4l2 before using it. (#3821)
+
+## pulse-server
+  - Also emit change events when the port list change.
+
+## Bluetooth
+  - Log a more verbose explanation when other soundservers seem to be
+    interfering with bluetooth.
+  - Add quirks for Rockbox Brick. (#3786)
+  - Add quirks for SoundCore mini2. (#2927)
+
+## JACK
+  - Improve check for the running state of clients. (#3794)
+
 # PipeWire 1.0.1 (2024-01-11)
 
 This is a bugfix release that is API and ABI compatible with previous
@@ -49,9 +118,6 @@ This is a bugfix release that is API and ABI compatible with previous
   - Improve error handling while connecting.
   - Fix dts_offset.
 
-Older versions:
-
-
 # PipeWire 1.0.0 (2023-11-26)
 
 The PipeWire project is immensely proud to announce the 1.0 release
diff --git a/debian/changelog b/debian/changelog
index 00652c5cca2bebe7f6632a35559f762d4afb86ab..91b38d24708bed40055835d247399ee65d07d650 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,44 @@
+pipewire (1.0.3-1~bpo12+1) bookworm-backports; urgency=medium
+
+  * Rebuild for bookworm-backports.
+  * Disable ROC, not available in Bookworm
+  * Install udev rules in /lib/udev/rules.d
+  * Add patches to keep compatibility with libcamera < 0.2.0
+
+ -- Dylan Aïssi <daissi@debian.org>  Thu, 15 Feb 2024 11:51:26 +0100
+
+pipewire (1.0.3-1) unstable; urgency=medium
+
+  * New upstream release
+
+ -- Dylan Aïssi <daissi@debian.org>  Fri, 02 Feb 2024 16:06:47 +0100
+
+pipewire (1.0.2-1) unstable; urgency=medium
+
+  [ Dylan Aïssi ]
+  * New upstream release
+  * Drop patches included in upstream release:
+      - libcamera0.2 compatibility
+
+  [ Jeremy Bícha ]
+  * Cherry-pick patches from Sergio Costas for snap permissions support.
+    CVE-2022-4964 (LP: #1995707)
+  * Add apparmor and snapd-glib to Build-Depends for snap feature
+  * Cherry-pick a patch to fix LP: #2051504
+
+ -- Dylan Aïssi <daissi@debian.org>  Thu, 01 Feb 2024 13:01:35 +0100
+
+pipewire (1.0.1-2) unstable; urgency=medium
+
+  * Switch Build-Deps from systemd to systemd-dev (Closes: #1060598)
+  * Use pkgconf instead of the transitional pkg-config
+  * Bump minimum meson to 0.61.1
+  * Drop 'dh_missing --fail-missing', default since debhelper compat 13
+  * Bump minimum libcamera to 0.2.0
+  * Cherry-pick upstream patches for libcamera0.2 compatibility
+
+ -- Dylan Aïssi <daissi@debian.org>  Mon, 22 Jan 2024 17:24:44 +0100
+
 pipewire (1.0.1-1~bpo12+1) bookworm-backports; urgency=medium
 
   * Rebuild for bookworm-backports.
diff --git a/debian/control b/debian/control
index 54564a09e006e3668535dbebad0bad35f9e41898..a95b2cae74e7fcf3dc0451bc905690e391f6d567 100644
--- a/debian/control
+++ b/debian/control
@@ -7,6 +7,7 @@ Uploaders: Jeremy Bicha <jbicha@debian.org>,
 Build-Depends: debhelper-compat (= 13),
                doxygen <!nodoc>,
                graphviz <!nodoc>,
+               libapparmor-dev [linux-any],
                libasound2-dev,
                libavahi-client-dev,
                libbluetooth-dev [linux-any],
@@ -31,6 +32,7 @@ Build-Depends: debhelper-compat (= 13),
 #              libroc-dev (>= 0.3.0+dfsg-3),
                libsbc-dev,
                libsdl2-dev <!noinsttest>,
+               libsnapd-glib-dev [linux-any],
                libsndfile1-dev,
                libssl-dev,
                libsystemd-dev [linux-any],
@@ -39,11 +41,11 @@ Build-Depends: debhelper-compat (= 13),
                libv4l-dev,
                libwebrtc-audio-processing-dev,
                libxfixes-dev (>= 1:6.0.0),
-               meson (>= 0.59.0),
+               meson (>= 0.61.1),
                modemmanager-dev,
-               pkg-config,
+               pkgconf,
                python3-docutils,
-               systemd [linux-any]
+               systemd-dev
 Build-Conflicts: libfdk-aac-dev
 Standards-Version: 4.6.2
 Vcs-Browser: https://salsa.debian.org/utopia-team/pipewire
diff --git a/debian/gbp.conf b/debian/gbp.conf
index d8690834d056cfc29241b181c58761de74fb7a91..24390e79c5c51dc4a6c48a3517e44e92cb601f9c 100644
--- a/debian/gbp.conf
+++ b/debian/gbp.conf
@@ -1,7 +1,7 @@
 [DEFAULT]
 pristine-tar = True
 debian-branch = debian/bookworm-backports
-upstream-branch = upstream/1.0
+upstream-branch = upstream/1.0.x
 upstream-vcs-tag = %(version)s
 
 [buildpackage]
diff --git a/debian/patches/Revert-libcamera0.2-01cb3fa8.patch b/debian/patches/Revert-libcamera0.2-01cb3fa8.patch
new file mode 100644
index 0000000000000000000000000000000000000000..75649a7fc297787c0346ec3d5cd8609414aa067e
--- /dev/null
+++ b/debian/patches/Revert-libcamera0.2-01cb3fa8.patch
@@ -0,0 +1,48 @@
+From: Dylan Aïssi <dylan.aissi@collabora.com>
+Date: Fri, 2 Feb 2024 16:32:57 +0100
+Subject: [PATCH] Revert "spa: libcamera: bump minimum supported version to
+ 0.2.0"
+
+This reverts commit 01cb3fa862d36adfded8f02540660b43b9c1584c.
+libcamera 0.2.0 is not available in Bookworm, so we need to revert this patch
+to keep compatibility with the version in Bookworm.
+---
+ spa/meson.build                            | 3 ++-
+ spa/plugins/libcamera/libcamera-device.cpp | 2 ++
+ 2 files changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/spa/meson.build b/spa/meson.build
+index db0a84425..0ee750d6e 100644
+--- a/spa/meson.build
++++ b/spa/meson.build
+@@ -96,8 +96,9 @@ if get_option('spa-plugins').allowed()
+   endif
+   summary({'Vulkan': have_vulkan}, bool_yn: true, section: 'Misc dependencies')
+ 
+-  libcamera_dep = dependency('libcamera', version: '>= 0.2.0', required: get_option('libcamera'))
++  libcamera_dep = dependency('libcamera', required: get_option('libcamera'))
+   summary({'libcamera': libcamera_dep.found()}, bool_yn: true, section: 'Backend')
++  cdata.set('HAVE_LIBCAMERA_SYSTEM_DEVICES', libcamera_dep.version().version_compare('>= 0.1.0'))
+ 
+   compress_offload_option = get_option('compress-offload')
+   summary({'Compress-Offload': compress_offload_option.allowed()}, bool_yn: true, section: 'Backend')
+diff --git a/spa/plugins/libcamera/libcamera-device.cpp b/spa/plugins/libcamera/libcamera-device.cpp
+index b25a4eb72..0abf2f619 100644
+--- a/spa/plugins/libcamera/libcamera-device.cpp
++++ b/spa/plugins/libcamera/libcamera-device.cpp
+@@ -61,10 +61,12 @@ struct impl {
+ static const libcamera::Span<const int64_t> cameraDevice(
+ 			const Camera *camera)
+ {
++#ifdef HAVE_LIBCAMERA_SYSTEM_DEVICES
+ 	const ControlList &props = camera->properties();
+ 
+ 	if (auto devices = props.get(properties::SystemDevices))
+ 		return devices.value();
++#endif
+ 
+ 	return {};
+ }
+-- 
+2.30.2
+
diff --git a/debian/patches/Revert-libcamera0.2-fd33d2d3.patch b/debian/patches/Revert-libcamera0.2-fd33d2d3.patch
new file mode 100644
index 0000000000000000000000000000000000000000..a9b6c9a22ae73bea5d70d36bf064aa6a5bd46cd9
--- /dev/null
+++ b/debian/patches/Revert-libcamera0.2-fd33d2d3.patch
@@ -0,0 +1,72 @@
+From: Dylan Aïssi <dylan.aissi@collabora.com>
+Date: Fri, 2 Feb 2024 16:32:49 +0100
+Subject: [PATCH] Revert "spa: libcamera: use
+ `CameraConfiguration::orientation`"
+
+This reverts commit fd33d2d3bb6333c7d6e74cbaa806bff2d908f589.
+libcamera 0.2.0 is not available in Bookworm, so we need to revert this patch
+to keep compatibility with the version in Bookworm.
+---
+ spa/plugins/libcamera/libcamera-utils.cpp | 36 ++++++++++++-----------
+ 1 file changed, 19 insertions(+), 17 deletions(-)
+
+diff --git a/spa/plugins/libcamera/libcamera-utils.cpp b/spa/plugins/libcamera/libcamera-utils.cpp
+index c197248d3..2b1aea5a7 100644
+--- a/spa/plugins/libcamera/libcamera-utils.cpp
++++ b/spa/plugins/libcamera/libcamera-utils.cpp
+@@ -716,23 +716,25 @@ static int spa_libcamera_use_buffers(struct impl *impl, struct port *port,
+ }
+ 
+ static const struct {
+-	Orientation libcamera_orientation; /* clockwise rotation then horizontal mirroring */
+-	uint32_t spa_transform_value; /* horizontal mirroring then counter-clockwise rotation */
+-} orientation_map[] = {
+-	{ Orientation::Rotate0, SPA_META_TRANSFORMATION_None },
+-	{ Orientation::Rotate0Mirror, SPA_META_TRANSFORMATION_Flipped },
+-	{ Orientation::Rotate90, SPA_META_TRANSFORMATION_270 },
+-	{ Orientation::Rotate90Mirror, SPA_META_TRANSFORMATION_Flipped90 },
+-	{ Orientation::Rotate180, SPA_META_TRANSFORMATION_180 },
+-	{ Orientation::Rotate180Mirror, SPA_META_TRANSFORMATION_Flipped180 },
+-	{ Orientation::Rotate270, SPA_META_TRANSFORMATION_90 },
+-	{ Orientation::Rotate270Mirror, SPA_META_TRANSFORMATION_Flipped270 },
++	Transform libcamera_transform;
++	uint32_t spa_transform_value;
++} transform_map[] = {
++	{ Transform::Identity, SPA_META_TRANSFORMATION_None },
++	{ Transform::Rot0, SPA_META_TRANSFORMATION_None },
++	{ Transform::HFlip, SPA_META_TRANSFORMATION_Flipped },
++	{ Transform::VFlip, SPA_META_TRANSFORMATION_Flipped180 },
++	{ Transform::HVFlip, SPA_META_TRANSFORMATION_180 },
++	{ Transform::Rot180, SPA_META_TRANSFORMATION_180 },
++	{ Transform::Transpose, SPA_META_TRANSFORMATION_Flipped90 },
++	{ Transform::Rot90, SPA_META_TRANSFORMATION_90 },
++	{ Transform::Rot270, SPA_META_TRANSFORMATION_270 },
++	{ Transform::Rot180Transpose, SPA_META_TRANSFORMATION_Flipped270 },
+ };
+ 
+-static uint32_t libcamera_orientation_to_spa_transform_value(Orientation orientation)
++static uint32_t libcamera_transform_to_spa_transform_value(Transform transform)
+ {
+-	for (const auto& t : orientation_map) {
+-		if (t.libcamera_orientation == orientation)
++	for (const auto& t : transform_map) {
++		if (t.libcamera_transform == transform)
+ 			return t.spa_transform_value;
+ 	}
+ 	return SPA_META_TRANSFORMATION_None;
+@@ -786,9 +788,9 @@ mmap_init(struct impl *impl, struct port *port,
+ 			buffers[i], SPA_META_VideoTransform, sizeof(*b->videotransform));
+ 		if (b->videotransform) {
+ 			b->videotransform->transform =
+-				libcamera_orientation_to_spa_transform_value(impl->config->orientation);
+-			spa_log_debug(impl->log, "Setting videotransform for buffer %u to %u",
+-				i, b->videotransform->transform);
++				libcamera_transform_to_spa_transform_value(impl->config->transform);
++			spa_log_debug(impl->log, "Setting videotransform for buffer %d to %u (from %s)",
++				i, b->videotransform->transform, transformToString(impl->config->transform));
+ 
+ 		}
+ 
+-- 
+2.30.2
+
diff --git a/debian/patches/series b/debian/patches/series
index b015341dcc6c7b109a535f04835fd39928afaf71..3fe13612576f315a24a49db4d4f1ec62467c69ba 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1,3 +1,24 @@
 Don-t-automatically-start-pipewire-for-root-logins.patch
 Fix_services.patch
-# Upstream patch for 0.3.8X
+
+# Upstream patches for snap permissions
+snap/pipewire-pulse-add-snap-permissions-support.patch
+snap/Add-missing-files.patch
+snap/Apply-1-suggestion-s-to-1-file-s.patch
+snap/Apply-1-suggestion-s-to-1-file-s-1.patch
+snap/Use-assert-to-check-client-is-not-NULL.patch
+snap/Apply-1-suggestion-s-to-1-file-s-2.patch
+snap/fix-possible-leak.patch
+snap/Better-error-logging-if-getting-connections-fails.patch
+snap/Move-variable-definition-inside-block.patch
+snap/Move-context-variable-definition-inside-block.patch
+snap/Move-add_permission-definition-inside-block.patch
+snap/Apply-1-suggestion-s-to-1-file-s-3.patch
+snap/Fix-spacing-when-calling-functions.patch
+snap/Better-meson_options-description.patch
+snap/Replace-spaces-with-tabs.patch
+snap/snap-policy-Manage-ENOPROTOOPT-error-in-aa_getpeercon.patch
+
+# Patches to keep compatibility with libcamera < 0.2.0
+Revert-libcamera0.2-01cb3fa8.patch
+Revert-libcamera0.2-fd33d2d3.patch
diff --git a/debian/patches/snap/Add-missing-files.patch b/debian/patches/snap/Add-missing-files.patch
new file mode 100644
index 0000000000000000000000000000000000000000..f0d7ba7cf41120987c00002b4c262a7476b769a4
--- /dev/null
+++ b/debian/patches/snap/Add-missing-files.patch
@@ -0,0 +1,242 @@
+From: Sergio Costas Rodriguez <sergio.costas@canonical.com>
+Date: Wed, 22 Nov 2023 11:48:14 +0100
+Subject: Add missing files
+
+Accidentally, I forgot to add snap-policy.* files.
+
+(cherry picked from commit 5e20a2d5704586f89f48d7575e63f9dac621b89f)
+Origin: upstream, after 1.0.1
+---
+ src/modules/module-protocol-pulse/snap-policy.c | 193 ++++++++++++++++++++++++
+ src/modules/module-protocol-pulse/snap-policy.h |  22 +++
+ 2 files changed, 215 insertions(+)
+ create mode 100644 src/modules/module-protocol-pulse/snap-policy.c
+ create mode 100644 src/modules/module-protocol-pulse/snap-policy.h
+
+diff --git a/src/modules/module-protocol-pulse/snap-policy.c b/src/modules/module-protocol-pulse/snap-policy.c
+new file mode 100644
+index 0000000..70bf984
+--- /dev/null
++++ b/src/modules/module-protocol-pulse/snap-policy.c
+@@ -0,0 +1,193 @@
++/* PipeWire */
++/* SPDX-FileCopyrightText: Copyright © 2022 Canonical Ltd. */
++/* SPDX-License-Identifier: MIT */
++
++#ifdef HAVE_CONFIG_H
++#include <config.h>
++#endif
++
++#include <glib.h>
++#include <snapd-glib/snapd-glib.h>
++#include <pipewire/pipewire.h>
++#include <sys/stat.h>
++#include <fcntl.h>
++#include "client.h"
++#include <sys/apparmor.h>
++#include <errno.h>
++#include "snap-policy.h"
++#include <fcntl.h>
++
++#define SNAP_LABEL_PREFIX      "snap."
++
++static gboolean check_is_same_snap(gchar *snap1, gchar *snap2) {
++    // Checks if two apparmor labels belong to the same snap
++    g_auto(GStrv) strings1 = NULL;
++    g_auto(GStrv) strings2 = NULL;
++
++    if (!g_str_has_prefix(snap1, SNAP_LABEL_PREFIX)) {
++        return FALSE;
++    }
++    if (!g_str_has_prefix(snap2, SNAP_LABEL_PREFIX)) {
++        return FALSE;
++    }
++    strings1 = g_strsplit(snap1, ".", 3);
++    strings2 = g_strsplit(snap2, ".", 3);
++
++    if (g_str_equal(strings1[1], strings2[1]) && (strings1[1] != NULL)) {
++        return TRUE;
++    }
++    return FALSE;
++}
++
++pw_sandbox_access_t pw_snap_get_audio_permissions(struct client *client, int fd, char **app_id)
++{
++    g_autofree gchar* aa_label = NULL;
++    gchar* snap_id = NULL;
++    gchar* snap_confinement = NULL;
++    gchar *separator = NULL;
++    g_autofree gchar *aacon = NULL;
++    gchar *aamode = NULL;
++    const char *context = NULL;
++    g_autoptr(SnapdClient) snapdclient = NULL;
++    g_autoptr(GPtrArray) plugs = NULL;
++    gboolean retv;
++    pw_sandbox_access_t permissions = PW_SANDBOX_ACCESS_NONE;
++    pw_sandbox_access_t add_permission = PW_SANDBOX_ACCESS_NONE;
++    SnapdPlug **plug = NULL;
++    GPtrArray *slots = NULL;
++    SnapdSlotRef **slot = NULL;
++    GError *error = NULL;
++    int exit_code;
++
++    *app_id = g_strdup("unknown");
++    if (client == NULL) {
++        pw_log_warn("Called snap_get_audio_permissions with NULL parameter.");
++        return PW_SANDBOX_ACCESS_NONE;
++    }
++
++    if (aa_getpeercon(fd, &aa_label, &snap_confinement) == -1) {
++        if (errno == EINVAL) {
++            // if apparmor isn't enabled, we can safely assume that there are no SNAPs in the system
++            return PW_SANDBOX_ACCESS_NOT_A_SANDBOX;
++        }
++        pw_log_warn("snap_get_audio_permissions: failed to get the AppArmor info.");
++        return PW_SANDBOX_ACCESS_NONE;
++    }
++    if (!g_str_has_prefix(aa_label, SNAP_LABEL_PREFIX)) {
++        // not a SNAP.
++        pw_log_info("snap_get_audio_permissions: not an snap.");
++        return PW_SANDBOX_ACCESS_NOT_A_SANDBOX;
++    }
++
++    snap_id = g_strdup(aa_label + strlen(SNAP_LABEL_PREFIX));
++    separator = strchr(snap_id, '.');
++    if (separator == NULL) {
++        pw_log_info("snap_get_audio_permissions: aa_label has only one dot; not a valid ID.");
++        return PW_SANDBOX_ACCESS_NONE;
++    }
++    *separator = 0;
++    g_free(*app_id);
++    *app_id = snap_id;
++
++    // it's a "classic" or a "devmode" confinement snap, so we give it full access
++    if (g_str_equal (snap_confinement, "complain")) {
++        return PW_SANDBOX_ACCESS_ALL;
++    }
++
++    snapdclient = snapd_client_new();
++    if (snapdclient == NULL) {
++        pw_log_warn("snap_get_audio_permissions: error creating SnapdClient object.");
++        return PW_SANDBOX_ACCESS_NONE;
++    }
++
++    if (aa_getcon(&aacon, &aamode) == -1) {
++        pw_log_warn("snap_get_audio_permissions: error checking if pipewire-pulse is inside a snap.");
++        return PW_SANDBOX_ACCESS_NONE; // failed to get access to apparmor
++    }
++
++    // If pipewire-pulse is inside a snap, use snapctl API
++    if (g_str_has_prefix(aacon, SNAP_LABEL_PREFIX)) {
++        // If the snap wanting to get access is the same that contains pipewire,
++        // give to it full access.
++        if (check_is_same_snap(aacon, aa_label))
++            return PW_SANDBOX_ACCESS_ALL;
++        snapd_client_set_socket_path (snapdclient, "/run/snapd-snap.socket");
++
++        /* Take context from the environment if available */
++        context = g_getenv ("SNAP_COOKIE");
++        if (!context)
++            context = "";
++
++        char *snapctl_command[] = { "is-connected", "--apparmor-label", aa_label, "pulseaudio", NULL };
++        if (!snapd_client_run_snapctl2_sync (snapdclient, context, (char **) snapctl_command, NULL, NULL, &exit_code, NULL, &error)) {
++            pw_log_warn("snap_get_audio_permissions: error summoning snapctl2 for pulseaudio interface: %s", error->message);
++            return PW_SANDBOX_ACCESS_NONE;
++        }
++        if (exit_code != 1) {
++            // 0  = Connected
++            // 10 = Classic environment
++            // 11 = Not a snap
++            return PW_SANDBOX_ACCESS_ALL;
++        }
++        char *snapctl_command2[] = { "is-connected", "--apparmor-label", aa_label, "audio-record", NULL };
++        if (!snapd_client_run_snapctl2_sync (snapdclient, context, (char **) snapctl_command2, NULL, NULL, &exit_code, NULL, &error)) {
++            pw_log_warn("snap_get_audio_permissions: error summoning snapctl2 for audio-record interface: %s", error->message);
++            return PW_SANDBOX_ACCESS_NONE;
++        }
++        if (exit_code == 1) {
++            return PW_SANDBOX_ACCESS_PLAYBACK;
++        }
++        return PW_SANDBOX_ACCESS_ALL;
++    }
++
++    retv = snapd_client_get_connections2_sync(snapdclient,
++                                              SNAPD_GET_CONNECTIONS_FLAGS_NONE,
++                                              snap_id,
++                                              NULL,
++                                              NULL,
++                                              NULL,
++                                              &plugs,
++                                              NULL,
++                                              NULL,
++                                              NULL);
++    if (retv == FALSE) {
++        pw_log_warn("Failed to get Snap connections for snap %s\n", snap_id);
++        return PW_SANDBOX_ACCESS_NONE;
++    }
++    if (plugs == NULL) {
++        pw_log_warn("Failed to get Snap connections for snap %s\n", snap_id);
++        return PW_SANDBOX_ACCESS_NONE;
++    }
++    if (plugs->pdata == NULL) {
++        pw_log_warn("Failed to get Snap connections for snap %s\n", snap_id);
++        return PW_SANDBOX_ACCESS_NONE;
++    }
++
++    plug = (SnapdPlug **)plugs->pdata;
++    for (guint p = 0; p < plugs->len; p++, plug++) {
++        const gchar *plug_name = snapd_plug_get_name(*plug);
++        if (g_str_equal("audio-record", plug_name)) {
++            add_permission = PW_SANDBOX_ACCESS_RECORD;
++        } else if (g_str_equal("audio-playback", plug_name)) {
++            add_permission = PW_SANDBOX_ACCESS_PLAYBACK;
++        } else if (g_str_equal("pulseaudio", plug_name)) {
++            add_permission = PW_SANDBOX_ACCESS_ALL;
++        } else {
++            continue;
++        }
++        slots = snapd_plug_get_connected_slots(*plug);
++        if (slots == NULL)
++            continue;
++        slot = (SnapdSlotRef**) slots->pdata;
++
++        for (guint q = 0; q < slots->len; q++, slot++) {
++            const gchar *slot_name = snapd_slot_ref_get_slot (*slot);
++            const gchar *snap_name = snapd_slot_ref_get_snap (*slot);
++            if (g_str_equal (snap_name, "snapd") &&
++                g_str_equal (slot_name, plug_name))
++                    permissions |= add_permission;
++        }
++    }
++
++    return permissions;
++}
+diff --git a/src/modules/module-protocol-pulse/snap-policy.h b/src/modules/module-protocol-pulse/snap-policy.h
+new file mode 100644
+index 0000000..0c152d3
+--- /dev/null
++++ b/src/modules/module-protocol-pulse/snap-policy.h
+@@ -0,0 +1,22 @@
++/* PipeWire */
++/* SPDX-FileCopyrightText: Copyright © 2022 Canonical Ltd. */
++/* SPDX-License-Identifier: MIT */
++
++#ifndef _SNAP_POLICY_H_
++#define _SNAP_POLICY_H_
++
++typedef enum _pw_sandbox_access {
++    PW_SANDBOX_ACCESS_NONE           = 0,
++    PW_SANDBOX_ACCESS_NOT_A_SANDBOX  = 1 << 0,
++    PW_SANDBOX_ACCESS_RECORD         = 1 << 1,
++    PW_SANDBOX_ACCESS_PLAYBACK       = 1 << 2,
++    PW_SANDBOX_ACCESS_ALL            = (PW_SANDBOX_ACCESS_PLAYBACK | PW_SANDBOX_ACCESS_RECORD),
++} pw_sandbox_access_t;
++
++#define PW_KEY_SNAP_ID "pipewire.snap.id"
++#define PW_KEY_SNAP_PLAYBACK_ALLOWED "pipewire.snap.audio.playback"
++#define PW_KEY_SNAP_RECORD_ALLOWED "pipewire.snap.audio.record"
++
++pw_sandbox_access_t pw_snap_get_audio_permissions(struct client *client, int fd, char **app_id);
++
++#endif // _SNAP_POLICY_H_
diff --git a/debian/patches/snap/Apply-1-suggestion-s-to-1-file-s-1.patch b/debian/patches/snap/Apply-1-suggestion-s-to-1-file-s-1.patch
new file mode 100644
index 0000000000000000000000000000000000000000..e659139adf5a31751483404cc038e968a76f7dc8
--- /dev/null
+++ b/debian/patches/snap/Apply-1-suggestion-s-to-1-file-s-1.patch
@@ -0,0 +1,24 @@
+From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <pobrn@protonmail.com>
+Date: Wed, 22 Nov 2023 11:47:07 +0000
+Subject: Apply 1 suggestion(s) to 1 file(s)
+
+(cherry picked from commit c34bd9575f46275b304298c221356bc827f7a1a9)
+Origin: upstream, after 1.0.1
+---
+ src/modules/module-protocol-pulse/snap-policy.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/src/modules/module-protocol-pulse/snap-policy.c b/src/modules/module-protocol-pulse/snap-policy.c
+index 8cf2762..7bec184 100644
+--- a/src/modules/module-protocol-pulse/snap-policy.c
++++ b/src/modules/module-protocol-pulse/snap-policy.c
+@@ -19,7 +19,8 @@
+ 
+ #define SNAP_LABEL_PREFIX      "snap."
+ 
+-static gboolean check_is_same_snap(gchar *snap1, gchar *snap2) {
++static gboolean check_is_same_snap(gchar *snap1, gchar *snap2)
++{
+     // Checks if two apparmor labels belong to the same snap
+     g_auto(GStrv) strings1 = NULL;
+     g_auto(GStrv) strings2 = NULL;
diff --git a/debian/patches/snap/Apply-1-suggestion-s-to-1-file-s-2.patch b/debian/patches/snap/Apply-1-suggestion-s-to-1-file-s-2.patch
new file mode 100644
index 0000000000000000000000000000000000000000..a4a1640e6ce9435f26842a1de6e9d885eb4ba5eb
--- /dev/null
+++ b/debian/patches/snap/Apply-1-suggestion-s-to-1-file-s-2.patch
@@ -0,0 +1,23 @@
+From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <pobrn@protonmail.com>
+Date: Wed, 22 Nov 2023 12:05:47 +0000
+Subject: Apply 1 suggestion(s) to 1 file(s)
+
+(cherry picked from commit abc4bd111be6273298aaeb290d8fb613ac7316d9)
+Origin: upstream, after 1.0.1
+---
+ src/modules/module-protocol-pulse/snap-policy.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/modules/module-protocol-pulse/snap-policy.c b/src/modules/module-protocol-pulse/snap-policy.c
+index d838d33..a6f2e24 100644
+--- a/src/modules/module-protocol-pulse/snap-policy.c
++++ b/src/modules/module-protocol-pulse/snap-policy.c
+@@ -58,7 +58,7 @@ pw_sandbox_access_t pw_snap_get_audio_permissions(struct client *client, int fd,
+     SnapdPlug **plug = NULL;
+     GPtrArray *slots = NULL;
+     SnapdSlotRef **slot = NULL;
+-    GError *error = NULL;
++    g_autoptr(GError) error = NULL;
+     int exit_code;
+ 
+     *app_id = g_strdup("unknown");
diff --git a/debian/patches/snap/Apply-1-suggestion-s-to-1-file-s-3.patch b/debian/patches/snap/Apply-1-suggestion-s-to-1-file-s-3.patch
new file mode 100644
index 0000000000000000000000000000000000000000..c27d11fce7bc93f82d861f38df5f24b0796cf266
--- /dev/null
+++ b/debian/patches/snap/Apply-1-suggestion-s-to-1-file-s-3.patch
@@ -0,0 +1,23 @@
+From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <pobrn@protonmail.com>
+Date: Wed, 22 Nov 2023 12:30:32 +0000
+Subject: Apply 1 suggestion(s) to 1 file(s)
+
+(cherry picked from commit b9b5a261998c2634477a71392a0c0c512c6e5974)
+Origin: upstream, after 1.0.1
+---
+ meson.build | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/meson.build b/meson.build
+index ccdd998..a564b16 100644
+--- a/meson.build
++++ b/meson.build
+@@ -439,7 +439,7 @@ else
+   snap_dep = dependency('snapd-glib', required : get_option('snap'))
+ endif
+ if snap_dep.found() and glib2_snap_dep.found() and gio2_snap_dep.found() and apparmor_snap_dep.found()
+-  cdata.set('HAVE_SNAP', 1)
++  cdata.set('HAVE_SNAP', true)
+   snap_deps = [glib2_snap_dep, gio2_snap_dep, snap_dep, apparmor_snap_dep]
+ endif
+ summary({'GLib-2.0 (Snap support)': glib2_snap_dep.found()}, bool_yn: true, section: 'Misc dependencies')
diff --git a/debian/patches/snap/Apply-1-suggestion-s-to-1-file-s.patch b/debian/patches/snap/Apply-1-suggestion-s-to-1-file-s.patch
new file mode 100644
index 0000000000000000000000000000000000000000..da7896325a8e56c9d477c500fea34cdda4d7516c
--- /dev/null
+++ b/debian/patches/snap/Apply-1-suggestion-s-to-1-file-s.patch
@@ -0,0 +1,23 @@
+From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <pobrn@protonmail.com>
+Date: Wed, 22 Nov 2023 11:46:57 +0000
+Subject: Apply 1 suggestion(s) to 1 file(s)
+
+(cherry picked from commit 69b093ebf14b04c9af4fbd899fdfbffff5fc4d8c)
+Origin: upstream, after 1.0.1
+---
+ src/modules/module-protocol-pulse/snap-policy.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/modules/module-protocol-pulse/snap-policy.c b/src/modules/module-protocol-pulse/snap-policy.c
+index 70bf984..8cf2762 100644
+--- a/src/modules/module-protocol-pulse/snap-policy.c
++++ b/src/modules/module-protocol-pulse/snap-policy.c
+@@ -90,7 +90,7 @@ pw_sandbox_access_t pw_snap_get_audio_permissions(struct client *client, int fd,
+     *app_id = snap_id;
+ 
+     // it's a "classic" or a "devmode" confinement snap, so we give it full access
+-    if (g_str_equal (snap_confinement, "complain")) {
++    if (g_str_equal(snap_confinement, "complain")) {
+         return PW_SANDBOX_ACCESS_ALL;
+     }
+ 
diff --git a/debian/patches/snap/Better-error-logging-if-getting-connections-fails.patch b/debian/patches/snap/Better-error-logging-if-getting-connections-fails.patch
new file mode 100644
index 0000000000000000000000000000000000000000..f306e91e58e08b1e879fdce01e703d158c3c10d7
--- /dev/null
+++ b/debian/patches/snap/Better-error-logging-if-getting-connections-fails.patch
@@ -0,0 +1,36 @@
+From: Sergio Costas Rodriguez <sergio.costas@canonical.com>
+Date: Wed, 22 Nov 2023 13:18:53 +0100
+Subject: Better error logging if getting connections fails
+
+(cherry picked from commit 1728b7de598a14b8b2819c6d269f586a4a843501)
+Origin: upstream, after 1.0.1
+---
+ src/modules/module-protocol-pulse/snap-policy.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/src/modules/module-protocol-pulse/snap-policy.c b/src/modules/module-protocol-pulse/snap-policy.c
+index a6f2e24..14e9827 100644
+--- a/src/modules/module-protocol-pulse/snap-policy.c
++++ b/src/modules/module-protocol-pulse/snap-policy.c
+@@ -148,17 +148,17 @@ pw_sandbox_access_t pw_snap_get_audio_permissions(struct client *client, int fd,
+                                               &plugs,
+                                               NULL,
+                                               NULL,
+-                                              NULL);
++                                              &error);
+     if (retv == FALSE) {
+-        pw_log_warn("Failed to get Snap connections for snap %s\n", snap_id);
++        pw_log_warn("Failed to get Snap connections for snap %s: %s\n", snap_id, error->message);
+         return PW_SANDBOX_ACCESS_NONE;
+     }
+     if (plugs == NULL) {
+-        pw_log_warn("Failed to get Snap connections for snap %s\n", snap_id);
++        pw_log_warn("Failed to get Snap connections for snap %s: %s\n", snap_id, error->message);
+         return PW_SANDBOX_ACCESS_NONE;
+     }
+     if (plugs->pdata == NULL) {
+-        pw_log_warn("Failed to get Snap connections for snap %s\n", snap_id);
++        pw_log_warn("Failed to get Snap connections for snap %s: %s\n", snap_id, error->message);
+         return PW_SANDBOX_ACCESS_NONE;
+     }
+ 
diff --git a/debian/patches/snap/Better-meson_options-description.patch b/debian/patches/snap/Better-meson_options-description.patch
new file mode 100644
index 0000000000000000000000000000000000000000..fe9bbbdcd26dbbb41841209af73af7fcf54f4da9
--- /dev/null
+++ b/debian/patches/snap/Better-meson_options-description.patch
@@ -0,0 +1,22 @@
+From: Sergio Costas Rodriguez <sergio.costas@canonical.com>
+Date: Wed, 22 Nov 2023 16:47:26 +0100
+Subject: Better meson_options description
+
+(cherry picked from commit 5125d69a6905f3ad86c1b1424d814f5442d9b677)
+Origin: upstream, after 1.0.1
+---
+ meson_options.txt | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/meson_options.txt b/meson_options.txt
+index e0937b4..6b274dc 100644
+--- a/meson_options.txt
++++ b/meson_options.txt
+@@ -331,6 +331,6 @@ option('libffado',
+        type: 'feature',
+        value: 'auto')
+ option('snap',
+-       description : 'Snap support is available.',
++       description : 'Enable Snap permissions support.',
+        type : 'feature',
+        value : 'auto')
diff --git a/debian/patches/snap/Fix-spacing-when-calling-functions.patch b/debian/patches/snap/Fix-spacing-when-calling-functions.patch
new file mode 100644
index 0000000000000000000000000000000000000000..f3d6561a5574cfd410ea7c63d1aa8f6c38a63e63
--- /dev/null
+++ b/debian/patches/snap/Fix-spacing-when-calling-functions.patch
@@ -0,0 +1,57 @@
+From: Sergio Costas Rodriguez <sergio.costas@canonical.com>
+Date: Wed, 22 Nov 2023 13:43:54 +0100
+Subject: Fix spacing when calling functions
+
+(cherry picked from commit fda4addf1ec3d97c13ed8998a92b7ae5d12f6c68)
+Origin: upstream, after 1.0.1
+---
+ src/modules/module-protocol-pulse/snap-policy.c | 16 ++++++++--------
+ 1 file changed, 8 insertions(+), 8 deletions(-)
+
+diff --git a/src/modules/module-protocol-pulse/snap-policy.c b/src/modules/module-protocol-pulse/snap-policy.c
+index b96c599..e1dd0d5 100644
+--- a/src/modules/module-protocol-pulse/snap-policy.c
++++ b/src/modules/module-protocol-pulse/snap-policy.c
+@@ -106,15 +106,15 @@ pw_sandbox_access_t pw_snap_get_audio_permissions(struct client *client, int fd,
+         // give to it full access.
+         if (check_is_same_snap(aacon, aa_label))
+             return PW_SANDBOX_ACCESS_ALL;
+-        snapd_client_set_socket_path (snapdclient, "/run/snapd-snap.socket");
++        snapd_client_set_socket_path(snapdclient, "/run/snapd-snap.socket");
+ 
+         /* Take context from the environment if available */
+-        const char *context = g_getenv ("SNAP_COOKIE");
++        const char *context = g_getenv("SNAP_COOKIE");
+         if (!context)
+             context = "";
+ 
+         char *snapctl_command[] = { "is-connected", "--apparmor-label", aa_label, "pulseaudio", NULL };
+-        if (!snapd_client_run_snapctl2_sync (snapdclient, context, (char **) snapctl_command, NULL, NULL, &exit_code, NULL, &error)) {
++        if (!snapd_client_run_snapctl2_sync(snapdclient, context, (char **) snapctl_command, NULL, NULL, &exit_code, NULL, &error)) {
+             pw_log_warn("snap_get_audio_permissions: error summoning snapctl2 for pulseaudio interface: %s", error->message);
+             return PW_SANDBOX_ACCESS_NONE;
+         }
+@@ -125,7 +125,7 @@ pw_sandbox_access_t pw_snap_get_audio_permissions(struct client *client, int fd,
+             return PW_SANDBOX_ACCESS_ALL;
+         }
+         char *snapctl_command2[] = { "is-connected", "--apparmor-label", aa_label, "audio-record", NULL };
+-        if (!snapd_client_run_snapctl2_sync (snapdclient, context, (char **) snapctl_command2, NULL, NULL, &exit_code, NULL, &error)) {
++        if (!snapd_client_run_snapctl2_sync(snapdclient, context, (char **) snapctl_command2, NULL, NULL, &exit_code, NULL, &error)) {
+             pw_log_warn("snap_get_audio_permissions: error summoning snapctl2 for audio-record interface: %s", error->message);
+             return PW_SANDBOX_ACCESS_NONE;
+         }
+@@ -177,10 +177,10 @@ pw_sandbox_access_t pw_snap_get_audio_permissions(struct client *client, int fd,
+         SnapdSlotRef **slot = (SnapdSlotRef**) slots->pdata;
+ 
+         for (guint q = 0; q < slots->len; q++, slot++) {
+-            const gchar *slot_name = snapd_slot_ref_get_slot (*slot);
+-            const gchar *snap_name = snapd_slot_ref_get_snap (*slot);
+-            if (g_str_equal (snap_name, "snapd") &&
+-                g_str_equal (slot_name, plug_name))
++            const gchar *slot_name = snapd_slot_ref_get_slot(*slot);
++            const gchar *snap_name = snapd_slot_ref_get_snap(*slot);
++            if (g_str_equal(snap_name, "snapd") &&
++                g_str_equal(slot_name, plug_name))
+                     permissions |= add_permission;
+         }
+     }
diff --git a/debian/patches/snap/Move-add_permission-definition-inside-block.patch b/debian/patches/snap/Move-add_permission-definition-inside-block.patch
new file mode 100644
index 0000000000000000000000000000000000000000..3dfe90e714cb13a6d0c5ba9ccc76069e0e621ea0
--- /dev/null
+++ b/debian/patches/snap/Move-add_permission-definition-inside-block.patch
@@ -0,0 +1,30 @@
+From: Sergio Costas Rodriguez <sergio.costas@canonical.com>
+Date: Wed, 22 Nov 2023 13:29:39 +0100
+Subject: Move add_permission definition inside block
+
+(cherry picked from commit 1c9016280c42dc43087700789d30066e9d7a19b9)
+Origin: upstream, after 1.0.1
+---
+ src/modules/module-protocol-pulse/snap-policy.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/modules/module-protocol-pulse/snap-policy.c b/src/modules/module-protocol-pulse/snap-policy.c
+index ebb5f29..b96c599 100644
+--- a/src/modules/module-protocol-pulse/snap-policy.c
++++ b/src/modules/module-protocol-pulse/snap-policy.c
+@@ -53,7 +53,6 @@ pw_sandbox_access_t pw_snap_get_audio_permissions(struct client *client, int fd,
+     g_autoptr(GPtrArray) plugs = NULL;
+     gboolean retv;
+     pw_sandbox_access_t permissions = PW_SANDBOX_ACCESS_NONE;
+-    pw_sandbox_access_t add_permission = PW_SANDBOX_ACCESS_NONE;
+     SnapdPlug **plug = NULL;
+     g_autoptr(GError) error = NULL;
+     int exit_code;
+@@ -161,6 +160,7 @@ pw_sandbox_access_t pw_snap_get_audio_permissions(struct client *client, int fd,
+ 
+     plug = (SnapdPlug **)plugs->pdata;
+     for (guint p = 0; p < plugs->len; p++, plug++) {
++        pw_sandbox_access_t add_permission;
+         const gchar *plug_name = snapd_plug_get_name(*plug);
+         if (g_str_equal("audio-record", plug_name)) {
+             add_permission = PW_SANDBOX_ACCESS_RECORD;
diff --git a/debian/patches/snap/Move-context-variable-definition-inside-block.patch b/debian/patches/snap/Move-context-variable-definition-inside-block.patch
new file mode 100644
index 0000000000000000000000000000000000000000..619221a4a934d23064a600d642def7b4e355b864
--- /dev/null
+++ b/debian/patches/snap/Move-context-variable-definition-inside-block.patch
@@ -0,0 +1,31 @@
+From: Sergio Costas Rodriguez <sergio.costas@canonical.com>
+Date: Wed, 22 Nov 2023 13:26:23 +0100
+Subject: Move context variable definition inside block
+
+(cherry picked from commit 67b9e9c4e8354a5f2075e41fa0d63f0b9b82d96a)
+Origin: upstream, after 1.0.1
+---
+ src/modules/module-protocol-pulse/snap-policy.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/src/modules/module-protocol-pulse/snap-policy.c b/src/modules/module-protocol-pulse/snap-policy.c
+index fcb1ed6..ebb5f29 100644
+--- a/src/modules/module-protocol-pulse/snap-policy.c
++++ b/src/modules/module-protocol-pulse/snap-policy.c
+@@ -49,7 +49,6 @@ pw_sandbox_access_t pw_snap_get_audio_permissions(struct client *client, int fd,
+     gchar *separator = NULL;
+     g_autofree gchar *aacon = NULL;
+     gchar *aamode = NULL;
+-    const char *context = NULL;
+     g_autoptr(SnapdClient) snapdclient = NULL;
+     g_autoptr(GPtrArray) plugs = NULL;
+     gboolean retv;
+@@ -111,7 +110,7 @@ pw_sandbox_access_t pw_snap_get_audio_permissions(struct client *client, int fd,
+         snapd_client_set_socket_path (snapdclient, "/run/snapd-snap.socket");
+ 
+         /* Take context from the environment if available */
+-        context = g_getenv ("SNAP_COOKIE");
++        const char *context = g_getenv ("SNAP_COOKIE");
+         if (!context)
+             context = "";
+ 
diff --git a/debian/patches/snap/Move-variable-definition-inside-block.patch b/debian/patches/snap/Move-variable-definition-inside-block.patch
new file mode 100644
index 0000000000000000000000000000000000000000..528a58239dd404ef96eaeb78224e187af2554f65
--- /dev/null
+++ b/debian/patches/snap/Move-variable-definition-inside-block.patch
@@ -0,0 +1,36 @@
+From: Sergio Costas Rodriguez <sergio.costas@canonical.com>
+Date: Wed, 22 Nov 2023 13:24:26 +0100
+Subject: Move variable definition inside block
+
+(cherry picked from commit 18d0e2e850d74c014c6c27d22c736ec40ac9a873)
+Origin: upstream, after 1.0.1
+---
+ src/modules/module-protocol-pulse/snap-policy.c | 6 ++----
+ 1 file changed, 2 insertions(+), 4 deletions(-)
+
+diff --git a/src/modules/module-protocol-pulse/snap-policy.c b/src/modules/module-protocol-pulse/snap-policy.c
+index 14e9827..fcb1ed6 100644
+--- a/src/modules/module-protocol-pulse/snap-policy.c
++++ b/src/modules/module-protocol-pulse/snap-policy.c
+@@ -56,8 +56,6 @@ pw_sandbox_access_t pw_snap_get_audio_permissions(struct client *client, int fd,
+     pw_sandbox_access_t permissions = PW_SANDBOX_ACCESS_NONE;
+     pw_sandbox_access_t add_permission = PW_SANDBOX_ACCESS_NONE;
+     SnapdPlug **plug = NULL;
+-    GPtrArray *slots = NULL;
+-    SnapdSlotRef **slot = NULL;
+     g_autoptr(GError) error = NULL;
+     int exit_code;
+ 
+@@ -174,10 +172,10 @@ pw_sandbox_access_t pw_snap_get_audio_permissions(struct client *client, int fd,
+         } else {
+             continue;
+         }
+-        slots = snapd_plug_get_connected_slots(*plug);
++        GPtrArray *slots = snapd_plug_get_connected_slots(*plug);
+         if (slots == NULL)
+             continue;
+-        slot = (SnapdSlotRef**) slots->pdata;
++        SnapdSlotRef **slot = (SnapdSlotRef**) slots->pdata;
+ 
+         for (guint q = 0; q < slots->len; q++, slot++) {
+             const gchar *slot_name = snapd_slot_ref_get_slot (*slot);
diff --git a/debian/patches/snap/Replace-spaces-with-tabs.patch b/debian/patches/snap/Replace-spaces-with-tabs.patch
new file mode 100644
index 0000000000000000000000000000000000000000..e64ab148133d5b370d5b19cd2fc622c7a1ce55a6
--- /dev/null
+++ b/debian/patches/snap/Replace-spaces-with-tabs.patch
@@ -0,0 +1,348 @@
+From: Sergio Costas Rodriguez <sergio.costas@canonical.com>
+Date: Wed, 22 Nov 2023 13:49:11 +0100
+Subject: Replace spaces with tabs
+
+(cherry picked from commit 6506bb2f4444be032985cc2874cc3eb94abf78fa)
+Origin: upstream, after 1.0.1
+---
+ src/modules/module-protocol-pulse/snap-policy.c | 322 ++++++++++++------------
+ 1 file changed, 161 insertions(+), 161 deletions(-)
+
+diff --git a/src/modules/module-protocol-pulse/snap-policy.c b/src/modules/module-protocol-pulse/snap-policy.c
+index e1dd0d5..f027f5c 100644
+--- a/src/modules/module-protocol-pulse/snap-policy.c
++++ b/src/modules/module-protocol-pulse/snap-policy.c
+@@ -18,172 +18,172 @@
+ #include <fcntl.h>
+ #include <assert.h>
+ 
+-#define SNAP_LABEL_PREFIX      "snap."
++#define SNAP_LABEL_PREFIX "snap."
+ 
+ static gboolean check_is_same_snap(gchar *snap1, gchar *snap2)
+ {
+-    // Checks if two apparmor labels belong to the same snap
+-    g_auto(GStrv) strings1 = NULL;
+-    g_auto(GStrv) strings2 = NULL;
+-
+-    if (!g_str_has_prefix(snap1, SNAP_LABEL_PREFIX)) {
+-        return FALSE;
+-    }
+-    if (!g_str_has_prefix(snap2, SNAP_LABEL_PREFIX)) {
+-        return FALSE;
+-    }
+-    strings1 = g_strsplit(snap1, ".", 3);
+-    strings2 = g_strsplit(snap2, ".", 3);
+-
+-    if (g_str_equal(strings1[1], strings2[1]) && (strings1[1] != NULL)) {
+-        return TRUE;
+-    }
+-    return FALSE;
++	// Checks if two apparmor labels belong to the same snap
++	g_auto(GStrv) strings1 = NULL;
++	g_auto(GStrv) strings2 = NULL;
++
++	if (!g_str_has_prefix(snap1, SNAP_LABEL_PREFIX)) {
++		return FALSE;
++	}
++	if (!g_str_has_prefix(snap2, SNAP_LABEL_PREFIX)) {
++		return FALSE;
++	}
++	strings1 = g_strsplit(snap1, ".", 3);
++	strings2 = g_strsplit(snap2, ".", 3);
++
++	if (g_str_equal(strings1[1], strings2[1]) && (strings1[1] != NULL)) {
++		return TRUE;
++	}
++	return FALSE;
+ }
+ 
+ pw_sandbox_access_t pw_snap_get_audio_permissions(struct client *client, int fd, char **app_id)
+ {
+-    g_autofree gchar* aa_label = NULL;
+-    gchar* snap_id = NULL;
+-    gchar* snap_confinement = NULL;
+-    gchar *separator = NULL;
+-    g_autofree gchar *aacon = NULL;
+-    gchar *aamode = NULL;
+-    g_autoptr(SnapdClient) snapdclient = NULL;
+-    g_autoptr(GPtrArray) plugs = NULL;
+-    gboolean retv;
+-    pw_sandbox_access_t permissions = PW_SANDBOX_ACCESS_NONE;
+-    SnapdPlug **plug = NULL;
+-    g_autoptr(GError) error = NULL;
+-    int exit_code;
+-
+-    *app_id = g_strdup("unknown");
+-    assert(client != NULL);
+-
+-    if (aa_getpeercon(fd, &aa_label, &snap_confinement) == -1) {
+-        if (errno == EINVAL) {
+-            // if apparmor isn't enabled, we can safely assume that there are no SNAPs in the system
+-            return PW_SANDBOX_ACCESS_NOT_A_SANDBOX;
+-        }
+-        pw_log_warn("snap_get_audio_permissions: failed to get the AppArmor info.");
+-        return PW_SANDBOX_ACCESS_NONE;
+-    }
+-    if (!g_str_has_prefix(aa_label, SNAP_LABEL_PREFIX)) {
+-        // not a SNAP.
+-        pw_log_info("snap_get_audio_permissions: not an snap.");
+-        return PW_SANDBOX_ACCESS_NOT_A_SANDBOX;
+-    }
+-
+-    snap_id = g_strdup(aa_label + strlen(SNAP_LABEL_PREFIX));
+-    separator = strchr(snap_id, '.');
+-    if (separator == NULL) {
+-        pw_log_info("snap_get_audio_permissions: aa_label has only one dot; not a valid ID.");
+-        return PW_SANDBOX_ACCESS_NONE;
+-    }
+-    *separator = 0;
+-    g_free(*app_id);
+-    *app_id = snap_id;
+-
+-    // it's a "classic" or a "devmode" confinement snap, so we give it full access
+-    if (g_str_equal(snap_confinement, "complain")) {
+-        return PW_SANDBOX_ACCESS_ALL;
+-    }
+-
+-    snapdclient = snapd_client_new();
+-    if (snapdclient == NULL) {
+-        pw_log_warn("snap_get_audio_permissions: error creating SnapdClient object.");
+-        return PW_SANDBOX_ACCESS_NONE;
+-    }
+-
+-    if (aa_getcon(&aacon, &aamode) == -1) {
+-        pw_log_warn("snap_get_audio_permissions: error checking if pipewire-pulse is inside a snap.");
+-        return PW_SANDBOX_ACCESS_NONE; // failed to get access to apparmor
+-    }
+-
+-    // If pipewire-pulse is inside a snap, use snapctl API
+-    if (g_str_has_prefix(aacon, SNAP_LABEL_PREFIX)) {
+-        // If the snap wanting to get access is the same that contains pipewire,
+-        // give to it full access.
+-        if (check_is_same_snap(aacon, aa_label))
+-            return PW_SANDBOX_ACCESS_ALL;
+-        snapd_client_set_socket_path(snapdclient, "/run/snapd-snap.socket");
+-
+-        /* Take context from the environment if available */
+-        const char *context = g_getenv("SNAP_COOKIE");
+-        if (!context)
+-            context = "";
+-
+-        char *snapctl_command[] = { "is-connected", "--apparmor-label", aa_label, "pulseaudio", NULL };
+-        if (!snapd_client_run_snapctl2_sync(snapdclient, context, (char **) snapctl_command, NULL, NULL, &exit_code, NULL, &error)) {
+-            pw_log_warn("snap_get_audio_permissions: error summoning snapctl2 for pulseaudio interface: %s", error->message);
+-            return PW_SANDBOX_ACCESS_NONE;
+-        }
+-        if (exit_code != 1) {
+-            // 0  = Connected
+-            // 10 = Classic environment
+-            // 11 = Not a snap
+-            return PW_SANDBOX_ACCESS_ALL;
+-        }
+-        char *snapctl_command2[] = { "is-connected", "--apparmor-label", aa_label, "audio-record", NULL };
+-        if (!snapd_client_run_snapctl2_sync(snapdclient, context, (char **) snapctl_command2, NULL, NULL, &exit_code, NULL, &error)) {
+-            pw_log_warn("snap_get_audio_permissions: error summoning snapctl2 for audio-record interface: %s", error->message);
+-            return PW_SANDBOX_ACCESS_NONE;
+-        }
+-        if (exit_code == 1) {
+-            return PW_SANDBOX_ACCESS_PLAYBACK;
+-        }
+-        return PW_SANDBOX_ACCESS_ALL;
+-    }
+-
+-    retv = snapd_client_get_connections2_sync(snapdclient,
+-                                              SNAPD_GET_CONNECTIONS_FLAGS_NONE,
+-                                              snap_id,
+-                                              NULL,
+-                                              NULL,
+-                                              NULL,
+-                                              &plugs,
+-                                              NULL,
+-                                              NULL,
+-                                              &error);
+-    if (retv == FALSE) {
+-        pw_log_warn("Failed to get Snap connections for snap %s: %s\n", snap_id, error->message);
+-        return PW_SANDBOX_ACCESS_NONE;
+-    }
+-    if (plugs == NULL) {
+-        pw_log_warn("Failed to get Snap connections for snap %s: %s\n", snap_id, error->message);
+-        return PW_SANDBOX_ACCESS_NONE;
+-    }
+-    if (plugs->pdata == NULL) {
+-        pw_log_warn("Failed to get Snap connections for snap %s: %s\n", snap_id, error->message);
+-        return PW_SANDBOX_ACCESS_NONE;
+-    }
+-
+-    plug = (SnapdPlug **)plugs->pdata;
+-    for (guint p = 0; p < plugs->len; p++, plug++) {
+-        pw_sandbox_access_t add_permission;
+-        const gchar *plug_name = snapd_plug_get_name(*plug);
+-        if (g_str_equal("audio-record", plug_name)) {
+-            add_permission = PW_SANDBOX_ACCESS_RECORD;
+-        } else if (g_str_equal("audio-playback", plug_name)) {
+-            add_permission = PW_SANDBOX_ACCESS_PLAYBACK;
+-        } else if (g_str_equal("pulseaudio", plug_name)) {
+-            add_permission = PW_SANDBOX_ACCESS_ALL;
+-        } else {
+-            continue;
+-        }
+-        GPtrArray *slots = snapd_plug_get_connected_slots(*plug);
+-        if (slots == NULL)
+-            continue;
+-        SnapdSlotRef **slot = (SnapdSlotRef**) slots->pdata;
+-
+-        for (guint q = 0; q < slots->len; q++, slot++) {
+-            const gchar *slot_name = snapd_slot_ref_get_slot(*slot);
+-            const gchar *snap_name = snapd_slot_ref_get_snap(*slot);
+-            if (g_str_equal(snap_name, "snapd") &&
+-                g_str_equal(slot_name, plug_name))
+-                    permissions |= add_permission;
+-        }
+-    }
+-
+-    return permissions;
++	g_autofree gchar* aa_label = NULL;
++	gchar* snap_id = NULL;
++	gchar* snap_confinement = NULL;
++	gchar *separator = NULL;
++	g_autofree gchar *aacon = NULL;
++	gchar *aamode = NULL;
++	g_autoptr(SnapdClient) snapdclient = NULL;
++	g_autoptr(GPtrArray) plugs = NULL;
++	gboolean retv;
++	pw_sandbox_access_t permissions = PW_SANDBOX_ACCESS_NONE;
++	SnapdPlug **plug = NULL;
++	g_autoptr(GError) error = NULL;
++	int exit_code;
++
++	*app_id = g_strdup("unknown");
++	assert(client != NULL);
++
++	if (aa_getpeercon(fd, &aa_label, &snap_confinement) == -1) {
++		if (errno == EINVAL) {
++			// if apparmor isn't enabled, we can safely assume that there are no SNAPs in the system
++			return PW_SANDBOX_ACCESS_NOT_A_SANDBOX;
++		}
++		pw_log_warn("snap_get_audio_permissions: failed to get the AppArmor info.");
++		return PW_SANDBOX_ACCESS_NONE;
++	}
++	if (!g_str_has_prefix(aa_label, SNAP_LABEL_PREFIX)) {
++		// not a SNAP.
++		pw_log_info("snap_get_audio_permissions: not an snap.");
++		return PW_SANDBOX_ACCESS_NOT_A_SANDBOX;
++	}
++
++	snap_id = g_strdup(aa_label + strlen(SNAP_LABEL_PREFIX));
++	separator = strchr(snap_id, '.');
++	if (separator == NULL) {
++		pw_log_info("snap_get_audio_permissions: aa_label has only one dot; not a valid ID.");
++		return PW_SANDBOX_ACCESS_NONE;
++	}
++	*separator = 0;
++	g_free(*app_id);
++	*app_id = snap_id;
++
++	// it's a "classic" or a "devmode" confinement snap, so we give it full access
++	if (g_str_equal(snap_confinement, "complain")) {
++		return PW_SANDBOX_ACCESS_ALL;
++	}
++
++	snapdclient = snapd_client_new();
++	if (snapdclient == NULL) {
++		pw_log_warn("snap_get_audio_permissions: error creating SnapdClient object.");
++		return PW_SANDBOX_ACCESS_NONE;
++	}
++
++	if (aa_getcon(&aacon, &aamode) == -1) {
++		pw_log_warn("snap_get_audio_permissions: error checking if pipewire-pulse is inside a snap.");
++		return PW_SANDBOX_ACCESS_NONE; // failed to get access to apparmor
++	}
++
++	// If pipewire-pulse is inside a snap, use snapctl API
++	if (g_str_has_prefix(aacon, SNAP_LABEL_PREFIX)) {
++		// If the snap wanting to get access is the same that contains pipewire,
++		// give to it full access.
++		if (check_is_same_snap(aacon, aa_label))
++			return PW_SANDBOX_ACCESS_ALL;
++		snapd_client_set_socket_path(snapdclient, "/run/snapd-snap.socket");
++
++		/* Take context from the environment if available */
++		const char *context = g_getenv("SNAP_COOKIE");
++		if (!context)
++			context = "";
++
++		char *snapctl_command[] = { "is-connected", "--apparmor-label", aa_label, "pulseaudio", NULL };
++		if (!snapd_client_run_snapctl2_sync(snapdclient, context, (char **) snapctl_command, NULL, NULL, &exit_code, NULL, &error)) {
++			pw_log_warn("snap_get_audio_permissions: error summoning snapctl2 for pulseaudio interface: %s", error->message);
++			return PW_SANDBOX_ACCESS_NONE;
++		}
++		if (exit_code != 1) {
++			// 0  = Connected
++			// 10 = Classic environment
++			// 11 = Not a snap
++			return PW_SANDBOX_ACCESS_ALL;
++		}
++		char *snapctl_command2[] = { "is-connected", "--apparmor-label", aa_label, "audio-record", NULL };
++		if (!snapd_client_run_snapctl2_sync(snapdclient, context, (char **) snapctl_command2, NULL, NULL, &exit_code, NULL, &error)) {
++			pw_log_warn("snap_get_audio_permissions: error summoning snapctl2 for audio-record interface: %s", error->message);
++			return PW_SANDBOX_ACCESS_NONE;
++		}
++		if (exit_code == 1) {
++			return PW_SANDBOX_ACCESS_PLAYBACK;
++		}
++		return PW_SANDBOX_ACCESS_ALL;
++	}
++
++	retv = snapd_client_get_connections2_sync(snapdclient,
++						  SNAPD_GET_CONNECTIONS_FLAGS_NONE,
++						  snap_id,
++						  NULL,
++						  NULL,
++						  NULL,
++						  &plugs,
++						  NULL,
++						  NULL,
++						  &error);
++	if (retv == FALSE) {
++		pw_log_warn("Failed to get Snap connections for snap %s: %s\n", snap_id, error->message);
++		return PW_SANDBOX_ACCESS_NONE;
++	}
++	if (plugs == NULL) {
++		pw_log_warn("Failed to get Snap connections for snap %s: %s\n", snap_id, error->message);
++		return PW_SANDBOX_ACCESS_NONE;
++	}
++	if (plugs->pdata == NULL) {
++		pw_log_warn("Failed to get Snap connections for snap %s: %s\n", snap_id, error->message);
++		return PW_SANDBOX_ACCESS_NONE;
++	}
++
++	plug = (SnapdPlug **)plugs->pdata;
++	for (guint p = 0; p < plugs->len; p++, plug++) {
++		pw_sandbox_access_t add_permission;
++		const gchar *plug_name = snapd_plug_get_name(*plug);
++		if (g_str_equal("audio-record", plug_name)) {
++			add_permission = PW_SANDBOX_ACCESS_RECORD;
++		} else if (g_str_equal("audio-playback", plug_name)) {
++			add_permission = PW_SANDBOX_ACCESS_PLAYBACK;
++		} else if (g_str_equal("pulseaudio", plug_name)) {
++			add_permission = PW_SANDBOX_ACCESS_ALL;
++		} else {
++			continue;
++		}
++		GPtrArray *slots = snapd_plug_get_connected_slots(*plug);
++		if (slots == NULL)
++			continue;
++		SnapdSlotRef **slot = (SnapdSlotRef**) slots->pdata;
++
++		for (guint q = 0; q < slots->len; q++, slot++) {
++			const gchar *slot_name = snapd_slot_ref_get_slot(*slot);
++			const gchar *snap_name = snapd_slot_ref_get_snap(*slot);
++			if (g_str_equal(snap_name, "snapd") &&
++			    g_str_equal(slot_name, plug_name))
++				permissions |= add_permission;
++		}
++	}
++
++	return permissions;
+ }
diff --git a/debian/patches/snap/Use-assert-to-check-client-is-not-NULL.patch b/debian/patches/snap/Use-assert-to-check-client-is-not-NULL.patch
new file mode 100644
index 0000000000000000000000000000000000000000..7b8029ad1a535108caf97cee0f05914921d6021e
--- /dev/null
+++ b/debian/patches/snap/Use-assert-to-check-client-is-not-NULL.patch
@@ -0,0 +1,34 @@
+From: Sergio Costas Rodriguez <sergio.costas@canonical.com>
+Date: Wed, 22 Nov 2023 13:03:06 +0100
+Subject: Use assert to check client is not NULL
+
+(cherry picked from commit b054bc25910713df63a1321bc86fe15432821800)
+Origin: upstream, after 1.0.1
+---
+ src/modules/module-protocol-pulse/snap-policy.c | 6 ++----
+ 1 file changed, 2 insertions(+), 4 deletions(-)
+
+diff --git a/src/modules/module-protocol-pulse/snap-policy.c b/src/modules/module-protocol-pulse/snap-policy.c
+index 7bec184..d838d33 100644
+--- a/src/modules/module-protocol-pulse/snap-policy.c
++++ b/src/modules/module-protocol-pulse/snap-policy.c
+@@ -16,6 +16,7 @@
+ #include <errno.h>
+ #include "snap-policy.h"
+ #include <fcntl.h>
++#include <assert.h>
+ 
+ #define SNAP_LABEL_PREFIX      "snap."
+ 
+@@ -61,10 +62,7 @@ pw_sandbox_access_t pw_snap_get_audio_permissions(struct client *client, int fd,
+     int exit_code;
+ 
+     *app_id = g_strdup("unknown");
+-    if (client == NULL) {
+-        pw_log_warn("Called snap_get_audio_permissions with NULL parameter.");
+-        return PW_SANDBOX_ACCESS_NONE;
+-    }
++    assert(client != NULL);
+ 
+     if (aa_getpeercon(fd, &aa_label, &snap_confinement) == -1) {
+         if (errno == EINVAL) {
diff --git a/debian/patches/snap/fix-possible-leak.patch b/debian/patches/snap/fix-possible-leak.patch
new file mode 100644
index 0000000000000000000000000000000000000000..3939cd1ffdda1bac8a8302c11ada4fe215c5ab37
--- /dev/null
+++ b/debian/patches/snap/fix-possible-leak.patch
@@ -0,0 +1,39 @@
+From: Sergio Costas Rodriguez <sergio.costas@canonical.com>
+Date: Wed, 22 Nov 2023 13:15:42 +0100
+Subject: fix possible leak
+
+If pw_check_flatpak() sets app_id, its value will leak when
+calling pw_snap_get_audio_permissions(). This patch fixes
+this.
+
+(cherry picked from commit ae11e61105d53e4dc0860894ccc17b1a8aead7fa)
+Origin: upstream, after 1.0.1
+---
+ src/modules/module-protocol-pulse/server.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/src/modules/module-protocol-pulse/server.c b/src/modules/module-protocol-pulse/server.c
+index 6ae97b7..bbb790f 100644
+--- a/src/modules/module-protocol-pulse/server.c
++++ b/src/modules/module-protocol-pulse/server.c
+@@ -408,7 +408,7 @@ on_connect(void *data, int fd, uint32_t mask)
+ 		client_access = server->client_access;
+ 
+ 	if (server->addr.ss_family == AF_UNIX) {
+-		spa_autofree char *app_id = NULL, *devices = NULL;
++		spa_autofree char *app_id = NULL, *snap_app_id = NULL, *devices = NULL;
+ #ifdef HAVE_SNAP
+ 		pw_sandbox_access_t snap_access;
+ #endif
+@@ -452,9 +452,9 @@ on_connect(void *data, int fd, uint32_t mask)
+ 		}
+ 		// check SNAP permissions
+ #ifdef HAVE_SNAP
+-		snap_access = pw_snap_get_audio_permissions(client, client_fd, &app_id);
++		snap_access = pw_snap_get_audio_permissions(client, client_fd, &snap_app_id);
+ 		if ((snap_access & PW_SANDBOX_ACCESS_NOT_A_SANDBOX) == 0) {
+-			pw_properties_set(client->props, PW_KEY_SNAP_ID, app_id);
++			pw_properties_set(client->props, PW_KEY_SNAP_ID, snap_app_id);
+ 
+ 			pw_properties_set(client->props,
+ 			                  PW_KEY_SNAP_PLAYBACK_ALLOWED,
diff --git a/debian/patches/snap/pipewire-pulse-add-snap-permissions-support.patch b/debian/patches/snap/pipewire-pulse-add-snap-permissions-support.patch
new file mode 100644
index 0000000000000000000000000000000000000000..039c02a9a26d891c3ea00c6b44f9651d917bef48
--- /dev/null
+++ b/debian/patches/snap/pipewire-pulse-add-snap-permissions-support.patch
@@ -0,0 +1,255 @@
+From: Sergio Costas Rodriguez <sergio.costas@canonical.com>
+Date: Wed, 22 Nov 2023 11:26:16 +0100
+Subject: pipewire-pulse: add snap permissions support
+
+SNAP containers have two main "audio" security rules:
+
+ * audio-playback: the applications inside the container can
+   send audio samples into a sink
+
+ * audio-record: the applications inside the container can
+   get audio samples from a source
+
+Also, old SNAP containers had the "pulseaudio" rule, which just
+exposed the pulseaudio socket directly, without limits. This
+is similar to the current Flatpak audio permissions.
+
+In the pulseaudio days, a specific pulseaudio module was used
+that checked the permissions given to the application and
+allowed or forbade access to the pulseaudio operations.
+With the change to pipewire, this functionality must be
+implemented in pipewire-pulse to guarantee the sandbox
+security.
+
+This patch adds support for sandboxing permissions in the
+pulseaudio module, and implements support for the SNAP audio
+security model, thus forbiding a SNAP application to record
+audio unless it has permissions to do so.
+
+The current code for pipewire-pulseaudio checks the permissions
+of the snap and adds three properties to each new client:
+
+ * pipewire.snap.id: contains the Snap ID of the client.
+
+ * pipewire.snap.audio.playback: its value is 'true' if the client
+   has permission to play audio, or 'false' if not.
+
+ * pipewire.snap.audio.record: its value is 'true' if the client
+   has permission to record audio, or 'false' if not.
+
+These properties must be processed by wireplumber to add or
+remove access permissions to the corresponding nodes. That
+code is available in a separate patch: https://gitlab.freedesktop.org/pipewire/wireplumber/-/merge_requests/567
+
+(cherry picked from commit d568dcd64f64454289e1f35ed07a11749f95b04e)
+Origin: upstream, after 1.0.1
+---
+ .gitlab-ci.yml                             | 18 +++++++++++-------
+ meson.build                                | 16 ++++++++++++++++
+ meson_options.txt                          |  4 ++++
+ src/modules/meson.build                    |  7 +++++++
+ src/modules/module-protocol-pulse/server.c | 21 +++++++++++++++++++++
+ 5 files changed, 59 insertions(+), 7 deletions(-)
+
+diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
+index e0fce1c..87b3852 100644
+--- a/.gitlab-ci.yml
++++ b/.gitlab-ci.yml
+@@ -96,6 +96,7 @@ include:
+       debhelper-compat
+       findutils
+       git
++      libapparmor-dev
+       libasound2-dev
+       libavcodec-dev
+       libavfilter-dev
+@@ -107,6 +108,7 @@ include:
+       libgstreamer-plugins-base1.0-dev
+       libsbc-dev
+       libsdl2-dev
++      libsnapd-glib-dev
+       libudev-dev
+       libva-dev
+       libv4l-dev
+@@ -247,7 +249,7 @@ build_on_ubuntu:
+     - .build
+   stage: build
+   variables:
+-    MESON_OPTIONS: "-Dsession-managers=[]"
++    MESON_OPTIONS: "-Dsession-managers=[] -Dsnap=enabled"
+ 
+ .build_on_fedora:
+   extends:
+@@ -274,6 +276,7 @@ build_on_fedora:
+         -Dsdl2=enabled
+         -Dsndfile=enabled
+         -Dsession-managers=[]
++        -Dsnap=disabled
+   artifacts:
+     name: pipewire-$CI_COMMIT_SHA
+     when: always
+@@ -289,7 +292,7 @@ build_on_alpine:
+     - .build
+   stage: build
+   variables:
+-    MESON_OPTIONS: "-Dsession-managers=[]"
++    MESON_OPTIONS: "-Dsession-managers=[] -Dsnap=disabled"
+ 
+ # build with all auto() options enabled
+ build_all:
+@@ -308,6 +311,7 @@ build_all:
+         -Dsession-managers=[]
+         -Dc_args=['-UFASTPATH']
+         -Dcpp_args=['-UFASTPATH']
++        -Dsnap=disabled
+   parallel:
+     matrix:
+       - CC: [gcc, clang]
+@@ -317,7 +321,7 @@ build_with_no_commandline_options:
+   extends:
+     - .build_on_fedora
+   variables:
+-    MESON_OPTIONS: "-Dsession-managers=[]"
++    MESON_OPTIONS: "-Dsession-managers=[] -Dsnap=disabled"
+   parallel:
+     matrix:
+       - CC: [gcc, clang]
+@@ -353,7 +357,7 @@ build_release:
+   extends:
+     - .build_on_fedora
+   variables:
+-    MESON_OPTIONS: "-Dtest=enabled -Dbuildtype=release -Db_ndebug=true -Dsession-managers=[]"
++    MESON_OPTIONS: "-Dtest=enabled -Dbuildtype=release -Db_ndebug=true -Dsession-managers=[] -Dsnap=disabled"
+   parallel:
+     matrix:
+       - CC: [gcc, clang]
+@@ -367,7 +371,7 @@ build_session_managers:
+     - meson compile -C "$BUILD_DIR" $COMPILE_ARGS
+     - meson install -C "$BUILD_DIR" --no-rebuild
+   variables:
+-    MESON_OPTIONS: "-Dsession-managers=$SESSION_MANAGERS"
++    MESON_OPTIONS: "-Dsession-managers=$SESSION_MANAGERS -Dsnap=disabled"
+   parallel:
+     matrix:
+       - SESSION_MANAGERS: ["[]", "wireplumber", "media-session", "media-session,wireplumber", "wireplumber,media-session" ]
+@@ -384,7 +388,7 @@ build_meson_prerelease:
+     - meson compile -C "$BUILD_DIR" $COMPILE_ARGS
+     - meson install -C "$BUILD_DIR" --no-rebuild
+   variables:
+-    MESON_OPTIONS: "-Dsession-managers=wireplumber,media-session"
++    MESON_OPTIONS: "-Dsession-managers=wireplumber,media-session -Dsnap=disabled"
+   allow_failure: true
+ 
+ build_meson_exact_release:
+@@ -402,7 +406,7 @@ build_meson_exact_release:
+     - meson compile -C "$BUILD_DIR" $COMPILE_ARGS
+     - meson install -C "$BUILD_DIR" --no-rebuild
+   variables:
+-    MESON_OPTIONS: "-Dsession-managers=[]"
++    MESON_OPTIONS: "-Dsession-managers=[] -Dsnap=disabled"
+ 
+ valgrind:
+   extends:
+diff --git a/meson.build b/meson.build
+index eb76fb9..ccdd998 100644
+--- a/meson.build
++++ b/meson.build
+@@ -430,6 +430,22 @@ summary({'lilv (for lv2 plugins)': lilv_lib.found()}, bool_yn: true)
+ 
+ libffado_dep = dependency('libffado', required: get_option('libffado'))
+ summary({'ffado': libffado_dep.found()}, bool_yn: true)
++glib2_snap_dep = dependency('glib-2.0', required : get_option('snap'))
++gio2_snap_dep = dependency('gio-2.0', required : get_option('snap'))
++apparmor_snap_dep = dependency('libapparmor', required : get_option('snap'))
++if dependency('snapd-glib-2', required: false).found()
++  snap_dep = dependency('snapd-glib-2', required : get_option('snap'))
++else
++  snap_dep = dependency('snapd-glib', required : get_option('snap'))
++endif
++if snap_dep.found() and glib2_snap_dep.found() and gio2_snap_dep.found() and apparmor_snap_dep.found()
++  cdata.set('HAVE_SNAP', 1)
++  snap_deps = [glib2_snap_dep, gio2_snap_dep, snap_dep, apparmor_snap_dep]
++endif
++summary({'GLib-2.0 (Snap support)': glib2_snap_dep.found()}, bool_yn: true, section: 'Misc dependencies')
++summary({'Gio-2.0 (Snap support)': gio2_snap_dep.found()}, bool_yn: true, section: 'Misc dependencies')
++summary({'Apparmor (Snap support)': apparmor_snap_dep.found()}, bool_yn: true, section: 'Misc dependencies')
++summary({'Snapd-glib (Snap support)': snap_dep.found()}, bool_yn: true, section: 'Misc dependencies')
+ 
+ check_functions = [
+   ['gettid', '#include <unistd.h>', ['-D_GNU_SOURCE'], []],
+diff --git a/meson_options.txt b/meson_options.txt
+index a4b0ab5..e0937b4 100644
+--- a/meson_options.txt
++++ b/meson_options.txt
+@@ -330,3 +330,7 @@ option('libffado',
+        description: 'Enable code that depends on libffado',
+        type: 'feature',
+        value: 'auto')
++option('snap',
++       description : 'Snap support is available.',
++       type : 'feature',
++       value : 'auto')
+diff --git a/src/modules/meson.build b/src/modules/meson.build
+index 1b434b7..0909f24 100644
+--- a/src/modules/meson.build
++++ b/src/modules/meson.build
+@@ -393,6 +393,13 @@ pipewire_module_protocol_pulse_sources = [
+   'module-protocol-pulse/modules/module-zeroconf-discover.c',
+ ]
+ 
++if snap_dep.found() and glib2_snap_dep.found() and gio2_snap_dep.found() and apparmor_snap_dep.found()
++  pipewire_module_protocol_pulse_sources += [
++    'module-protocol-pulse/snap-policy.c',
++  ]
++  pipewire_module_protocol_pulse_deps += snap_deps
++endif
++
+ if dbus_dep.found()
+   pipewire_module_protocol_pulse_sources += [
+     'module-protocol-pulse/dbus-name.c',
+diff --git a/src/modules/module-protocol-pulse/server.c b/src/modules/module-protocol-pulse/server.c
+index 53b1a7e..6ae97b7 100644
+--- a/src/modules/module-protocol-pulse/server.c
++++ b/src/modules/module-protocol-pulse/server.c
+@@ -42,6 +42,9 @@
+ #include "stream.h"
+ #include "utils.h"
+ #include "flatpak-utils.h"
++#ifdef HAVE_SNAP
++#include "snap-policy.h"
++#endif
+ 
+ #define LISTEN_BACKLOG 32
+ #define MAX_CLIENTS 64
+@@ -406,6 +409,9 @@ on_connect(void *data, int fd, uint32_t mask)
+ 
+ 	if (server->addr.ss_family == AF_UNIX) {
+ 		spa_autofree char *app_id = NULL, *devices = NULL;
++#ifdef HAVE_SNAP
++		pw_sandbox_access_t snap_access;
++#endif
+ 
+ #ifdef SO_PRIORITY
+ 		val = 6;
+@@ -444,6 +450,21 @@ on_connect(void *data, int fd, uint32_t mask)
+ 			else
+ 				pw_properties_set(client->props, PW_KEY_MEDIA_CATEGORY, NULL);
+ 		}
++		// check SNAP permissions
++#ifdef HAVE_SNAP
++		snap_access = pw_snap_get_audio_permissions(client, client_fd, &app_id);
++		if ((snap_access & PW_SANDBOX_ACCESS_NOT_A_SANDBOX) == 0) {
++			pw_properties_set(client->props, PW_KEY_SNAP_ID, app_id);
++
++			pw_properties_set(client->props,
++			                  PW_KEY_SNAP_PLAYBACK_ALLOWED,
++			                  (snap_access & PW_SANDBOX_ACCESS_PLAYBACK) ? "true" : "false");
++
++			pw_properties_set(client->props,
++			                  PW_KEY_SNAP_RECORD_ALLOWED,
++			                  (snap_access & PW_SANDBOX_ACCESS_RECORD) ? "true" : "false");
++		}
++#endif
+ 	}
+ 	else if (server->addr.ss_family == AF_INET || server->addr.ss_family == AF_INET6) {
+ 
diff --git a/debian/patches/snap/snap-policy-Manage-ENOPROTOOPT-error-in-aa_getpeercon.patch b/debian/patches/snap/snap-policy-Manage-ENOPROTOOPT-error-in-aa_getpeercon.patch
new file mode 100644
index 0000000000000000000000000000000000000000..214179780a0b1d35140b2f814550c4f01211b04c
--- /dev/null
+++ b/debian/patches/snap/snap-policy-Manage-ENOPROTOOPT-error-in-aa_getpeercon.patch
@@ -0,0 +1,31 @@
+From: Sergio Costas <sergio.costas@canonical.com>
+Date: Tue, 30 Jan 2024 10:28:27 +0000
+Subject: snap-policy: Manage ENOPROTOOPT error in aa_getpeercon()
+
+Bug-Ubuntu: https://launchpad.net/bugs/2051504
+
+(cherry picked from commit e8fcaa5157506e5def0f45dc135ad97da75237f1)
+Origin: upstream, after 1.0.1
+---
+ src/modules/module-protocol-pulse/snap-policy.c | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+diff --git a/src/modules/module-protocol-pulse/snap-policy.c b/src/modules/module-protocol-pulse/snap-policy.c
+index f027f5c..48084b8 100644
+--- a/src/modules/module-protocol-pulse/snap-policy.c
++++ b/src/modules/module-protocol-pulse/snap-policy.c
+@@ -65,7 +65,13 @@ pw_sandbox_access_t pw_snap_get_audio_permissions(struct client *client, int fd,
+ 			// if apparmor isn't enabled, we can safely assume that there are no SNAPs in the system
+ 			return PW_SANDBOX_ACCESS_NOT_A_SANDBOX;
+ 		}
+-		pw_log_warn("snap_get_audio_permissions: failed to get the AppArmor info.");
++		if  (errno == ENOPROTOOPT) {
++			// if fine grained unix mediation isn't available, we can't know if this is a snap or
++			// not, so we have no choice but give full access
++			pw_log_warn("snap_get_audio_permissions: kernel lacks 'fine grained unix mediation'; snap audio permissions won't be honored.");
++			return PW_SANDBOX_ACCESS_NOT_A_SANDBOX;
++		}
++ 		pw_log_warn("snap_get_audio_permissions: failed to get the AppArmor info: %s.", strerror(errno));
+ 		return PW_SANDBOX_ACCESS_NONE;
+ 	}
+ 	if (!g_str_has_prefix(aa_label, SNAP_LABEL_PREFIX)) {
diff --git a/debian/rules b/debian/rules
index 150cbd4af35d4844a30741ef525daf79be8094a4..b1fa5749c098074abf619825249c3b346f694bcc 100755
--- a/debian/rules
+++ b/debian/rules
@@ -46,11 +46,13 @@ LIBFFADO=enabled
 endif
 
 ifneq (,$(filter hurd-amd64 hurd-i386,$(DEB_HOST_ARCH)))
+SNAP=disabled
 UDEVRULESDIR=
 else
 # export UDEVRULESDIR=/usr/lib/udev/rules.d
 # For pre-Trixie releases udev rules should go in:
 export UDEVRULESDIR=/lib/udev/rules.d
+SNAP=enabled
 endif
 
 override_dh_auto_configure:
@@ -76,6 +78,7 @@ override_dh_auto_configure:
 		-Droc=disabled \
 		-Dsdl2=$(SDL2) \
 		-Dsession-managers= \
+		-Dsnap=$(SNAP) \
 		-Dtest=enabled \
 		-Dudevrulesdir=$(UDEVRULESDIR) \
 		-Dvideotestsrc=enabled \
@@ -100,9 +103,6 @@ override_dh_auto_test:
 		--timeout-multiplier $(test_timeout_multiplier) \
 		$(NULL)
 
-override_dh_missing:
-	dh_missing --fail-missing
-
 override_dh_makeshlibs:
 	dh_makeshlibs \
 		--exclude=/usr/lib/$(DEB_HOST_MULTIARCH)/gstreamer-1.0 \
diff --git a/meson.build b/meson.build
index eb76fb9eed8f3f4d9d77366cf5fef7431c23fa6b..b9019b8400f4f039fdd6ec99a4bcd663078931b9 100644
--- a/meson.build
+++ b/meson.build
@@ -1,5 +1,5 @@
 project('pipewire', ['c' ],
-  version : '1.0.1',
+  version : '1.0.3',
   license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ],
   meson_version : '>= 0.61.1',
   default_options : [ 'warning_level=3',
diff --git a/pipewire-alsa/alsa-plugins/pcm_pipewire.c b/pipewire-alsa/alsa-plugins/pcm_pipewire.c
index 3464692dc7566feaa2f6cacc2037b301d6364bca..e5043aa949c2a86e4c83b96243bcee4d11d163f0 100644
--- a/pipewire-alsa/alsa-plugins/pcm_pipewire.c
+++ b/pipewire-alsa/alsa-plugins/pcm_pipewire.c
@@ -1298,7 +1298,7 @@ SND_PCM_PLUGIN_DEFINE_FUNC(pipewire)
 	int err;
 
 	pw_init(NULL, NULL);
-	if (strstr(pw_get_library_version(), "0.2") != NULL)
+	if (spa_strstartswith(pw_get_library_version(), "0.2"))
 		return -ENOTSUP;
 
 	props = pw_properties_new(NULL, NULL);
diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c
index b891e60a3b36812aa20a3dc8f9c4d023558096cb..27c6d2555435640746d1a0e55d5cf09fd561ece6 100644
--- a/pipewire-jack/src/pipewire-jack.c
+++ b/pipewire-jack/src/pipewire-jack.c
@@ -3241,18 +3241,28 @@ static const struct pw_proxy_events proxy_events = {
 	.destroy = proxy_destroy,
 };
 
+static bool node_is_active(struct client *c, struct object *n)
+{
+	return !n->node.is_jack ||
+		(c->node_id == n->id ? c->active : n->node.is_running);
+}
+
 static void node_info(void *data, const struct pw_node_info *info)
 {
 	struct object *n = data;
 	struct client *c = n->client;
-	const char *str;
+	bool active;
 
 	if (info->change_mask & PW_NODE_CHANGE_MASK_PROPS) {
-		str = spa_dict_lookup(info->props, PW_KEY_NODE_ALWAYS_PROCESS);
+		/* JACK clients always need ALWAYS_PROCESS=true or else they don't
+		 * conform to the JACK API. We would try to hide the ports of
+		 * PAUSED JACK clients, for example, even if they are active. */
+		const char *str = spa_dict_lookup(info->props, PW_KEY_NODE_ALWAYS_PROCESS);
 		n->node.is_jack = str ? spa_atob(str) : false;
 	}
 
-	n->node.is_running = !n->node.is_jack || (info->state == PW_NODE_STATE_RUNNING);
+	n->node.is_running = info->state == PW_NODE_STATE_RUNNING;
+	active = node_is_active(c, n);
 
 	pw_log_debug("DSP node %d %08"PRIx64" jack:%u state change %s running:%d", info->id,
 			info->change_mask, n->node.is_jack,
@@ -3264,7 +3274,7 @@ static void node_info(void *data, const struct pw_node_info *info)
 			if (p->type != INTERFACE_Port || p->removed ||
 			    p->port.node_id != info->id)
 				continue;
-			if (n->node.is_running)
+			if (active)
 				queue_notify(c, NOTIFY_TYPE_PORTREGISTRATION, p, 1, NULL);
 			else {
 				spa_list_for_each(l, &c->context.objects, link) {
@@ -3492,7 +3502,7 @@ static void registry_event_global(void *data, uint32_t id,
 			o->port.latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT);
 			o->port.latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT);
 
-			do_emit = !ot->node.is_jack || ot->node.is_running;
+			do_emit = node_is_active(c, ot);
 
 			o->proxy = pw_registry_bind(c->registry,
 				id, type, PW_VERSION_PORT, 0);
@@ -3785,7 +3795,7 @@ jack_client_t * jack_client_open (const char *client_name,
 
         if (getenv("PIPEWIRE_NOJACK") != NULL ||
             getenv("PIPEWIRE_INTERNAL") != NULL ||
-	    strstr(pw_get_library_version(), "0.2") != NULL)
+	    spa_strstartswith(pw_get_library_version(), "0.2"))
 		goto disabled;
 
 	return_val_if_fail(client_name != NULL, NULL);
diff --git a/spa/include/spa/utils/defs.h b/spa/include/spa/utils/defs.h
index 8940b52df7721ca5807880a57d62dfe4874130e6..393b1f33468b95381cab1412d4d188f0301e0d9f 100644
--- a/spa/include/spa/utils/defs.h
+++ b/spa/include/spa/utils/defs.h
@@ -156,6 +156,10 @@ struct spa_fraction {
 ({							\
 	fminf(fmaxf(v, low), high);			\
 })
+#define SPA_CLAMPD(v,low,high)				\
+({							\
+	fmin(fmax(v, low), high);			\
+})
 
 
 #define SPA_SWAP(a,b)					\
diff --git a/spa/meson.build b/spa/meson.build
index 0ee750d6eb052f5bf378a5d12a066f36f2e82637..db0a844258b0e9526be071daf9b0d5c8b1f92329 100644
--- a/spa/meson.build
+++ b/spa/meson.build
@@ -96,9 +96,8 @@ if get_option('spa-plugins').allowed()
   endif
   summary({'Vulkan': have_vulkan}, bool_yn: true, section: 'Misc dependencies')
 
-  libcamera_dep = dependency('libcamera', required: get_option('libcamera'))
+  libcamera_dep = dependency('libcamera', version: '>= 0.2.0', required: get_option('libcamera'))
   summary({'libcamera': libcamera_dep.found()}, bool_yn: true, section: 'Backend')
-  cdata.set('HAVE_LIBCAMERA_SYSTEM_DEVICES', libcamera_dep.version().version_compare('>= 0.1.0'))
 
   compress_offload_option = get_option('compress-offload')
   summary({'Compress-Offload': compress_offload_option.allowed()}, bool_yn: true, section: 'Backend')
diff --git a/spa/plugins/alsa/alsa-pcm-sink.c b/spa/plugins/alsa/alsa-pcm-sink.c
index aa8105ab1a4164a2798ccbcedd20ae534757cf1b..2d595122750dc43c4e359c54b2a18681198b3498 100644
--- a/spa/plugins/alsa/alsa-pcm-sink.c
+++ b/spa/plugins/alsa/alsa-pcm-sink.c
@@ -458,14 +458,19 @@ impl_node_port_enum_params(void *object, int seq,
 		break;
 
 	case SPA_PARAM_Buffers:
+	{
+		uint32_t min_buffers;
+
 		if (!this->have_format)
 			return -EIO;
 		if (result.index > 0)
 			return 0;
 
+		min_buffers = (this->quantum_limit * 4 * this->frame_scale) > this->buffer_frames ?  2 : 1;
+
 		param = spa_pod_builder_add_object(&b.b,
 			SPA_TYPE_OBJECT_ParamBuffers, id,
-			SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS),
+			SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(min_buffers, min_buffers, MAX_BUFFERS),
 			SPA_PARAM_BUFFERS_blocks,  SPA_POD_Int(this->blocks),
 			SPA_PARAM_BUFFERS_size,    SPA_POD_CHOICE_RANGE_Int(
 							this->quantum_limit * this->frame_size * this->frame_scale,
@@ -473,6 +478,7 @@ impl_node_port_enum_params(void *object, int seq,
 							INT32_MAX),
 			SPA_PARAM_BUFFERS_stride,  SPA_POD_Int(this->frame_size));
 		break;
+	}
 
 	case SPA_PARAM_Meta:
 		switch (result.index) {
diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c
index 7774bfd4258ec0e8bd709365c6b59e9032727c45..fd1057eae9ae72661817f792af7b98ac5b4f7eb0 100644
--- a/spa/plugins/alsa/alsa-pcm.c
+++ b/spa/plugins/alsa/alsa-pcm.c
@@ -3476,8 +3476,8 @@ int spa_alsa_reassign_follower(struct state *state)
 	if (following != state->following) {
 		spa_log_debug(state->log, "%p: reassign follower %d->%d", state, state->following, following);
 		state->following = following;
-		setup_matching(state);
 	}
+	setup_matching(state);
 	if (state->started)
 		spa_loop_invoke(state->data_loop, do_state_sync, 0, NULL, 0, true, state);
 
@@ -3534,7 +3534,8 @@ void spa_alsa_emit_node_info(struct state *state, bool full)
 				state->stream == SND_PCM_STREAM_PLAYBACK ? "Audio/Sink" : "Audio/Source");
 		items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_DRIVER, "true");
 		if (state->have_format) {
-			snprintf(latency, sizeof(latency), "%lu/%d", state->buffer_frames / 2, state->rate);
+			snprintf(latency, sizeof(latency), "%lu/%d",
+					state->buffer_frames / (2 * state->frame_scale), state->rate);
 			items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_MAX_LATENCY, latency);
 			snprintf(period, sizeof(period), "%lu", state->period_frames);
 			items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-size", period);
diff --git a/spa/plugins/audioconvert/resample-native-avx.c b/spa/plugins/audioconvert/resample-native-avx.c
index c4b4c605b81b395eee2f7684c0e515626e8bb6c5..a57d668b782aa3e84137bf4503252001140cacc8 100644
--- a/spa/plugins/audioconvert/resample-native-avx.c
+++ b/spa/plugins/audioconvert/resample-native-avx.c
@@ -16,18 +16,18 @@ static inline void inner_product_avx(float *d, const float * SPA_RESTRICT s,
 	uint32_t n_taps4 = n_taps & ~0xf;
 
 	for (; i < n_taps4; i += 16) {
-		ty = (__m256)_mm256_lddqu_si256((__m256i*)(s + i + 0));
+		ty = _mm256_loadu_ps(s + i + 0);
 		sy[0] = _mm256_fmadd_ps(ty, _mm256_load_ps(taps + i + 0), sy[0]);
-		ty = (__m256)_mm256_lddqu_si256((__m256i*)(s + i + 8));
+		ty = _mm256_loadu_ps(s + i + 8);
 		sy[1] = _mm256_fmadd_ps(ty, _mm256_load_ps(taps + i + 8), sy[1]);
 	}
 	sy[0] = _mm256_add_ps(sy[1], sy[0]);
 	sx[1] = _mm256_extractf128_ps(sy[0], 1);
 	sx[0] = _mm256_extractf128_ps(sy[0], 0);
 	for (; i < n_taps; i += 8) {
-		tx = (__m128)_mm_lddqu_si128((__m128i*)(s + i + 0));
+		tx = _mm_loadu_ps(s + i + 0);
 		sx[0] = _mm_fmadd_ps(tx, _mm_load_ps(taps + i + 0), sx[0]);
-		tx = (__m128)_mm_lddqu_si128((__m128i*)(s + i + 4));
+		tx = _mm_loadu_ps(s + i + 4);
 		sx[1] = _mm_fmadd_ps(tx, _mm_load_ps(taps + i + 4), sx[1]);
 	}
 	sx[0] = _mm_add_ps(sx[0], sx[1]);
@@ -45,10 +45,10 @@ static inline void inner_product_ip_avx(float *d, const float * SPA_RESTRICT s,
 	uint32_t i, n_taps4 = n_taps & ~0xf;
 
 	for (i = 0; i < n_taps4; i += 16) {
-		ty = (__m256)_mm256_lddqu_si256((__m256i*)(s + i + 0));
+		ty = _mm256_loadu_ps(s + i + 0);
 		sy[0] = _mm256_fmadd_ps(ty, _mm256_load_ps(t0 + i + 0), sy[0]);
 		sy[1] = _mm256_fmadd_ps(ty, _mm256_load_ps(t1 + i + 0), sy[1]);
-		ty = (__m256)_mm256_lddqu_si256((__m256i*)(s + i + 8));
+		ty = _mm256_loadu_ps(s + i + 8);
 		sy[0] = _mm256_fmadd_ps(ty, _mm256_load_ps(t0 + i + 8), sy[0]);
 		sy[1] = _mm256_fmadd_ps(ty, _mm256_load_ps(t1 + i + 8), sy[1]);
 	}
@@ -56,10 +56,10 @@ static inline void inner_product_ip_avx(float *d, const float * SPA_RESTRICT s,
 	sx[1] = _mm_add_ps(_mm256_extractf128_ps(sy[1], 0), _mm256_extractf128_ps(sy[1], 1));
 
 	for (; i < n_taps; i += 8) {
-		tx = (__m128)_mm_lddqu_si128((__m128i*)(s + i + 0));
+		tx = _mm_loadu_ps(s + i + 0);
 		sx[0] = _mm_fmadd_ps(tx, _mm_load_ps(t0 + i + 0), sx[0]);
 		sx[1] = _mm_fmadd_ps(tx, _mm_load_ps(t1 + i + 0), sx[1]);
-		tx = (__m128)_mm_lddqu_si128((__m128i*)(s + i + 4));
+		tx = _mm_loadu_ps(s + i + 4);
 		sx[0] = _mm_fmadd_ps(tx, _mm_load_ps(t0 + i + 4), sx[0]);
 		sx[1] = _mm_fmadd_ps(tx, _mm_load_ps(t1 + i + 4), sx[1]);
 	}
diff --git a/spa/plugins/avb/avb-pcm-sink.c b/spa/plugins/avb/avb-pcm-sink.c
index 2c77483e3eb7e39ddf23a0b8799bd4d24d9ec328..6b9e4856cb298d1fd8a8b7464170a3a530f6e1c6 100644
--- a/spa/plugins/avb/avb-pcm-sink.c
+++ b/spa/plugins/avb/avb-pcm-sink.c
@@ -557,7 +557,7 @@ impl_node_port_set_param(void *object,
 {
 	struct state *this = object;
 	struct port *port;
-	int res;
+	int res = 0;
 
 	spa_return_val_if_fail(this != NULL, -EINVAL);
 
diff --git a/spa/plugins/avb/avb-pcm-source.c b/spa/plugins/avb/avb-pcm-source.c
index 1fa48dfdb106bc3809408cb902da11d2325612c9..9811c24314db8ccb0f4bef71978ef163a9d05d6c 100644
--- a/spa/plugins/avb/avb-pcm-source.c
+++ b/spa/plugins/avb/avb-pcm-source.c
@@ -557,7 +557,7 @@ impl_node_port_set_param(void *object,
 {
 	struct state *this = object;
 	struct port *port;
-	int res;
+	int res = 0;
 
 	spa_return_val_if_fail(this != NULL, -EINVAL);
 
diff --git a/spa/plugins/bluez5/bap-codec-lc3.c b/spa/plugins/bluez5/bap-codec-lc3.c
index 1cffbeddeeb1faf00eea0d21e53486a9cd7a0317..0226d14e2eba8c1da797cb2923e8fd67ac34a493 100644
--- a/spa/plugins/bluez5/bap-codec-lc3.c
+++ b/spa/plugins/bluez5/bap-codec-lc3.c
@@ -600,22 +600,22 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
 	spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0);
 	choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]);
 	i = 0;
-	if (conf.rate & LC3_CONFIG_FREQ_48KHZ) {
+	if (conf.rate == LC3_CONFIG_FREQ_48KHZ) {
 		if (i++ == 0)
 			spa_pod_builder_int(b, 48000);
 		spa_pod_builder_int(b, 48000);
 	}
-	if (conf.rate & LC3_CONFIG_FREQ_24KHZ) {
+	if (conf.rate == LC3_CONFIG_FREQ_24KHZ) {
 		if (i++ == 0)
 			spa_pod_builder_int(b, 24000);
 		spa_pod_builder_int(b, 24000);
 	}
-	if (conf.rate & LC3_CONFIG_FREQ_16KHZ) {
+	if (conf.rate == LC3_CONFIG_FREQ_16KHZ) {
 		if (i++ == 0)
 			spa_pod_builder_int(b, 16000);
 		spa_pod_builder_int(b, 16000);
 	}
-	if (conf.rate & LC3_CONFIG_FREQ_8KHZ) {
+	if (conf.rate == LC3_CONFIG_FREQ_8KHZ) {
 		if (i++ == 0)
 			spa_pod_builder_int(b, 8000);
 		spa_pod_builder_int(b, 8000);
diff --git a/spa/plugins/bluez5/bluez-hardware.conf b/spa/plugins/bluez5/bluez-hardware.conf
index 28b949570c23c6a080d871ce9de9962317bbf868..34abe6ff82a89d36a81aab8eac7e9fd9b9c4b65a 100644
--- a/spa/plugins/bluez5/bluez-hardware.conf
+++ b/spa/plugins/bluez5/bluez-hardware.conf
@@ -40,9 +40,11 @@ bluez5.features.device = [
     { name = "Motorola DC800", no-features = [ sbc-xq ] },  # #pipewire-1590
     { name = "Motorola S305", no-features = [ sbc-xq ] },  # #pipewire-1590
     { name = "PMK True Wireless Earbuds" no-features = [ sbc-xq ] }, # Primark earbud
+    { name = "Rockbox Brick", no-features = [ hw-volume ] }, # #pipewire-3786
     { name = "Soundcore Life P2-L", no-features = [ msbc-alt1, msbc-alt1-rtl ] },
     { name = "Soundcore Motion B", no-features = [ hw-volume ] },
     { name = "SoundCore mini", no-features = [ hw-volume ] },  # #pipewire-1686
+    { name = "SoundCore mini2", no-features = [ hw-volume ] },  # #pipewire-2927
     { name = "SoundCore 2", no-features = [ sbc-xq ] },  # #pipewire-2291
     { name = "Tribit MAXSound Plus", no-features = [ hw-volume ] },  # #pipewire-1592
     { name = "Urbanista Stockholm Plus", no-features = [ msbc-alt1, msbc-alt1-rtl ] },
diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c
index bb6e0a8b336d8cc0b03578ff3fb780a91507fc46..82a2c85bface9442756eb1c6b46c681b545f29f2 100644
--- a/spa/plugins/bluez5/bluez5-dbus.c
+++ b/spa/plugins/bluez5/bluez5-dbus.c
@@ -5545,7 +5545,12 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
 			transport = spa_bt_transport_find(monitor, path);
 			if (transport == NULL) {
 				spa_log_warn(monitor->log,
-						"Properties changed in unknown transport %s", path);
+						"Properties changed in unknown transport '%s'. "
+						"Multiple sound server instances (PipeWire/Pulseaudio/bluez-alsa) are "
+						"probably trying to use Bluetooth audio at the same time, which can "
+						"cause problems. The system configuration likely should be fixed "
+						"to have only one sound server that manages Bluetooth audio.",
+						path);
 				goto finish;
 			}
 
diff --git a/spa/plugins/libcamera/libcamera-device.cpp b/spa/plugins/libcamera/libcamera-device.cpp
index 0abf2f619520f4eeb44f0aece14ee8e2895d8940..b25a4eb728d8312b789a5d2d72ee462b94f231bc 100644
--- a/spa/plugins/libcamera/libcamera-device.cpp
+++ b/spa/plugins/libcamera/libcamera-device.cpp
@@ -61,12 +61,10 @@ struct impl {
 static const libcamera::Span<const int64_t> cameraDevice(
 			const Camera *camera)
 {
-#ifdef HAVE_LIBCAMERA_SYSTEM_DEVICES
 	const ControlList &props = camera->properties();
 
 	if (auto devices = props.get(properties::SystemDevices))
 		return devices.value();
-#endif
 
 	return {};
 }
diff --git a/spa/plugins/libcamera/libcamera-utils.cpp b/spa/plugins/libcamera/libcamera-utils.cpp
index 2b1aea5a76bf47a7893236e6bbba170f92da6954..c197248d30ef25c322f6279be1d48e0339471560 100644
--- a/spa/plugins/libcamera/libcamera-utils.cpp
+++ b/spa/plugins/libcamera/libcamera-utils.cpp
@@ -716,25 +716,23 @@ static int spa_libcamera_use_buffers(struct impl *impl, struct port *port,
 }
 
 static const struct {
-	Transform libcamera_transform;
-	uint32_t spa_transform_value;
-} transform_map[] = {
-	{ Transform::Identity, SPA_META_TRANSFORMATION_None },
-	{ Transform::Rot0, SPA_META_TRANSFORMATION_None },
-	{ Transform::HFlip, SPA_META_TRANSFORMATION_Flipped },
-	{ Transform::VFlip, SPA_META_TRANSFORMATION_Flipped180 },
-	{ Transform::HVFlip, SPA_META_TRANSFORMATION_180 },
-	{ Transform::Rot180, SPA_META_TRANSFORMATION_180 },
-	{ Transform::Transpose, SPA_META_TRANSFORMATION_Flipped90 },
-	{ Transform::Rot90, SPA_META_TRANSFORMATION_90 },
-	{ Transform::Rot270, SPA_META_TRANSFORMATION_270 },
-	{ Transform::Rot180Transpose, SPA_META_TRANSFORMATION_Flipped270 },
+	Orientation libcamera_orientation; /* clockwise rotation then horizontal mirroring */
+	uint32_t spa_transform_value; /* horizontal mirroring then counter-clockwise rotation */
+} orientation_map[] = {
+	{ Orientation::Rotate0, SPA_META_TRANSFORMATION_None },
+	{ Orientation::Rotate0Mirror, SPA_META_TRANSFORMATION_Flipped },
+	{ Orientation::Rotate90, SPA_META_TRANSFORMATION_270 },
+	{ Orientation::Rotate90Mirror, SPA_META_TRANSFORMATION_Flipped90 },
+	{ Orientation::Rotate180, SPA_META_TRANSFORMATION_180 },
+	{ Orientation::Rotate180Mirror, SPA_META_TRANSFORMATION_Flipped180 },
+	{ Orientation::Rotate270, SPA_META_TRANSFORMATION_90 },
+	{ Orientation::Rotate270Mirror, SPA_META_TRANSFORMATION_Flipped270 },
 };
 
-static uint32_t libcamera_transform_to_spa_transform_value(Transform transform)
+static uint32_t libcamera_orientation_to_spa_transform_value(Orientation orientation)
 {
-	for (const auto& t : transform_map) {
-		if (t.libcamera_transform == transform)
+	for (const auto& t : orientation_map) {
+		if (t.libcamera_orientation == orientation)
 			return t.spa_transform_value;
 	}
 	return SPA_META_TRANSFORMATION_None;
@@ -788,9 +786,9 @@ mmap_init(struct impl *impl, struct port *port,
 			buffers[i], SPA_META_VideoTransform, sizeof(*b->videotransform));
 		if (b->videotransform) {
 			b->videotransform->transform =
-				libcamera_transform_to_spa_transform_value(impl->config->transform);
-			spa_log_debug(impl->log, "Setting videotransform for buffer %d to %u (from %s)",
-				i, b->videotransform->transform, transformToString(impl->config->transform));
+				libcamera_orientation_to_spa_transform_value(impl->config->orientation);
+			spa_log_debug(impl->log, "Setting videotransform for buffer %u to %u",
+				i, b->videotransform->transform);
 
 		}
 
diff --git a/spa/plugins/support/node-driver.c b/spa/plugins/support/node-driver.c
index dc36c821660c104eb6d0f29dfc052d69825a10fb..df9b9cfdb8dfed1f42ce6868fce72b723fc07310 100644
--- a/spa/plugins/support/node-driver.c
+++ b/spa/plugins/support/node-driver.c
@@ -33,6 +33,7 @@
 #define DEFAULT_FREEWHEEL_WAIT	10
 #define DEFAULT_CLOCK_PREFIX	"clock.system"
 #define DEFAULT_CLOCK_ID	CLOCK_MONOTONIC
+#define DEFAULT_RESYNC_MS	10
 
 #define CLOCKFD 3
 #define FD_TO_CLOCKID(fd)	((~(clockid_t) (fd) << 3) | CLOCKFD)
@@ -46,6 +47,7 @@ struct props {
 	char clock_name[64];
 	clockid_t clock_id;
 	uint32_t freewheel_wait;
+	float resync_ms;
 };
 
 struct impl {
@@ -81,6 +83,7 @@ struct impl {
 	uint64_t base_time;
 	struct spa_dll dll;
 	double max_error;
+	double max_resync;
 };
 
 static void reset_props(struct props *props)
@@ -89,6 +92,7 @@ static void reset_props(struct props *props)
 	spa_zero(props->clock_name);
 	props->clock_id = CLOCK_MONOTONIC;
 	props->freewheel_wait = DEFAULT_FREEWHEEL_WAIT;
+	props->resync_ms = DEFAULT_RESYNC_MS;
 }
 
 static const struct clock_info {
@@ -281,6 +285,7 @@ static void on_timeout(struct spa_source *source)
 	if (this->last_time == 0) {
 		spa_dll_set_bw(&this->dll, SPA_DLL_BW_MIN, duration, rate);
 		this->max_error = rate * MAX_ERROR_MS / 1000;
+		this->max_resync = rate * this->props.resync_ms / 1000;
 		position = current_position;
 	} else if (SPA_LIKELY(this->clock)) {
 		position = this->clock->position + this->clock->duration;
@@ -288,21 +293,27 @@ static void on_timeout(struct spa_source *source)
 		position = current_position;
 	}
 
-	/* check the elapsed time of the other clock against
-	 * the graph clock elapsed time, feed this error into the
-	 * dll and adjust the timeout of our MONOTONIC clock. */
-	err = (double)position - (double)current_position;
-	if (err > this->max_error)
-		err = this->max_error;
-	else if (err < -this->max_error)
-		err = -this->max_error;
-
 	this->last_time = current_time;
 
 	if (this->props.freewheel) {
 		corr = 1.0;
 		this->next_time = nsec + this->props.freewheel_wait * SPA_NSEC_PER_SEC;
 	} else if (this->tracking) {
+		/* check the elapsed time of the other clock against
+		 * the graph clock elapsed time, feed this error into the
+		 * dll and adjust the timeout of our MONOTONIC clock. */
+		err = (double)position - (double)current_position;
+		if (fabs(err) > this->max_error) {
+			if (fabs(err) > this->max_resync) {
+				spa_log_warn(this->log, "err %f > max_resync %f, resetting",
+						err, this->max_resync);
+				spa_dll_set_bw(&this->dll, SPA_DLL_BW_MIN, duration, rate);
+				position = current_position;
+				err = 0.0;
+			} else {
+				err = SPA_CLAMPD(err, -this->max_error, this->max_error);
+			}
+		}
 		corr = spa_dll_update(&this->dll, err);
 		this->next_time = nsec + duration / corr * 1e9 / rate;
 	} else {
@@ -621,6 +632,8 @@ impl_init(const struct spa_handle_factory *factory,
 			}
 		} else if (spa_streq(k, "freewheel.wait")) {
 			this->props.freewheel_wait = atoi(s);
+		} else if (spa_streq(k, "resync.ms")) {
+			this->props.resync_ms = atof(s);
 		}
 	}
 	if (this->props.clock_name[0] == '\0') {
diff --git a/spa/plugins/v4l2/v4l2-source.c b/spa/plugins/v4l2/v4l2-source.c
index 413670eacc002c95fab5c750521aadf8ce618fa9..c532ea18591be0cd4ed8febeab3b08ce96759f4b 100644
--- a/spa/plugins/v4l2/v4l2-source.c
+++ b/spa/plugins/v4l2/v4l2-source.c
@@ -71,6 +71,7 @@ struct port {
 	struct impl *impl;
 
 	bool alloc_buffers;
+	bool probed_expbuf;
 	bool have_expbuf;
 
 	bool next_fmtdesc;
@@ -715,7 +716,7 @@ static int impl_node_port_set_param(void *object,
 				    uint32_t id, uint32_t flags,
 				    const struct spa_pod *param)
 {
-	int res;
+	int res = 0;
 	struct impl *this = object;
 	struct port *port;
 
@@ -1020,8 +1021,7 @@ impl_init(const struct spa_handle_factory *factory,
 	port->info.params = port->params;
 	port->info.n_params = N_PORT_PARAMS;
 
-	port->alloc_buffers = true;
-	port->have_expbuf = true;
+	port->probed_expbuf = false;
 	port->have_query_ext_ctrl = true;
 	port->dev.log = this->log;
 	port->dev.fd = -1;
diff --git a/spa/plugins/v4l2/v4l2-utils.c b/spa/plugins/v4l2/v4l2-utils.c
index 8647ca9e3e897c4f33131bfc62f5c09548b3f29d..1ed46d62ce737a02fee1479a00575f099d7c1287 100644
--- a/spa/plugins/v4l2/v4l2-utils.c
+++ b/spa/plugins/v4l2/v4l2-utils.c
@@ -367,11 +367,11 @@ static const struct format_info *find_format_info_by_media_type(uint32_t type,
 	return NULL;
 }
 
-static uint32_t
+static int
 enum_filter_format(uint32_t media_type, int32_t media_subtype,
 		   const struct spa_pod *filter, uint32_t index)
 {
-	uint32_t video_format = 0;
+	uint32_t video_format = SPA_VIDEO_FORMAT_UNKNOWN;
 
 	switch (media_type) {
 	case SPA_MEDIA_TYPE_video:
@@ -383,12 +383,12 @@ enum_filter_format(uint32_t media_type, int32_t media_subtype,
 			const uint32_t *values;
 
 			if (!(p = spa_pod_find_prop(filter, NULL, SPA_FORMAT_VIDEO_format)))
-				return SPA_VIDEO_FORMAT_UNKNOWN;
+				return -ENOENT;
 
 			val = spa_pod_get_values(&p->value, &n_values, &choice);
 
 			if (val->type != SPA_TYPE_Id)
-				return SPA_VIDEO_FORMAT_UNKNOWN;
+				return -EINVAL;
 
 			values = SPA_POD_BODY(val);
 
@@ -503,7 +503,7 @@ spa_v4l2_enum_format(struct impl *this, int seq,
 	int res, n_fractions;
 	const struct format_info *info;
 	struct spa_pod_choice *choice;
-	uint32_t filter_media_type, filter_media_subtype, video_format;
+	uint32_t filter_media_type, filter_media_subtype;
 	struct spa_v4l2_device *dev = &port->dev;
 	uint8_t buffer[1024];
 	struct spa_pod_builder b = { 0 };
@@ -545,16 +545,19 @@ spa_v4l2_enum_format(struct impl *this, int seq,
 		if (filter) {
 			struct v4l2_format fmt;
 
-			video_format = enum_filter_format(filter_media_type,
+			res = enum_filter_format(filter_media_type,
 					    filter_media_subtype,
 					    filter, port->fmtdesc.index);
-
-			if (video_format == SPA_VIDEO_FORMAT_UNKNOWN)
+			if (res == -ENOENT)
+				goto do_enum_fmt;
+			if (res < 0)
+				goto exit;
+			if (res == SPA_VIDEO_FORMAT_UNKNOWN)
 				goto enum_end;
 
 			info = find_format_info_by_media_type(filter_media_type,
 							      filter_media_subtype,
-							      video_format, 0);
+							      res, 0);
 			if (info == NULL)
 				goto next_fmtdesc;
 
@@ -580,6 +583,7 @@ spa_v4l2_enum_format(struct impl *this, int seq,
 			}
 
 		} else {
+do_enum_fmt:
 			if ((res = xioctl(dev->fd, VIDIOC_ENUM_FMT, &port->fmtdesc)) < 0) {
 				if (errno == EINVAL)
 					goto enum_end;
@@ -815,6 +819,7 @@ spa_v4l2_enum_format(struct impl *this, int seq,
 						 port->frmival.discrete.denominator,
 						 port->frmival.discrete.numerator);
 			port->frmival.index++;
+			n_fractions++;
 		} else if (port->frmival.type == V4L2_FRMIVAL_TYPE_CONTINUOUS ||
 			   port->frmival.type == V4L2_FRMIVAL_TYPE_STEPWISE) {
 			if (n_fractions == 0)
@@ -828,20 +833,23 @@ spa_v4l2_enum_format(struct impl *this, int seq,
 
 			if (port->frmival.type == V4L2_FRMIVAL_TYPE_CONTINUOUS) {
 				choice->body.type = SPA_CHOICE_Range;
+				n_fractions += 2;
 			} else {
 				choice->body.type = SPA_CHOICE_Step;
 				spa_pod_builder_fraction(&b,
 							 port->frmival.stepwise.step.denominator,
 							 port->frmival.stepwise.step.numerator);
+				n_fractions += 3;
 			}
 
 			port->frmsize.index++;
 			port->next_frmsize = true;
 			break;
 		}
-		n_fractions++;
 	}
-	if (n_fractions <= 1)
+	if (n_fractions == 0)
+		goto next_frmsize;
+	if (n_fractions == 1)
 		choice->body.type = SPA_CHOICE_None;
 
 	spa_pod_builder_pop(&b, &f[1]);
@@ -859,6 +867,48 @@ spa_v4l2_enum_format(struct impl *this, int seq,
 	return res;
 }
 
+static int probe_expbuf(struct impl *this)
+{
+	struct port *port = &this->out_ports[0];
+	struct spa_v4l2_device *dev = &port->dev;
+	struct v4l2_requestbuffers reqbuf;
+	struct v4l2_exportbuffer expbuf;
+
+	if (port->probed_expbuf)
+		return 0;
+	port->probed_expbuf = true;
+
+	spa_zero(reqbuf);
+	reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	reqbuf.memory = V4L2_MEMORY_MMAP;
+	reqbuf.count = 2;
+
+	if (xioctl(dev->fd, VIDIOC_REQBUFS, &reqbuf) < 0) {
+		spa_log_error(this->log, "'%s' VIDIOC_REQBUFS: %m", this->props.device);
+		return -errno;
+	}
+
+	spa_zero(expbuf);
+	expbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	expbuf.index = 0;
+	expbuf.flags = O_CLOEXEC | O_RDONLY;
+	if (xioctl(dev->fd, VIDIOC_EXPBUF, &expbuf) < 0) {
+		spa_log_info(this->log, "'%s' EXPBUF not supported: %m", this->props.device);
+		port->have_expbuf = false;
+		port->alloc_buffers = false;
+	} else {
+		port->have_expbuf = true;
+		port->alloc_buffers = true;
+	}
+
+	reqbuf.count = 0;
+	if (xioctl(dev->fd, VIDIOC_REQBUFS, &reqbuf) < 0) {
+		spa_log_error(this->log, "'%s' VIDIOC_REQBUFS: %m", this->props.device);
+		return -errno;
+	}
+	return 0;
+}
+
 static int spa_v4l2_set_format(struct impl *this, struct spa_video_info *format, uint32_t flags)
 {
 	struct port *port = &this->out_ports[0];
@@ -965,6 +1015,8 @@ static int spa_v4l2_set_format(struct impl *this, struct spa_video_info *format,
 	port->rate.denom = framerate->num = streamparm.parm.capture.timeperframe.denominator;
 	port->rate.num = framerate->denom = streamparm.parm.capture.timeperframe.numerator;
 
+	probe_expbuf(this);
+
 	port->fmt = fmt;
 	port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_RATE;
 	port->info.flags = (port->alloc_buffers ? SPA_PORT_FLAG_CAN_ALLOC_BUFFERS : 0) |
@@ -1576,6 +1628,7 @@ mmap_init(struct impl *this,
 
 		spa_log_debug(this->log, "data types %08x", d[0].type);
 
+again:
 		if (port->have_expbuf &&
 		    d[0].type != SPA_ID_INVALID &&
 		    (d[0].type & ((1u << SPA_DATA_DmaBuf)|(1u<<SPA_DATA_MemFd)))) {
@@ -1590,7 +1643,7 @@ mmap_init(struct impl *this,
 					spa_log_debug(this->log, "'%s' VIDIOC_EXPBUF not supported: %m",
 							this->props.device);
 					port->have_expbuf = false;
-					goto fallback;
+					goto again;
 				}
 				spa_log_error(this->log, "'%s' VIDIOC_EXPBUF: %m", this->props.device);
 				return -errno;
@@ -1606,7 +1659,6 @@ mmap_init(struct impl *this,
 			spa_log_debug(this->log, "EXPBUF fd:%d", expbuf.fd);
 			use_expbuf = true;
 		} else if (d[0].type & (1u << SPA_DATA_MemPtr)) {
-fallback:
 			d[0].type = SPA_DATA_MemPtr;
 			d[0].flags = SPA_DATA_FLAG_READABLE;
 			d[0].fd = -1;
@@ -1626,6 +1678,7 @@ fallback:
 			use_expbuf = false;
 		} else {
 			spa_log_error(this->log, "unsupported data type:%08x", d[0].type);
+			port->alloc_buffers = false;
 			return -ENOTSUP;
 		}
 		spa_v4l2_buffer_recycle(this, i);
diff --git a/src/daemon/pipewire-aes67.conf.in b/src/daemon/pipewire-aes67.conf.in
index 3b7e711e0fbe72b2c8d5ded865dbe76d7c604c77..fa2cd67f3329308e1e201b6a92f05a9e5153469b 100644
--- a/src/daemon/pipewire-aes67.conf.in
+++ b/src/daemon/pipewire-aes67.conf.in
@@ -39,6 +39,7 @@ context.objects = [
             #clock.id        = tai
             clock.device    = "/dev/ptp0"
             #clock.interface = "eth0"
+            resync.ms       = 1.5
             object.export   = true
         }
     }
diff --git a/src/daemon/pipewire.conf.in b/src/daemon/pipewire.conf.in
index a9fb1c35dd9e9f3430efd509131b2b0a3d078913..b47bbec44db45bc718d4a7d48a77cb407c9a986b 100644
--- a/src/daemon/pipewire.conf.in
+++ b/src/daemon/pipewire.conf.in
@@ -172,7 +172,7 @@ context.modules = [
     # Use libcanberra to play X11 Bell
     { name = libpipewire-module-x11-bell
         args = {
-            #sink.name = "@DEFAULT_SINK@"
+            #sink.name = "\@DEFAULT_SINK\@"
             #sample.name = "bell-window-system"
             #x11.display = null
             #x11.xauthority = null
diff --git a/src/modules/module-filter-chain/lv2_plugin.c b/src/modules/module-filter-chain/lv2_plugin.c
index 3f036ceaff7bbd07574ae7ff5ad80b2e6fdf62a5..06b6ada5c61c820e9634099edd9ecd8081e81dd8 100644
--- a/src/modules/module-filter-chain/lv2_plugin.c
+++ b/src/modules/module-filter-chain/lv2_plugin.c
@@ -237,6 +237,7 @@ struct instance {
 	const LV2_Worker_Interface *work_iface;
 
 	int32_t block_length;
+	LV2_Atom empty_atom;
 };
 
 static int
@@ -284,7 +285,7 @@ static void *lv2_instantiate(const struct fc_descriptor *desc,
 	struct plugin *p = d->p;
 	struct context *c = p->c;
 	struct instance *i;
-	uint32_t n_features = 0;
+	uint32_t n, n_features = 0;
 	static const int32_t min_block_length = 1;
 	static const int32_t max_block_length = 8192;
 	static const int32_t seq_size = 32768;
@@ -339,6 +340,12 @@ static void *lv2_instantiate(const struct fc_descriptor *desc,
                 i->work_iface = (const LV2_Worker_Interface*)
 			lilv_instance_get_extension_data(i->instance, LV2_WORKER__interface);
         }
+	for (n = 0; n < desc->n_ports; n++) {
+		const LilvPort *port = lilv_plugin_get_port_by_index(p->p, n);
+		if (lilv_port_is_a(p->p, port, c->atom_AtomPort)) {
+			lilv_instance_connect_port(i->instance, n, &i->empty_atom);
+		}
+	}
 
 	return i;
 }
diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c
index 88e6f84c2af3a7e1319edab2b1bf35fb787f6098..4753cc091ce573d0faa8001f5f2fdb5650281104 100644
--- a/src/modules/module-netjack2-driver.c
+++ b/src/modules/module-netjack2-driver.c
@@ -416,7 +416,7 @@ static void make_stream_ports(struct stream *s)
 
 		if (i < s->info.channels) {
 			str = spa_debug_type_find_short_name(spa_type_audio_channel,
-					s->info.position[i]);
+					s->info.position[i % SPA_AUDIO_MAX_CHANNELS]);
 			if (str)
 				snprintf(name, sizeof(name), "%s_%s", prefix, str);
 			else
@@ -819,6 +819,7 @@ static int handle_follower_setup(struct impl *impl, struct nj2_session_params *p
 {
 	int res;
 	struct netjack2_peer *peer = &impl->peer;
+	uint32_t i;
 
 	pw_log_info("got follower setup");
 	nj2_dump_session_params(params);
@@ -842,10 +843,18 @@ static int handle_follower_setup(struct impl *impl, struct nj2_session_params *p
 
 	impl->source.n_ports = peer->params.send_audio_channels + peer->params.send_midi_channels;
 	impl->source.info.rate =  peer->params.sample_rate;
-	impl->source.info.channels =  peer->params.send_audio_channels;
+	if ((uint32_t)peer->params.send_audio_channels != impl->source.info.channels) {
+		impl->source.info.channels = peer->params.send_audio_channels;
+		for (i = 0; i < SPA_MIN(impl->source.info.channels, SPA_AUDIO_MAX_CHANNELS); i++)
+			impl->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i;
+	}
 	impl->sink.n_ports = peer->params.recv_audio_channels + peer->params.recv_midi_channels;
 	impl->sink.info.rate =  peer->params.sample_rate;
-	impl->sink.info.channels =  peer->params.recv_audio_channels;
+	if ((uint32_t)peer->params.recv_audio_channels != impl->sink.info.channels) {
+		impl->sink.info.channels =  peer->params.recv_audio_channels;
+		for (i = 0; i < SPA_MIN(impl->sink.info.channels, SPA_AUDIO_MAX_CHANNELS); i++)
+			impl->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i;
+	}
 	impl->samplerate = peer->params.sample_rate;
 	impl->period_size = peer->params.period_size;
 
@@ -920,7 +929,7 @@ on_socket_io(void *data, int fd, uint32_t mask)
 		if (len < (int)sizeof(struct nj2_session_params))
 			goto short_packet;
 
-		if (strcmp(params.type, "params") != 0)
+		if (strncmp(params.type, "params", sizeof(params.type)) != 0)
 			goto wrong_type;
 
 		switch(ntohl(params.packet_id)) {
diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c
index 3afe6a888d9f0837294bd35603961aa8b254c02d..31310ca0b77e036351d53db0d883fb52ad621642 100644
--- a/src/modules/module-netjack2-manager.c
+++ b/src/modules/module-netjack2-manager.c
@@ -434,7 +434,7 @@ on_setup_io(void *data, int fd, uint32_t mask)
 		if (len < (int)sizeof(params))
 			goto short_packet;
 
-		if (strcmp(params.type, "params") != 0)
+		if (strncmp(params.type, "params", sizeof(params.type)) != 0)
 			goto wrong_type;
 
 		switch(ntohl(params.packet_id)) {
@@ -1072,7 +1072,7 @@ on_socket_io(void *data, int fd, uint32_t mask)
 		if (len < (int)sizeof(params))
 			goto short_packet;
 
-		if (strcmp(params.type, "params") != 0)
+		if (strncmp(params.type, "params", sizeof(params.type)) != 0)
 			goto wrong_type;
 
 		switch(ntohl(params.packet_id)) {
diff --git a/src/modules/module-netjack2/packets.h b/src/modules/module-netjack2/packets.h
index 4943e941a46fd153518a1751cde40354173f43f4..af60a6c7840504a1e5c881f64a56c8fdbec0a073 100644
--- a/src/modules/module-netjack2/packets.h
+++ b/src/modules/module-netjack2/packets.h
@@ -110,7 +110,7 @@ static inline void nj2_session_params_hton(struct nj2_session_params *net,
 }
 
 struct nj2_packet_header {
-	char type[8];			/* packet type ('headr') */
+	char type[8];			/* packet type ('header') */
 	uint32_t data_type;		/* 'a' for audio, 'm' for midi and 's' for sync */
 	uint32_t data_stream;		/* 's' for send, 'r' for return */
 	uint32_t id;			/* unique ID of the follower */
diff --git a/src/modules/module-netjack2/peer.c b/src/modules/module-netjack2/peer.c
index 5ad4bd7f730e67ec30bdd565bd94eb6dce406667..9417323a1c4c9dc03a58142ac192f52ce27da2d9 100644
--- a/src/modules/module-netjack2/peer.c
+++ b/src/modules/module-netjack2/peer.c
@@ -663,7 +663,7 @@ static inline int32_t netjack2_driver_sync_wait(struct netjack2_peer *peer)
 		if (len >= (ssize_t)sizeof(sync)) {
 			//nj2_dump_packet_header(&sync);
 
-			if (strcmp(sync.type, "header") == 0 &&
+			if (strncmp(sync.type, "header", sizeof(sync.type)) == 0 &&
 			    ntohl(sync.data_type) == 's' &&
 			    ntohl(sync.data_stream) == peer->other_stream &&
 			    ntohl(sync.id) == peer->params.id)
@@ -695,7 +695,7 @@ static inline int32_t netjack2_manager_sync_wait(struct netjack2_peer *peer)
 		if (len >= (ssize_t)sizeof(sync)) {
 			//nj2_dump_packet_header(sync);
 
-			if (strcmp(sync.type, "header") == 0 &&
+			if (strncmp(sync.type, "header", sizeof(sync.type)) == 0 &&
 			    ntohl(sync.data_type) == 's' &&
 			    ntohl(sync.data_stream) == peer->other_stream &&
 			    ntohl(sync.id) == peer->params.id)
diff --git a/src/modules/module-pipe-tunnel.c b/src/modules/module-pipe-tunnel.c
index 4acc98c0c572ffd564013944f8c9875ea2fd5268..4935d07f6f8f84a3df0bdd1fb6b93daa6072dbff 100644
--- a/src/modules/module-pipe-tunnel.c
+++ b/src/modules/module-pipe-tunnel.c
@@ -203,6 +203,7 @@ struct impl {
 
 	uint64_t next_time;
 	unsigned int have_sync:1;
+	unsigned int underrun:1;
 };
 
 static uint64_t get_time_ns(struct impl *impl)
@@ -407,7 +408,10 @@ static void capture_stream_process(void *data)
 	if (avail < (int32_t)size) {
 		memset(bd->data, 0, size);
 		if (avail >= 0) {
-			pw_log_warn("underrun %d < %u", avail, size);
+			if (!impl->underrun) {
+				pw_log_warn("underrun %d < %u", avail, size);
+				impl->underrun = true;
+			}
 			pause_stream(impl, true);
 		}
 		impl->have_sync = false;
@@ -429,6 +433,7 @@ static void capture_stream_process(void *data)
 
 		index += avail;
 		spa_ringbuffer_read_update(&impl->ring, index);
+		impl->underrun = false;
 	}
 	bd->chunk->offset = 0;
 	bd->chunk->size = size;
diff --git a/src/modules/module-protocol-pulse/manager.c b/src/modules/module-protocol-pulse/manager.c
index eb1c667ae2c71807fa28da10d04b5752a06028d7..1cf3944e9eae55888fdbd03d5749777fd6e0877d 100644
--- a/src/modules/module-protocol-pulse/manager.c
+++ b/src/modules/module-protocol-pulse/manager.c
@@ -373,7 +373,8 @@ static void device_event_param(void *data, int seq,
 	if (p == NULL)
 		return;
 
-	if (id == SPA_PARAM_Route && !has_param(&o->this.param_list, p)) {
+	if ((id == SPA_PARAM_Route || id == SPA_PARAM_EnumRoute) &&
+	    !has_param(&o->this.param_list, p)) {
 		uint32_t idx, device;
 		if (spa_pod_parse_object(param,
 				SPA_TYPE_OBJECT_ParamRoute, NULL,
diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c
index ccc727eea02f163d84cd3da0b8e424e894b9bd5f..b1a28b91f715761296f411e4ec1abd6b5bcbc5c1 100644
--- a/src/modules/module-raop-sink.c
+++ b/src/modules/module-raop-sink.c
@@ -1521,8 +1521,6 @@ static void stream_state_changed(void *data, bool started, const char *error)
 		pw_impl_module_schedule_destroy(impl->module);
 		return;
 	}
-	if (started)
-		rtsp_do_record(impl);
 }
 
 static int rtsp_do_connect(struct impl *impl)
diff --git a/src/pipewire/impl-port.c b/src/pipewire/impl-port.c
index 8add1cd2a5f0f539720f4658e55456ca578c23cb..1359f3c82cfd80d00c8e4d1fe3ad16476e41a03f 100644
--- a/src/pipewire/impl-port.c
+++ b/src/pipewire/impl-port.c
@@ -1716,7 +1716,7 @@ int pw_impl_port_set_param(struct pw_impl_port *port, uint32_t id, uint32_t flag
 	pw_log_debug("%p: %d set param on node %d:%d id:%d (%s): %d (%s)", port, port->state,
 			port->direction, port->port_id, id,
 			spa_debug_type_find_name(spa_type_param, id),
-			res, spa_strerror(res));
+			res, res <= 0 ? spa_strerror(res) : "modified");
 
 	/* set the parameters on all ports of the mixer node if possible */
 	if (res >= 0) {
diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c
index 39e603b2d4d3e8d34842372884cf44a06d8eeae7..d3338074912a59a0f2fcee8487b8ce1278273912 100644
--- a/src/pipewire/stream.c
+++ b/src/pipewire/stream.c
@@ -2199,9 +2199,10 @@ int pw_stream_update_params(struct pw_stream *stream,
 	if ((res = update_params(impl, SPA_ID_INVALID, params, n_params)) < 0)
 		return res;
 
-	emit_node_info(impl, false);
-	emit_port_info(impl, false);
-
+	if (impl->in_emit_param_changed == 0) {
+		emit_node_info(impl, false);
+		emit_port_info(impl, false);
+	}
 	return res;
 }
 
diff --git a/src/pipewire/thread.c b/src/pipewire/thread.c
index 4f753a9f97a9b22740035174ce9878954c668e90..f7b8c60ad00506465e55ee12c51387ccfa450488 100644
--- a/src/pipewire/thread.c
+++ b/src/pipewire/thread.c
@@ -102,13 +102,13 @@ static int impl_get_rt_range(void *object, const struct spa_dict *props,
 }
 static int impl_acquire_rt(void *object, struct spa_thread *thread, int priority)
 {
-	pw_log_warn("acquire_rt thread:%p prio:%d not implemented", thread, priority);
+	pw_log_info("acquire_rt thread:%p prio:%d not implemented", thread, priority);
 	return -ENOTSUP;
 }
 
 static int impl_drop_rt(void *object, struct spa_thread *thread)
 {
-	pw_log_warn("drop_rt thread:%p not implemented", thread);
+	pw_log_info("drop_rt thread:%p not implemented", thread);
 	return -ENOTSUP;
 }