From patchwork Mon Oct 5 15:40:29 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Scally X-Patchwork-Id: 28404 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 706C1C3305 for ; Mon, 5 Oct 2026 15:40:40 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 590E668DE1; Mon, 5 Oct 2026 17:40:39 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="FnEM2s7H"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6705C68A44 for ; Mon, 5 Oct 2026 17:40:36 +0200 (CEST) Received: from [127.0.1.1] (chfd-03-b2-v4wan-176392-cust229.vm15.cable.virginm.net [82.19.20.230]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C1177227; Mon, 5 Oct 2026 17:38:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1791214720; bh=ah/soejvY7XsVw6SPnRITDAf5dZY4nYc0kcnXFjzFgM=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=FnEM2s7H2LjIQKLjxD2CmBOL1Pn3wt1WD9+CS8el8LsxKuDvB7SSPwImRV6NgLQUJ 6FD4K9Ww5W/sBt/A8wpevJUp75Iuy2cEznAjOkJzC63K+uCifDfY1+N7CetSP6hMic tljJGYhLk2D7QSIzEIYwpJFUH5ae/I294U+W3n5k= From: Daniel Scally Date: Mon, 05 Oct 2026 16:40:29 +0100 Subject: [PATCH v2 1/3] libcamera: v4l2_device: Make supportsFrameStartEvent() generic MIME-Version: 1.0 Message-Id: <20261005-v4l2-events-v2-1-e4883190489d@ideasonboard.com> References: <20261005-v4l2-events-v2-0-e4883190489d@ideasonboard.com> In-Reply-To: <20261005-v4l2-events-v2-0-e4883190489d@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Daniel Scally X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=3543; i=dan.scally@ideasonboard.com; h=from:subject:message-id; bh=ah/soejvY7XsVw6SPnRITDAf5dZY4nYc0kcnXFjzFgM=; b=owEBbQKS/ZANAwAKAchJV3psRXUyAcsmYgBqw8TzQO/CghOt11UX5i3dKaof3rCQYgP3ZUekk oRDsZ8tuSGJAjMEAAEKAB0WIQQqyuwyDnZdb+mxmm/ISVd6bEV1MgUCasPE8wAKCRDISVd6bEV1 MgiaD/9Xec13n2Uz1YfqY963TvcqXJGQYCyWxiEWW0Qty6OHTOgOXU2gFOiZjGmvClyrCgVngUa Ddj9DvkpdhbHDdZeWdMZ1+5YcMdjw3GG1Pol9acBh5Ki2nApfTS/Q2eZAKtwHv1pWN6qfS1gkuJ e/JR9aXtRbJaswng8ukXUDVbvtsfsYVYyit7IUgQoHER+Zo5UPv1duksLOs4gIFyCg95M51fwwh qcbnpo56/uER8CKAfV1XNTzGsf8yqIYCFvI6ae7BKEM7UDio+4Blt40y6v5br9OeCYoOo2XIzKf 1HTW2gSivGAaEy1Kixs/GMG4XFXjy2Bo46jcIpmv6AQnik9S+G478S7TRTeR1cGEyDb/ht3rbFp pwdbbzEq8cAYtKmPLTjOJtVpK+BBrZ563gt+4rPXyZ85lv5pIRRss9GlpSYFyzLp6c4TWKozFbf wPyunvP6oqGkBrotMUnXrN0/jN2cgPx56BaER4NmQbjBR0VOcaJ8IfmnZAhT7+b1mPbp+VqGZa1 bkskclpF0sWP/cTR4n8qe8SPO+LzRic1/fJCOkFvCnw0CqwwYyprBbC9mgDLDt7gG5hYQScnQ8M v7CMZS2tl6kAddjkVarLs8/IkQ1paSdRVzvslYXrknS89sL8V8u2mzlgQAfEbw/ai3A4mA9NbBp +e1mpC+IZePC+HA== X-Developer-Key: i=dan.scally@ideasonboard.com; a=openpgp; fpr=EEC699ACA1B7CB5D31330C0BBD501C2A3546CCF6 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Replace the existing V4l2Device::supportsFrameStartEvent() function with a generic function with a v4l2_event_subscription parameter that allows users to check whether the V4L2Device supports event subscriptions for any type. Signed-off-by: Daniel Scally --- Changes in v2: - Use kernel uAPI types directly instead of V4L2Event and V4L2EventSubscription classes --- include/libcamera/internal/v4l2_device.h | 2 +- src/libcamera/pipeline/simple/simple.cpp | 7 ++++++- src/libcamera/v4l2_device.cpp | 19 ++++++++----------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/include/libcamera/internal/v4l2_device.h b/include/libcamera/internal/v4l2_device.h index 1a63fc547eac579fabf50421b719d07ddb2d6639..03e4d3dbb480557de98a5fa16fd41d4f2e7431c5 100644 --- a/include/libcamera/internal/v4l2_device.h +++ b/include/libcamera/internal/v4l2_device.h @@ -46,7 +46,7 @@ public: const std::string &deviceNode() const { return deviceNode_; } std::string devicePath() const; - bool supportsFrameStartEvent(); + bool supportsEvents(v4l2_event_subscription &sub); int setFrameStartEnabled(bool enable); Signal frameStart; diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp index 35c29ceca1bb1f9fb09e8c547b603ee8a2b67774..4631d5450b4ac4c47a78a631917d8b226a5269b0 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -22,6 +22,7 @@ #include #include +#include #include @@ -668,7 +669,11 @@ int SimpleCameraData::init() frameStartEmitter_ = nullptr; for (const Entity &entity : entities_) { V4L2Subdevice *sd = pipe->subdev(entity.entity); - if (!sd || !sd->supportsFrameStartEvent()) + + v4l2_event_subscription sub{}; + sub.type = V4L2_EVENT_FRAME_SYNC; + + if (!sd || !sd->supportsEvents(sub)) continue; LOG(SimplePipeline, Debug) diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp index 258c82b8d238156038909a4b7b7aa842c99bd677..225b938649dcfe86f5528c3a82ed1b5ca0c94a5a 100644 --- a/src/libcamera/v4l2_device.cpp +++ b/src/libcamera/v4l2_device.cpp @@ -470,24 +470,21 @@ std::string V4L2Device::devicePath() const } /** - * \brief Check if frame start event is supported + * \brief Check if an event subscription is supported * - * Due to limitations in the kernel API, this function may disable the frame - * start event as a side effect. It should only be called during initialization, - * before enabling the frame start event with setFrameStartEnabled(). + * Due to limitations in the kernel API, this function may disable the events as + * a side effect. It should only be called during initialization, before + * enabling the events explicitly. * - * \return True if frame start event is supported, false otherwise + * \return True if the subscription is supported, false otherwise */ -bool V4L2Device::supportsFrameStartEvent() +bool V4L2Device::supportsEvents(v4l2_event_subscription &sub) { - struct v4l2_event_subscription event{}; - event.type = V4L2_EVENT_FRAME_SYNC; - - int ret = ioctl(VIDIOC_SUBSCRIBE_EVENT, &event); + int ret = ioctl(VIDIOC_SUBSCRIBE_EVENT, &sub); if (ret) return false; - ioctl(VIDIOC_UNSUBSCRIBE_EVENT, &event); + ioctl(VIDIOC_UNSUBSCRIBE_EVENT, &sub); return true; } From patchwork Mon Oct 5 15:40:30 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Scally X-Patchwork-Id: 28405 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id E9C41C3307 for ; Mon, 5 Oct 2026 15:40:41 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1D54868AA8; Mon, 5 Oct 2026 17:40:40 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="PF9Avl12"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id B51A168A6E for ; Mon, 5 Oct 2026 17:40:36 +0200 (CEST) Received: from [127.0.1.1] (chfd-03-b2-v4wan-176392-cust229.vm15.cable.virginm.net [82.19.20.230]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 0E2985E; Mon, 5 Oct 2026 17:38:41 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1791214721; bh=3BOX1fP/oxYIDWLWI7IxHoZTsGq48zBR2uNiLy7ELmw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=PF9Avl12fRToKsTs4vjkMzF+o9+wePnkOVijB+rbNj7Z8BFtKXeoL4QbU5iKVoX7M 74PpRmMn62Fztsg/nse0Sx9pgAFwKEvuv5HCV/dNRZYHSOthSFW9+4GQzXwt/DCPTB UHscJ5yMUBUOzg8v/0W3Gah+7fJJ9KMy8NXwY7To= From: Daniel Scally Date: Mon, 05 Oct 2026 16:40:30 +0100 Subject: [PATCH v2 2/3] libcamera: v4l2_device: Emit signals for V4L2Events generically MIME-Version: 1.0 Message-Id: <20261005-v4l2-events-v2-2-e4883190489d@ideasonboard.com> References: <20261005-v4l2-events-v2-0-e4883190489d@ideasonboard.com> In-Reply-To: <20261005-v4l2-events-v2-0-e4883190489d@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Daniel Scally X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=14040; i=dan.scally@ideasonboard.com; h=from:subject:message-id; bh=3BOX1fP/oxYIDWLWI7IxHoZTsGq48zBR2uNiLy7ELmw=; b=owEBbQKS/ZANAwAKAchJV3psRXUyAcsmYgBqw8Tz4FVEGtGkByBQHtEW7evr4VpLruhxt7EU2 6PzHNzhM7mJAjMEAAEKAB0WIQQqyuwyDnZdb+mxmm/ISVd6bEV1MgUCasPE8wAKCRDISVd6bEV1 MkhXD/9m9EJDzNXpFDs4zSsT0+xclpY7Ay6hlOicbBJOQX3N9sU/8jHxbO87BOW6RE7qnm/aV4t jCaEp6M56BD/UI4PRf/xHDtV7Uc/SlXVS2N3idqjOPHTFaEBXlk7kbbF0RLS6lurJZwQvSH8Fh0 1/+U5d+EUnydWsDgT0M3wvVNtnN/55hVbcanUURVEmJp0w/P/KMrqcXcGb3c64/UV2ZaGjfwIJY NlxWwr9LTrx4TSrFoxab9t/lN+TTQQBbGJpSu/fy+E5utUTtiFo8Y6RY6xq1cXW2kNfZKzpw/xn RGwnJjvQL2QPm4C2rv0Rvw32p0VOWGVJDzXu7G2C45ZsIlFLOGwsgVzV7+vHBOWPghTg72h9krd ASZgnlmVVk1d+F6Xe72V5CfzF9i7ZCn3rUMjI37rXPM6JKsOaCryMtBeLQ0Vo6MrDCQ6yvHEeAF n/3k6C3sc7sVAaoOINPgvtvwG0VX/l4xqC6HeSpe759KXOl+XizUb2sMCA/wA+OiM0ado+j6SDb eFTzpMGJvED4bRLCh/MRxYjRAwouPmvQyco7Gpx+6wCjsgznZ087iK4UVnKUHylIdB+wo4AzWBg gYbB7wT6SbcJLWZccNYCEjBMJsln6kabIVibeGWZKCRuptWStADzFTT8A63pDe8uqF70XbnFo2q mpo8n1hZ8n3QbmQ== X-Developer-Key: i=dan.scally@ideasonboard.com; a=openpgp; fpr=EEC699ACA1B7CB5D31330C0BBD501C2A3546CCF6 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Currently V4L2Device emits a signal for the V4L2_EVENT_FRAME_SYNC event, with a Signal signature that is specific to it. Replace this with a generic Signal and leave the Slots to decide how to handle the event. This will allow us to easily extend handling to other events in the future. Update the existing slots to handle the new Signal signature. Signed-off-by: Daniel Scally --- Changes in v2: - Reworked to drop V4L2Event class and simply use the kernel uAPI directly for the new Signal. --- include/libcamera/internal/v4l2_device.h | 2 +- src/libcamera/pipeline/ipu3/cio2.h | 4 +++- src/libcamera/pipeline/ipu3/ipu3.cpp | 18 ++++++++++++------ src/libcamera/pipeline/mali-c55/mali-c55.cpp | 18 ++++++++++++++---- src/libcamera/pipeline/rkisp1/rkisp1.cpp | 14 ++++++++++++-- src/libcamera/pipeline/rpi/common/pipeline_base.cpp | 13 +++++++++---- src/libcamera/pipeline/rpi/common/pipeline_base.h | 4 +++- src/libcamera/pipeline/simple/simple.cpp | 19 +++++++++++++++---- src/libcamera/v4l2_device.cpp | 16 ++++------------ 9 files changed, 73 insertions(+), 35 deletions(-) diff --git a/include/libcamera/internal/v4l2_device.h b/include/libcamera/internal/v4l2_device.h index 03e4d3dbb480557de98a5fa16fd41d4f2e7431c5..6b77dd46f654e4b95db0327e73164719c47a77d9 100644 --- a/include/libcamera/internal/v4l2_device.h +++ b/include/libcamera/internal/v4l2_device.h @@ -48,7 +48,7 @@ public: bool supportsEvents(v4l2_event_subscription &sub); int setFrameStartEnabled(bool enable); - Signal frameStart; + Signal eventReady; void updateControlInfo(); diff --git a/src/libcamera/pipeline/ipu3/cio2.h b/src/libcamera/pipeline/ipu3/cio2.h index 91651a16404e2c524b13829b4909474e579ad217..a3165fcc49c571b10fd104c025f7004d3b70341f 100644 --- a/src/libcamera/pipeline/ipu3/cio2.h +++ b/src/libcamera/pipeline/ipu3/cio2.h @@ -16,6 +16,8 @@ #include "libcamera/internal/v4l2_subdevice.h" #include "libcamera/internal/v4l2_videodevice.h" +struct v4l2_event; + namespace libcamera { class CameraSensor; @@ -58,7 +60,7 @@ public: FrameBuffer *queueBuffer(FrameBuffer *rawBuffer); void tryReturnBuffer(FrameBuffer *buffer); Signal &bufferReady() { return output_->bufferReady; } - Signal &frameStart() { return csi2_->frameStart; } + Signal eventReady() { return csi2_->eventReady; } Signal<> bufferAvailable; diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp index 14cab9e5559b809328e1a408368c906b72e6c027..e48fa327055e372f42487ffc09f55441cc8e3871 100644 --- a/src/libcamera/pipeline/ipu3/ipu3.cpp +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -63,7 +64,7 @@ public: void statBufferReady(FrameBuffer *buffer); void queuePendingRequests(); void cancelPendingRequests(); - void frameStart(uint32_t sequence); + void handleEvent(const v4l2_event &event); CIO2Device cio2_; ImgUDevice *imgu_; @@ -1089,8 +1090,8 @@ int PipelineHandlerIPU3::registerCameras() data->delayedCtrls_ = std::make_unique(cio2->sensor()->device(), params); - data->cio2_.frameStart().connect(data.get(), - &IPU3CameraData::frameStart); + data->cio2_.eventReady().connect(data.get(), + &IPU3CameraData::handleEvent); /* Convert the sensor rotation to a transformation */ const auto &rotation = data->properties_.get(properties::Rotation); @@ -1313,7 +1314,7 @@ void IPU3CameraData::cio2BufferReady(FrameBuffer *buffer) * Record the sensor's timestamp in the request metadata. * * \todo The sensor timestamp should be better estimated by connecting - * to the V4L2Device::frameStart signal. + * to the V4L2Device::eventReady signal for frame sync events. */ request->_d()->metadata().set(controls::SensorTimestamp, buffer->metadata().timestamp); @@ -1382,9 +1383,14 @@ void IPU3CameraData::statBufferReady(FrameBuffer *buffer) * TestPatternMode one. Other controls are handled through the delayed * controls class. */ -void IPU3CameraData::frameStart(uint32_t sequence) + +void IPU3CameraData::handleEvent(const v4l2_event &event) { - delayedCtrls_->applyControls(sequence); + if (event.type != V4L2_EVENT_FRAME_SYNC) + return; + + auto frameSyncEvent = &event.u.frame_sync; + delayedCtrls_->applyControls(frameSyncEvent->frame_sequence); if (processingRequests_.empty()) return; diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp index 73a03373c8334ea41478d18047c90c97aeb52c2e..da3153ad7fafc133cdde6accb5e3bd54f80520c9 100644 --- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -198,6 +199,8 @@ public: PixelFormat adjustRawFormat(const PixelFormat &pixFmt) const; Size adjustRawSizes(const PixelFormat &pixFmt, const Size &rawSize) const; + void handleEvent(const v4l2_event &event); + Stream frStream_; Stream dsStream_; @@ -451,6 +454,15 @@ Size MaliC55CameraData::adjustRawSizes(const PixelFormat &rawFmt, const Size &si return bestSize; } +void MaliC55CameraData::handleEvent(const v4l2_event &event) +{ + if (event.type != V4L2_EVENT_FRAME_SYNC) + return; + + auto frameSyncEvent = &event.u.frame_sync; + delayedCtrls_->applyControls(frameSyncEvent->frame_sequence); +} + int MaliC55CameraData::loadIPA() { int ret; @@ -1849,8 +1861,7 @@ bool PipelineHandlerMaliC55::registerSensorCamera(MediaLink *ispLink) V4L2Subdevice *sensorSubdev = in->sensor_->device(); data->delayedCtrls_ = std::make_unique(sensorSubdev, params); - isp_->frameStart.connect(data->delayedCtrls_.get(), - &DelayedControls::applyControls); + isp_->eventReady.connect(data.get(), &MaliC55CameraData::handleEvent); /* \todo Init properties. */ @@ -1913,8 +1924,7 @@ bool PipelineHandlerMaliC55::registerMemoryInputCamera(MediaLink *link) data->delayedCtrls_ = std::make_unique(sensor->device(), params); - isp_->frameStart.connect(data->delayedCtrls_.get(), - &DelayedControls::applyControls); + isp_->eventReady.connect(data.get(), &MaliC55CameraData::handleEvent); ivc_->bufferReady.connect(mem->cru_.get(), &RZG2LCRU::returnBuffer); diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp index 96382c93a4273297e439891d9cea0287fd84fdad..e49dd546ee0a5207c75a364564d5673fd8463b30 100644 --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp @@ -106,6 +106,8 @@ public: const PipelineHandlerRkISP1 *pipe() const; int loadIPA(unsigned int hwRevision, uint32_t supportedBlocks); + void handleEvent(const v4l2_event &event); + Stream mainPathStream_; Stream selfPathStream_; std::unique_ptr sensor_; @@ -430,6 +432,15 @@ int RkISP1CameraData::loadIPA(unsigned int hwRevision, uint32_t supportedBlocks) return 0; } +void RkISP1CameraData::handleEvent(const v4l2_event &event) +{ + if (event.type != V4L2_EVENT_FRAME_SYNC) + return; + + auto frameSyncEvent = &event.u.frame_sync; + delayedCtrls_->applyControls(frameSyncEvent->frame_sequence); +} + int RkISP1CameraData::loadTuningFile(const std::string &path) { int ret; @@ -1472,8 +1483,7 @@ int PipelineHandlerRkISP1::createCamera(MediaEntity *sensor) data->delayedCtrls_ = std::make_unique(data->sensor_->device(), params); - isp_->frameStart.connect(data->delayedCtrls_.get(), - &DelayedControls::applyControls); + isp_->eventReady.connect(data.get(), &RkISP1CameraData::handleEvent); uint32_t supportedBlocks = kDefaultExtParamsBlocks; diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp index 71f3f26a4ffeb933176665677231549269782369..2315a1c47dc26ed88fa149106a25dcf1ffbc3ec0 100644 --- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp @@ -873,7 +873,7 @@ int PipelineHandlerBase::registerCamera(std::unique_ptr &camera /* Setup the general IPA signal handlers. */ data->frontendDevice()->dequeueTimeout.connect(data, &RPi::CameraData::cameraTimeout); - data->frontendDevice()->frameStart.connect(data, &RPi::CameraData::frameStarted); + data->frontendDevice()->eventReady.connect(data, &RPi::CameraData::handleEvent); data->ipa_->setDelayedControls.connect(data, &CameraData::setDelayedControls); data->ipa_->setLensControls.connect(data, &CameraData::setLensControls); data->ipa_->metadataReady.connect(data, &CameraData::metadataReady); @@ -1392,12 +1392,17 @@ void CameraData::cameraTimeout() clearIncompleteRequests(); } -void CameraData::frameStarted(uint32_t sequence) +void CameraData::handleEvent(const v4l2_event &event) { - LOG(RPI, Debug) << "Frame start " << sequence; + if (event.type != V4L2_EVENT_FRAME_SYNC) + return; + + auto frameSyncEvent = &event.u.frame_sync; + + LOG(RPI, Debug) << "Frame start " << frameSyncEvent->frame_sequence; /* Write any controls for the next frame as soon as we can. */ - delayedCtrls_->applyControls(sequence); + delayedCtrls_->applyControls(frameSyncEvent->frame_sequence); } void CameraData::clearIncompleteRequests() diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.h b/src/libcamera/pipeline/rpi/common/pipeline_base.h index ca12a4875faf4f1b8b8ad1d637a20414857fe2ff..f076721c659330150b01ff4079a83a7ac719e9d3 100644 --- a/src/libcamera/pipeline/rpi/common/pipeline_base.h +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.h @@ -36,6 +36,8 @@ using namespace std::chrono_literals; +struct v4l2_event; + namespace libcamera { namespace RPi { @@ -86,7 +88,7 @@ public: virtual void platformSetIspCrop(unsigned int index, const Rectangle &ispCrop) = 0; void cameraTimeout(); - void frameStarted(uint32_t sequence); + void handleEvent(const v4l2_event &event); void clearIncompleteRequests(); void handleStreamBuffer(FrameBuffer *buffer, Stream *stream); diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp index 4631d5450b4ac4c47a78a631917d8b226a5269b0..75e068bf516d80af6c8aeeb94bf00ba265744fdd 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -299,6 +299,8 @@ public: return stream - &streams_.front(); } + void handleEvent(const v4l2_event &event); + struct Entity { /* The media entity, always valid. */ MediaEntity *entity; @@ -988,6 +990,15 @@ void SimpleCameraData::clearIncompleteRequests() } } +void SimpleCameraData::handleEvent(const v4l2_event &event) +{ + if (event.type != V4L2_EVENT_FRAME_SYNC) + return; + + auto frameSyncEvent = &event.u.frame_sync; + delayedCtrls_->applyControls(frameSyncEvent->frame_sequence); +} + void SimpleCameraData::tryCompleteRequest(Request *request) { if (request->hasPendingBuffers()) @@ -1676,8 +1687,8 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL stop(camera); return ret; } - frameStartEmitter->frameStart.connect(data->delayedCtrls_.get(), - &DelayedControls::applyControls); + frameStartEmitter->eventReady.connect(data, + &SimpleCameraData::handleEvent); } ret = video->streamOn(); @@ -1716,8 +1727,8 @@ void SimplePipelineHandler::stopDevice(Camera *camera) if (frameStartEmitter) { frameStartEmitter->setFrameStartEnabled(false); - frameStartEmitter->frameStart.disconnect(data->delayedCtrls_.get(), - &DelayedControls::applyControls); + frameStartEmitter->eventReady.connect(data, + &SimpleCameraData::handleEvent); } if (data->useConversion_) { diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp index 225b938649dcfe86f5528c3a82ed1b5ca0c94a5a..192edd802a22d3edc7055132eb84ca00e095dd26 100644 --- a/src/libcamera/v4l2_device.cpp +++ b/src/libcamera/v4l2_device.cpp @@ -493,7 +493,7 @@ bool V4L2Device::supportsEvents(v4l2_event_subscription &sub) * \param[in] enable True to enable frame start events, false to disable them * * This function enables or disables generation of frame start events. Once - * enabled, the events are signalled through the frameStart signal. + * enabled, the events are signalled through the eventReady signal. * * \return 0 on success, a negative error code otherwise */ @@ -518,8 +518,8 @@ int V4L2Device::setFrameStartEnabled(bool enable) } /** - * \var V4L2Device::frameStart - * \brief A Signal emitted when capture of a frame has started + * \var V4L2Device::eventReady + * \brief A Signal emitted when a V4L2 event is received */ /** @@ -857,15 +857,7 @@ void V4L2Device::eventAvailable() return; } - if (event.type != V4L2_EVENT_FRAME_SYNC) { - LOG(V4L2, Error) - << "Spurious event (" << event.type - << "), disabling event notifier"; - fdEventNotifier_->setEnabled(false); - return; - } - - frameStart.emit(event.u.frame_sync.frame_sequence); + eventReady.emit(event); } static const std::map v4l2ToColorSpace = { From patchwork Mon Oct 5 15:40:31 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Scally X-Patchwork-Id: 28406 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id C5C86C32F4 for ; Mon, 5 Oct 2026 15:40:42 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id BD2D668A8E; Mon, 5 Oct 2026 17:40:40 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="fXMtaH7S"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 03486689D8 for ; Mon, 5 Oct 2026 17:40:37 +0200 (CEST) Received: from [127.0.1.1] (chfd-03-b2-v4wan-176392-cust229.vm15.cable.virginm.net [82.19.20.230]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5B280227; Mon, 5 Oct 2026 17:38:41 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1791214721; bh=74t+50F/pujto0JB6Rb3TftL1/tQE0qbDX9AxroL5lg=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=fXMtaH7SbuUcpI/xOyFuxpo88TR28uydRru9IBjxlI7R+lccFCXWfdKGu4zWCGpev ycU41aFya6Z3pQw1pVA+oHHw62vk88MO3MOYA9+tS5Qu6Czip8BX7mSGJhxVaX5TgZ /f7WYpX3TxYg1KdKpiGpYVEU/oC6jc+aropRcxxI= From: Daniel Scally Date: Mon, 05 Oct 2026 16:40:31 +0100 Subject: [PATCH v2 3/3] libcamera: v4l2_device: Make setFrameStartEnabled() generic MIME-Version: 1.0 Message-Id: <20261005-v4l2-events-v2-3-e4883190489d@ideasonboard.com> References: <20261005-v4l2-events-v2-0-e4883190489d@ideasonboard.com> In-Reply-To: <20261005-v4l2-events-v2-0-e4883190489d@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Daniel Scally X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=9567; i=dan.scally@ideasonboard.com; h=from:subject:message-id; bh=74t+50F/pujto0JB6Rb3TftL1/tQE0qbDX9AxroL5lg=; b=owEBbQKS/ZANAwAKAchJV3psRXUyAcsmYgBqw8TzkeJ/uCbmSvf5MUd3uii65MnS64+SexBW+ GKiTDQGX2OJAjMEAAEKAB0WIQQqyuwyDnZdb+mxmm/ISVd6bEV1MgUCasPE8wAKCRDISVd6bEV1 MsT5D/9eYmcggWt+vmtdAcbq+dS/EAbsPScUkF272wyxib4+3/mncX9JSPYu5EtkgrSwkfVuvEW pPZAAR5/A/g34szxzEex2BigDCr8OkQ2PJFW+nUaagGm3mstsFIYPruesA4na4Iv/WXjSCBDc/J C5jexMp4akEJzEAalaZryfzyh2ADxTv+0IAH0ol3kK55cj1cmXsPr82MQH52GZbqNmBL8NnixxM 90Jmrp0hHyN9qviB9zptiQDHt6a0WG0eIt/baRFTydBXqKXZmiAl3XXQFMr09UqlFK1WeboRLcI TKD3hYq3bF65QjV3BEFElJ2ucGh9kj9aUkfhy3oHv2x//7z0cPtrqM2QqXsU9nZO4svBAFVUYaR ihHeFu2MyIQQqFC3/8bqoBcng5rflPmSPKcDrkuZpZZ+rBcS2WmeiDF9KHCmFVBDCUvMxDArc0i X2RO5HLpzQGcORE4U4ijIReIKaGYUB/Ayym4Adlih8iW31B99FvTZrcwVVQv4RNezd4rXx25ZgR +kWULkaK9R5Ggn7KNlQBPNipOKPO3ylPD30CHQ3X3/IUV3h3OxZMrFiC8DewpFL0GX9oj9jYbr7 Xx6KKeC50vdDKLnAYRgpAyOwHzHA8GLLgUkqnqIbrftytuwd63haw9sCVhL5IqSF6YahUgsusPC l7ZqDnK2RIRBQCQ== X-Developer-Key: i=dan.scally@ideasonboard.com; a=openpgp; fpr=EEC699ACA1B7CB5D31330C0BBD501C2A3546CCF6 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" The current implementation of V4L2Device::setFrameStartEnabled hard codes V4L2_EVENT_FRAME_SYNC as it passes the subscription to the kernel. Re-work the function such that the subscription details are parametised. This will allow consumers to subscribe to any supported events. Signed-off-by: Daniel Scally --- Changes in v2: - use kernel uAPI directly instead of class V4L2Event and V4L2EventSubscription --- include/libcamera/internal/v4l2_device.h | 5 ++-- src/libcamera/pipeline/ipu3/cio2.cpp | 9 +++++-- src/libcamera/pipeline/mali-c55/mali-c55.cpp | 10 ++++++-- src/libcamera/pipeline/rkisp1/rkisp1.cpp | 10 +++++--- .../pipeline/rpi/common/pipeline_base.cpp | 8 +++++-- src/libcamera/pipeline/simple/simple.cpp | 8 +++++-- src/libcamera/v4l2_device.cpp | 28 ++++++++++++---------- 7 files changed, 53 insertions(+), 25 deletions(-) diff --git a/include/libcamera/internal/v4l2_device.h b/include/libcamera/internal/v4l2_device.h index 6b77dd46f654e4b95db0327e73164719c47a77d9..124f6cd9e477b8881de8f7c69d304a144a66c67a 100644 --- a/include/libcamera/internal/v4l2_device.h +++ b/include/libcamera/internal/v4l2_device.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -47,7 +48,7 @@ public: std::string devicePath() const; bool supportsEvents(v4l2_event_subscription &sub); - int setFrameStartEnabled(bool enable); + int setEventsEnabled(v4l2_event_subscription &sub, bool enable); Signal eventReady; void updateControlInfo(); @@ -90,7 +91,7 @@ private: UniqueFD fd_; std::unique_ptr fdEventNotifier_; - bool frameStartEnabled_; + std::set> subscribedEvents_; }; } /* namespace libcamera */ diff --git a/src/libcamera/pipeline/ipu3/cio2.cpp b/src/libcamera/pipeline/ipu3/cio2.cpp index 7481b2686df070e94fc5f698141db3d3b97896ea..876f78ace06cbc96fb8100cfc4ed76c73de1d449 100644 --- a/src/libcamera/pipeline/ipu3/cio2.cpp +++ b/src/libcamera/pipeline/ipu3/cio2.cpp @@ -356,7 +356,10 @@ int CIO2Device::start() return ret; } - ret = csi2_->setFrameStartEnabled(true); + v4l2_event_subscription sub{}; + sub.type = V4L2_EVENT_FRAME_SYNC; + + ret = csi2_->setEventsEnabled(sub, true); if (ret) { stop(); return ret; @@ -367,9 +370,11 @@ int CIO2Device::start() int CIO2Device::stop() { + v4l2_event_subscription sub{}; int ret; - csi2_->setFrameStartEnabled(false); + sub.type = V4L2_EVENT_FRAME_SYNC; + csi2_->setEventsEnabled(sub, false); ret = output_->streamOff(); diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp index da3153ad7fafc133cdde6accb5e3bd54f80520c9..270efb520c53f10cf456ce89a34e8ba40270d560 100644 --- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp @@ -1393,7 +1393,10 @@ int PipelineHandlerMaliC55::start(Camera *camera, [[maybe_unused]] const Control return ret; } - ret = isp_->setFrameStartEnabled(true); + v4l2_event_subscription sub{}; + sub.type = V4L2_EVENT_FRAME_SYNC; + + ret = isp_->setEventsEnabled(sub, true); if (ret) LOG(MaliC55, Error) << "Failed to enable frame start events"; @@ -1404,7 +1407,10 @@ void PipelineHandlerMaliC55::stopDevice(Camera *camera) { MaliC55CameraData *data = cameraData(camera); - isp_->setFrameStartEnabled(false); + v4l2_event_subscription sub{}; + sub.type = V4L2_EVENT_FRAME_SYNC; + + isp_->setEventsEnabled(sub, false); if (auto *mem = std::get_if(&data->input_)) { ivc_->streamOff(); diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp index e49dd546ee0a5207c75a364564d5673fd8463b30..967f39a9feb6d9d40d0b8df366adbf7d6863458c 100644 --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp @@ -1304,7 +1304,9 @@ int PipelineHandlerRkISP1::start(Camera *camera, [[maybe_unused]] const ControlL return ret; } - isp_->setFrameStartEnabled(true); + v4l2_event_subscription sub{}; + sub.type = V4L2_EVENT_FRAME_SYNC; + isp_->setEventsEnabled(sub, true); activeCamera_ = camera; @@ -1317,7 +1319,9 @@ void PipelineHandlerRkISP1::stopDevice(Camera *camera) RkISP1CameraData *data = cameraData(camera); int ret; - isp_->setFrameStartEnabled(false); + v4l2_event_subscription sub{}; + sub.type = V4L2_EVENT_FRAME_SYNC; + isp_->setEventsEnabled(sub, false); data->ipa_->stop(); @@ -1653,7 +1657,7 @@ void PipelineHandlerRkISP1::imageBufferReady(FrameBuffer *buffer) * Record the sensor's timestamp in the request metadata. * * \todo The sensor timestamp should be better estimated by connecting - * to the V4L2Device::frameStart signal. + * to the V4L2Device::FrameSync signal. */ request->_d()->metadata().set(controls::SensorTimestamp, metadata.timestamp); diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp index 2315a1c47dc26ed88fa149106a25dcf1ffbc3ec0..cb642967cf315cb8325fab6edb3723831073aec1 100644 --- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp @@ -702,7 +702,9 @@ int PipelineHandlerBase::start(Camera *camera, const ControlList *controls) data->state_ = CameraData::State::Idle; /* Enable SOF event generation. */ - data->frontendDevice()->setFrameStartEnabled(true); + v4l2_event_subscription sub{}; + sub.type = V4L2_EVENT_FRAME_SYNC; + data->frontendDevice()->setEventsEnabled(sub, true); data->platformStart(); @@ -731,7 +733,9 @@ void PipelineHandlerBase::stopDevice(Camera *camera) } /* Disable SOF event generation. */ - data->frontendDevice()->setFrameStartEnabled(false); + v4l2_event_subscription sub{}; + sub.type = V4L2_EVENT_FRAME_SYNC; + data->frontendDevice()->setEventsEnabled(sub, false); data->clearIncompleteRequests(); diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp index 75e068bf516d80af6c8aeeb94bf00ba265744fdd..04f82a55168e7c87f7024a962f85b818347b3236 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -1682,7 +1682,9 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL data->delayedCtrls_->reset(); if (frameStartEmitter) { - ret = frameStartEmitter->setFrameStartEnabled(true); + v4l2_event_subscription sub{}; + sub.type = V4L2_EVENT_FRAME_SYNC; + ret = frameStartEmitter->setEventsEnabled(sub, true); if (ret) { stop(camera); return ret; @@ -1726,7 +1728,9 @@ void SimplePipelineHandler::stopDevice(Camera *camera) V4L2Subdevice *frameStartEmitter = data->frameStartEmitter_; if (frameStartEmitter) { - frameStartEmitter->setFrameStartEnabled(false); + v4l2_event_subscription sub{}; + sub.type = V4L2_EVENT_FRAME_SYNC; + frameStartEmitter->setEventsEnabled(sub, false); frameStartEmitter->eventReady.connect(data, &SimpleCameraData::handleEvent); } diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp index 192edd802a22d3edc7055132eb84ca00e095dd26..03001fbc6ffd906715d195033676e5d5d529f493 100644 --- a/src/libcamera/v4l2_device.cpp +++ b/src/libcamera/v4l2_device.cpp @@ -57,8 +57,7 @@ LOG_DEFINE_CATEGORY(V4L2) * at open() time, and the \a logTag to prefix log messages with. */ V4L2Device::V4L2Device(const std::string &deviceNode) - : deviceNode_(deviceNode), fdEventNotifier_(nullptr), - frameStartEnabled_(false) + : deviceNode_(deviceNode), fdEventNotifier_(nullptr) { } @@ -489,30 +488,35 @@ bool V4L2Device::supportsEvents(v4l2_event_subscription &sub) } /** - * \brief Enable or disable frame start event notification + * \brief Enable or event notifications + * \param[in] sub Details of the event to subscribe to * \param[in] enable True to enable frame start events, false to disable them * - * This function enables or disables generation of frame start events. Once - * enabled, the events are signalled through the eventReady signal. + * This function enables or disables generation of events for a particular + * subscriptions. Once enabled, the events are signalled through the eventReady + * signal. * * \return 0 on success, a negative error code otherwise */ -int V4L2Device::setFrameStartEnabled(bool enable) +int V4L2Device::setEventsEnabled(v4l2_event_subscription &sub, bool enable) { - if (frameStartEnabled_ == enable) - return 0; + std::tuple subTypeId(sub.type, sub.id); - struct v4l2_event_subscription event{}; - event.type = V4L2_EVENT_FRAME_SYNC; + if (subscribedEvents_.contains(subTypeId)) + return 0; unsigned long request = enable ? VIDIOC_SUBSCRIBE_EVENT : VIDIOC_UNSUBSCRIBE_EVENT; - int ret = ioctl(request, &event); + int ret = ioctl(request, &sub); if (enable && ret) return ret; fdEventNotifier_->setEnabled(enable); - frameStartEnabled_ = enable; + + if (enable) + subscribedEvents_.insert(subTypeId); + else + subscribedEvents_.erase(subTypeId); return ret; }