From 5644881bcb7b2ccf1d004868884ac2cd53fb2757 Mon Sep 17 00:00:00 2001 From: Gaggery Tsai Date: Fri, 27 Mar 2026 15:22:57 -0700 Subject: [PATCH] v4l2-relayd: reset output pipeline on reader disconnect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../0001-reset-output-on-idle.patch | 37 +++++++++++++++++++ pkgbuilds/edge/v4l2-relayd/PKGBUILD | 13 +++++-- 2 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 pkgbuilds/edge/v4l2-relayd/0001-reset-output-on-idle.patch diff --git a/pkgbuilds/edge/v4l2-relayd/0001-reset-output-on-idle.patch b/pkgbuilds/edge/v4l2-relayd/0001-reset-output-on-idle.patch new file mode 100644 index 0000000..e5f83f4 --- /dev/null +++ b/pkgbuilds/edge/v4l2-relayd/0001-reset-output-on-idle.patch @@ -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); + } diff --git a/pkgbuilds/edge/v4l2-relayd/PKGBUILD b/pkgbuilds/edge/v4l2-relayd/PKGBUILD index b1ea252..aa56c90 100644 --- a/pkgbuilds/edge/v4l2-relayd/PKGBUILD +++ b/pkgbuilds/edge/v4l2-relayd/PKGBUILD @@ -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}"