v4l2-relayd: reset output pipeline on reader disconnect

When the last camera reader disconnects, cycle the output pipeline
(v4l2sink) through READY → PLAYING to reset v4l2loopback's format
state. Without this, switching between camera apps that use different
PipeWire paths (e.g. Chromium → GNOME Snapshot) fails with "no more
input formats" because v4l2loopback retains stale format state from
the previous session.

The reset is guarded by an input_was_active flag so it only triggers
after an actual streaming session, not during PipeWire's brief device
probes on startup.

Tested on Dell XPS 16 (Panther Lake) with PipeWire 1.6.2:
- Snapshot → Zoom → Snapshot: working
- Suspend → resume → Snapshot: working
- Boot → first Snapshot: 30fps
This commit is contained in:
Gaggery Tsai committed 2026-03-30 10:09:12 -07:00
1 parent 3c7c381965
commit 5644881bcb
2 files changed
+47 -3

No files matched your search

@@ -0,0 +1,37 @@
--- src/v4l2-relayd.c.orig 2026-03-30 09:59:14.804751921 -0700
+++ src/v4l2-relayd.c 2026-03-30 09:59:14.827617699 -0700
@@ -58,6 +58,7 @@
static GstElement *input_pipeline = NULL;
static GstElement *output_pipeline = NULL;
static GstElement *splash_pipeline = NULL;
+static GTimer *input_timer = NULL;
static gboolean backend_pipeline_bus_call (GstBus *bus,
GstMessage *msg,
@@ -278,6 +279,10 @@
{
gst_element_set_state (splash_pipeline_get (), GST_STATE_NULL);
gst_element_set_state (input_pipeline_get (), GST_STATE_PLAYING);
+ if (input_timer == NULL)
+ input_timer = g_timer_new ();
+ else
+ g_timer_start (input_timer);
}
static void
@@ -285,6 +290,15 @@
{
if (input_pipeline != NULL)
gst_element_set_state (input_pipeline, GST_STATE_NULL);
+ /* After a real streaming session (>3s), reset the output pipeline
+ * to clear stale v4l2loopback state that causes format negotiation
+ * failures when switching between camera apps. Brief probes from
+ * PipeWire/WirePlumber (<1s) are skipped. */
+ if (input_timer != NULL && g_timer_elapsed (input_timer, NULL) > 3.0
+ && output_pipeline != NULL) {
+ gst_element_set_state (output_pipeline, GST_STATE_READY);
+ gst_element_set_state (output_pipeline, GST_STATE_PLAYING);
+ }
if (input_pipeline != NULL)
gst_element_set_state (splash_pipeline, GST_STATE_PLAYING);
}
+10 -3
View File
@@ -2,7 +2,7 @@
pkgname=v4l2-relayd
pkgver=0.2.0
pkgrel=1
pkgrel=2
pkgdesc="Plays gstreamer sources into v4l2loopback devices."
arch=('x86_64')
url="https://gitlab.com/vicamo/v4l2-relayd"
@@ -15,8 +15,15 @@ depends=(
'v4l2loopback-dkms'
)
makedepends=('git')
source=("https://gitlab.com/vicamo/v4l2-relayd/-/archive/upstream/${pkgver}/v4l2-relayd-upstream-${pkgver}.tar.gz")
sha256sums=('0c063edf18dcc6edcdef46e695128cfc2b2d60964ea8538c7e79a2454310c53d')
source=("https://gitlab.com/vicamo/v4l2-relayd/-/archive/upstream/${pkgver}/v4l2-relayd-upstream-${pkgver}.tar.gz"
"0001-reset-output-on-idle.patch")
sha256sums=('0c063edf18dcc6edcdef46e695128cfc2b2d60964ea8538c7e79a2454310c53d'
'SKIP')
prepare() {
cd "$srcdir/${pkgname}-upstream-${pkgver}"
patch -p0 < "$srcdir/0001-reset-output-on-idle.patch"
}
build() {
cd "$srcdir/${pkgname}-upstream-${pkgver}"