From patchwork Fri Sep 25 15:16:21 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Scally X-Patchwork-Id: 28380 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 7ECE3C32F6 for ; Fri, 25 Sep 2026 15:16:39 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6ABF5689D2; Fri, 25 Sep 2026 17:16:37 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="rdYexVIS"; 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 1A4D0689B5 for ; Fri, 25 Sep 2026 17:16:31 +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 4CA70D0; Fri, 25 Sep 2026 17:14:42 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1790349282; bh=BFzyN6amMPjWvD7mCDZGm5qk07ItsPY2BfX1sXyRO30=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=rdYexVISb/5W52mqab1F6O9YpcheVqf0UHQIElK+P2UQEUnUm+bwYqHP3qn2DBT1H iPHw6F56Wdl6ip57i1VvFSnyrI4TfihZwpWQoTL0jiidAf8ThIQTLN4lN/IXF7PWyM ifZbwRxcpucq2/cUg+9WRhCQII8DiUMg9LjfOHXU= From: Daniel Scally Date: Fri, 25 Sep 2026 16:16:21 +0100 Subject: [PATCH 1/4] libcamera: v4l2_event: Add V4L2Event class and functionality MIME-Version: 1.0 Message-Id: <20260925-v4l2-events-v1-1-b2ec212eb5d0@ideasonboard.com> References: <20260925-v4l2-events-v1-0-b2ec212eb5d0@ideasonboard.com> In-Reply-To: <20260925-v4l2-events-v1-0-b2ec212eb5d0@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=13214; i=dan.scally@ideasonboard.com; h=from:subject:message-id; bh=BFzyN6amMPjWvD7mCDZGm5qk07ItsPY2BfX1sXyRO30=; b=owEBbQKS/ZANAwAKAchJV3psRXUyAcsmYgBqtpBMRFxVSFWJOoTqBUzz1HJHg8LdFhmVKxjBS 3ZWjP5mZfaJAjMEAAEKAB0WIQQqyuwyDnZdb+mxmm/ISVd6bEV1MgUCaraQTAAKCRDISVd6bEV1 MkqgEACw9J9wS+hp2fodcfXX5UpezgiReaWkTtQ2/fGd9WbNTkGdEsATPKm2Qwctbn5YOwmUpsO pa2Noz85m5IhGl6+yP8xEll/xhASlrdlcBsT2CqKB4lR/jLh4VBcNs2JRb6tBoszahBuAOq9ye5 bnMBL1ulTeixLr7UxY5JSJqGdFSqJ0DgFoSHTyGIHbxDIQiPqGtEQQ5l4Lg2PbTYRYDtT08Xh/6 LRGpwj+m7rcTCDvQ2niIWcl6ooCcuaQ50T4x5VMFZQJmTFMAgmTKIdrXdVRBjUTgLToY+u62VGy ACj2Gh2zgLwYKo6l52l1TvaNRfa/aLCVxGDS42NgnLHnjKgzrBQt6lJyFE0Zd4FrEGmVJoySYO6 0KCEeOAu3q4ggdtnDmEOkh8cY+6jm2FDfuStNi8xiW82xB0aAjbt5wywDRGOXi8KthPEIZT1n7K mGj2Hf0ii2/3LVxBfiOqqUt+E2VqR2frkU/EQtBvwuaHHYYYbrvekun7+hrmkUPP5J+X9VgE72v 45sHPBrhY8RWgd2VcqKXAQESaIIPLOohbJ68DdPpGPa70BPQ0gub37dhL0BAx9Gsc9EBSIV44VJ Nx4YTRQXnwmw4WvbD6jBTAMLdaLrAcaOieeNTvmU0heM5mgIjj7Uh4FUu1NTKp82uWWwyTjWbXH MR5hwnDAy5Fh3Ow== 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" Add a base and derived classes to represent V4L2 Events to prepare for more generic event handling in V4L2Device. Signed-off-by: Daniel Scally --- include/libcamera/internal/v4l2_event.h | 158 ++++++++++++++++++ src/libcamera/meson.build | 1 + src/libcamera/v4l2_event.cpp | 288 ++++++++++++++++++++++++++++++++ 3 files changed, 447 insertions(+) diff --git a/include/libcamera/internal/v4l2_event.h b/include/libcamera/internal/v4l2_event.h new file mode 100644 index 0000000000000000000000000000000000000000..8e8d539b253b9235ece2e732e12a7609a0a5e89b --- /dev/null +++ b/include/libcamera/internal/v4l2_event.h @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas on Board Oy + * + * V4L2 Event representations + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace libcamera { + +class V4L2Event +{ +public: + V4L2Event(const v4l2_event *event); + + enum Type { + VerticalSync, + EndOfStream, + Control, + FrameSync, + SourceChange, + MotionDetected, + NumberOfEventTypes, + }; + + static constexpr std::array, Type::NumberOfEventTypes> typeMap = { { + { V4L2_EVENT_VSYNC, Type::VerticalSync }, + { V4L2_EVENT_EOS, Type::EndOfStream }, + { V4L2_EVENT_CTRL, Type::Control }, + { V4L2_EVENT_FRAME_SYNC, Type::FrameSync }, + { V4L2_EVENT_SOURCE_CHANGE, Type::SourceChange }, + { V4L2_EVENT_MOTION_DET, Type::MotionDetected }, + } }; + + static std::shared_ptr createEvent(const struct v4l2_event *event); + Type type() { return type_; } + static std::optional typeFromV4L2(uint32_t type); + static std::optional typeToV4L2(Type type); + +private: + Type type_; +}; + +class V4L2EventSubscription +{ +public: + V4L2EventSubscription(V4L2Event::Type type, uint32_t id = 0) + : type_(type), id_(id) + { + } + + V4L2Event::Type type() { return type_; } + uint32_t id() { return id_; } + + bool operator<(const V4L2EventSubscription &other) const + { + return std::tie(type_, id_) < std::tie(other.type_, other.id_); + } +private: + V4L2Event::Type type_; + uint32_t id_; +}; + +class V4L2VerticalSyncEvent : public V4L2Event +{ +public: + V4L2VerticalSyncEvent(const v4l2_event *event) + : V4L2Event(event), field_(event->u.vsync.field) + { + } + + uint8_t field() { return field_; } +private: + uint8_t field_; +}; + +class V4L2ControlEvent : public V4L2Event +{ +public: + V4L2ControlEvent(const v4l2_event *event); + + uint32_t controlId() { return controlId_; } + uint32_t changes() { return changes_; } + uint32_t type() { return type_; } + uint32_t flags() { return flags_; } + int64_t value() { return value_; } + uint32_t min() { return min_; } + uint32_t max() { return max_; } + uint32_t step() { return step_; } + uint32_t def() { return default_; } +private: + uint32_t controlId_; + uint32_t changes_; + uint32_t type_; + uint32_t flags_; + int64_t value_; + uint32_t min_; + uint32_t max_; + uint32_t step_; + uint32_t default_; +}; + +class V4L2FrameSyncEvent : public V4L2Event +{ +public: + V4L2FrameSyncEvent(const v4l2_event *event) + : V4L2Event(event), sequence_(event->u.frame_sync.frame_sequence) + { + } + + uint32_t sequence() { return sequence_; } +private: + uint32_t sequence_; +}; + +class V4L2SourceChangeEvent : public V4L2Event +{ +public: + V4L2SourceChangeEvent(const v4l2_event *event) + : V4L2Event(event), changes_(event->u.src_change.changes) + { + } + + uint32_t changes() { return changes_; } + +private: + uint32_t changes_; +}; + +class V4L2MotionDetectedEvent : public V4L2Event +{ +public: + V4L2MotionDetectedEvent(const v4l2_event *event) + : V4L2Event(event), flags_(event->u.motion_det.flags), + frame_sequence_(event->u.motion_det.frame_sequence), + region_mask_(event->u.motion_det.region_mask) + { + } + + uint32_t flags() { return flags_; } + uint32_t frameSequence() { return frame_sequence_; } + uint32_t regionMask() { return region_mask_; } +private: + uint32_t flags_; + uint32_t frame_sequence_; + uint32_t region_mask_; +}; + +} /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index 17c1b2cb347902d509da35a114845aa4ca554623..04e4bbb1451f4666e6c4d8a79c14473838180241 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -53,6 +53,7 @@ libcamera_internal_sources = files([ 'source_paths.cpp', 'sysfs.cpp', 'v4l2_device.cpp', + 'v4l2_event.cpp', 'v4l2_pixelformat.cpp', 'v4l2_request.cpp', 'v4l2_subdevice.cpp', diff --git a/src/libcamera/v4l2_event.cpp b/src/libcamera/v4l2_event.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7279eba6aadd04ed329ae36e0c2b9258cdb98d2b --- /dev/null +++ b/src/libcamera/v4l2_event.cpp @@ -0,0 +1,288 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas on Board Oy + * + * V4L2 Event representations + */ + +#include "libcamera/internal/v4l2_event.h" + +#include +#include + +#include + +#include + +/** + * \file v4l2_event.h + * \brief Handling of V4L2 Events + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(V4L2Event) + +/** + * \class V4L2Event + * \brief Base class to represent V4L2 Events + * + * The V4L2Event class holds common utilities and helper functions, and is + * mostly intended to facilitate the creation of more specific derived classes. + */ + +/** + * \brief Construct a new V4L2Event::V4L2Event object + * + * \param[in] event The v4l2_event definition + */ +V4L2Event::V4L2Event(const v4l2_event *event) +{ + auto type = typeFromV4L2(event->type); + assert(type); + + type_ = *type; +} + +/** + * \enum V4L2Event::Type + * \brief Enumeration of possible V4L2 Event Types + * + * \var V4L2Event::VerticalSync + * \brief An event triggered on the vertical sync + * + * \var V4L2Event::EndOfStream + * \brief An event triggered when the end of a stream is reached + * + * \var V4L2Event::Control + * \brief An event triggered when the value of the specified control changes, + * if a button control is pressed or if the control's flags change. + * + * \var V4L2Event::FrameSync + * \brief An event triggered when reception of a frame has begun + * + * \var V4L2Event::SourceChange + * \brief Triggered when a source parameter is changed + * + * \var V4L2Event::MotionDetected + * \brief Triggered when motion is detected + * + */ + +/** + * \var V4L2Event::typeMap + * \brief Maps V4L2 types from the kernel header to V4l2Event::Type values + */ + +/** + * \brief Create a derivation of a \ref V4L2Event from the kernel's + * representation of one. + * + * This funtion allocates one of the derived classes of \ref V4L2Event, based on + * the input event's type member. + * + * \param[in] event The kernel uAPI representation of a V4L2 event + */ +std::shared_ptr V4L2Event::createEvent(const struct v4l2_event *event) +{ + switch (event->type) { + case V4L2_EVENT_VSYNC: + return std::make_shared(event); + case V4L2_EVENT_EOS: + return std::make_shared(event); + case V4L2_EVENT_CTRL: + return std::make_shared(event); + case V4L2_EVENT_FRAME_SYNC: + return std::make_shared(event); + case V4L2_EVENT_SOURCE_CHANGE: + return std::make_shared(event); + case V4L2_EVENT_MOTION_DET: + return std::make_shared(event); + default: + return nullptr; + } +} + +/** + * \fn V4L2Event::type() + * \brief Return the \ref V4L2Event::Type associated with this event + */ + +/** + * \brief Map the kernel V4L2 event type to a \ref V4L2Event::Type + * + * \param[in] type The kernel's event type + * \return std::optional + */ +std::optional V4L2Event::typeFromV4L2(uint32_t type) +{ + for (const auto &[v4l2Type, eventType] : typeMap) { + if (v4l2Type == type) + return eventType; + } + + return std::nullopt; +} + +/** + * \brief Map a \ref V4L2Event::Type to the kernel's equivalent + * + * \param[in] type The V4L2Event::Type to translate + * \return std::optional + */ +std::optional V4L2Event::typeToV4L2(V4L2Event::Type type) +{ + for (const auto &[v4l2Type, eventType] : typeMap) { + if (eventType == type) + return v4l2Type; + } + + return std::nullopt; +} + +/** + * \class V4L2EventSubscription + * \brief Class to represent the kernel uAPI's struct v4l2_event_subscription + */ + +/** + * \fn V4L2EventSubscription::V4L2EventSubscription + * \brief Constructs a new V4L2EventSubscription object + */ + +/** + * \fn V4L2EventSubscription::type + * \brief Returns the \ref V4L2Event::Type associated with this subscription + */ + +/** + * \fn V4L2EventSubscription::id + * \brief Returns the ID field for this subscription + */ + +/** + * \fn V4L2EventSubscription::operator<(const V4L2EventSubscription &other) const + * \brief "Less-than" comparator for event subscriptions + * + * This function allows us to check whether an equivalent subscription has + * already been made by storing an instance of the class in a std::set. With + * this operator the normal find() / insert() and erase() operations will + * work on the set. + */ + +/** + * \class V4L2VerticalSyncEvent + * \brief Representation of a V4L2Event of type VerticalSync + * + * \fn V4L2VerticalSyncEvent::V4L2VerticalSyncEvent + * \brief Construct a new V4L2VerticalSyncEvent object + * + * \param[in] event The v4l2_event definition + * + * \fn V4L2VerticalSyncEvent::field + * \brief The upcoming V4L2_FIELD_* + */ + +/** + * \class V4L2ControlEvent + * \brief Representation of a V4L2Event of type Control + * + * \fn V4L2ControlEvent::controlId + * \brief Return the V4L2_CID_* control ID associated with this event + * + * \fn V4L2ControlEvent::changes + * \brief Return the bit mask of V4L2_EVENT_CTRL_CH_* flags + * + * \fn V4L2ControlEvent::type + * \brief Return the V4L2_CTRL_TYPE_* for this control + * + * \fn V4L2ControlEvent::flags + * \brief Return the V4L2_CTRL_FL_* bitmask for this control + * + * \fn V4L2ControlEvent::value + * \brief Return the value of this control + * + * For the value, we take the value64 field from V4L2 and use it unconditionally + * + * \fn V4L2ControlEvent::min + * \brief Return the minimum value of this control + * + * \fn V4L2ControlEvent::max + * \brief Return the maximum value of this control + * + * \fn V4L2ControlEvent::step + * \brief Return the control value step + * + * \fn V4L2ControlEvent::def + * \brief Return the default value of this control + */ + +/** + * \brief Construct a new V4L2ControlEvent object + * + * \param[in] event The v4l2_event definition + */ +V4L2ControlEvent::V4L2ControlEvent(const struct v4l2_event *event) + : V4L2Event(event), controlId_(event->id) +{ + const v4l2_event_ctrl *ctrl = &event->u.ctrl; + + changes_ = ctrl->changes; + type_ = ctrl->type; + flags_ = ctrl->flags; + value_ = ctrl->value64; + min_ = ctrl->minimum; + max_ = ctrl->maximum; + step_ = ctrl->step; + default_ = ctrl->default_value; +} + +/** + * \class V4L2FrameSyncEvent + * \brief Representation of a V4L2Event of type FrameSync + * + * \fn V4L2FrameSyncEvent::V4L2FrameSyncEvent() + * \brief Construct a new V4L2FrameSyncEvent object + * + * \param[in] event The v4l2_event definition + * + * \fn V4L2FrameSyncEvent::sequence() + * \brief Return the frame sequence number for this Frame Sync event + */ + +/** + * \class V4L2SourceChangeEvent + * \brief Representation of a V4L2Event of type SourceChange + * + * \fn V4L2SourceChangeEvent::V4L2SourceChangeEvent() + * \brief Construct a new V4L2SourceChangeEvent object + * + * \param[in] event The v4l2_event definition + * + * \fn V4L2SourceChangeEvent::changes() + * \brief Return the change flags for this Source Change event + */ + +/** + * \class V4L2MotionDetectedEvent + * \brief Representation of a V4L2Event of type MotionDetected + * + * \fn V4L2MotionDetectedEvent::V4L2MotionDetectedEvent() + * \brief Construct a new V4L2MotionDetectedEvent object + * + * \param[in] event The v4l2_event definition + * + * \fn V4L2MotionDetectedEvent::flags + * \brief Flags for this motion detection event, from V4L2_EVENT_MD_FL_* + * + * \fn V4L2MotionDetectedEvent::frameSequence + * \brief The frame sequence number for this event + * + * This field is invalid unless \ref V4L2MotionDetectedEvent::flags includes + * the V4L2_EVENT_MD_FL_HAVE_FRAME_SEQ flag. + * + * \fn V4L2MotionDetectedEvent::regionMask + * \brief The bitmask of the regions that reported motion + */ + +} /* namespace libcamera */ From patchwork Fri Sep 25 15:16:22 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Scally X-Patchwork-Id: 28378 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 DE20BBDCBD for ; Fri, 25 Sep 2026 15:16:35 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 742E6689C5; Fri, 25 Sep 2026 17:16:32 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="fXFnHjJJ"; 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 B3BA2689AA for ; Fri, 25 Sep 2026 17:16:30 +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 93F0E12F; Fri, 25 Sep 2026 17:14:42 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1790349282; bh=5IPJVie1k1opuihW4m5DnGBcTjJRjx6133QzyrLGhAM=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=fXFnHjJJ0e2c0rzmA5Un4ck9T2DqiMQBQ2R5mUSeP7xqhDJvTFKBvcngwXPgX1edh SSKb/WBzZNggeSbcrGuSOduWql0FoHC54XC2r+b1eT+5tJgcfTmkBNPw3bUqQ4KfwU 4YBh5lK5Rd7mjIWe2TdeDdvmDTxOgDUuDuStj3JQ= From: Daniel Scally Date: Fri, 25 Sep 2026 16:16:22 +0100 Subject: [PATCH 2/4] libcamera: v4l2_device: Make supportsFrameStartEvent() generic MIME-Version: 1.0 Message-Id: <20260925-v4l2-events-v1-2-b2ec212eb5d0@ideasonboard.com> References: <20260925-v4l2-events-v1-0-b2ec212eb5d0@ideasonboard.com> In-Reply-To: <20260925-v4l2-events-v1-0-b2ec212eb5d0@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=3934; i=dan.scally@ideasonboard.com; h=from:subject:message-id; bh=5IPJVie1k1opuihW4m5DnGBcTjJRjx6133QzyrLGhAM=; b=owEBbQKS/ZANAwAKAchJV3psRXUyAcsmYgBqtpBNLAJIs4+ya5kSUsO9UYEKCEIeF9y5Q2qL4 c2EPukHwDeJAjMEAAEKAB0WIQQqyuwyDnZdb+mxmm/ISVd6bEV1MgUCaraQTQAKCRDISVd6bEV1 Mk+0D/9KEf0ZW8oEygPY7FMIFutmIMxN1PsvXjY64QIt2Yla9MrQ4q+u4aZkrTkXkUPeiC3pocx A4i1661GJ6fbaeV0utfTt0XkCWiAD+hfjof3aTTSOAWvrIm1UpJAdVNWsyjki+/2LhKUlmCbVPB 9rtJ4pW6SOFCLKmH1Ct1s0m11TD/sdeNKyJu23Z1ws0YigFuEXEQi1JZ9EAnfAmKPFyNJzH+t3c NFhbyjY7yviPq9QBz31Zby4q2fTOrWLFHY5t+7OlwErEdo7gcdzrxX74ULXCnJKVx2KNL2G+MYP sajC5Xg+so6HS+tQK8aB10c0h/5cJ7AgOrARsR3r8OtNl2NwUvw7i2gGUojGLq2A2EbX6A+y7JM 0FIE1x60XO7dd/5JtZB4HIAACqstk/WepL0RhVYXK8aqJ+Y2T7o4sudhfb9wKZt35wlKbScWHEB YPiidgSyFVkgJw2zFccsaLtFZZihA6OwU/K9Cqo0TWun97lBrQXcdjx/wP9BbqJbpgyijKhjS0b xBFtisYpjrEW1cxcPSSg4SzahjxwUAKJt9j8oSpYLE4AUoP21Pq8tyySZ7EYAKQJERTsDNo0Nib De+m1AR9n4NBe9GxspPDFOSKuKKFLOHEg8ps/CQnjAtfG1ZNxr8KUoRaVSAYnyxKSCxJuBE0h2b 0BflEFh+FKnGboQ== 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 V4L2EventSubscription parameter that allows users to check whether the V4L2Device supports event subscriptions for any type. Signed-off-by: Daniel Scally --- include/libcamera/internal/v4l2_device.h | 3 ++- src/libcamera/pipeline/simple/simple.cpp | 4 +++- src/libcamera/v4l2_device.cpp | 21 ++++++++++++++------- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/include/libcamera/internal/v4l2_device.h b/include/libcamera/internal/v4l2_device.h index 1a63fc547eac579fabf50421b719d07ddb2d6639..cc8116874e9eeecac76f5c25e8b5719c07976781 100644 --- a/include/libcamera/internal/v4l2_device.h +++ b/include/libcamera/internal/v4l2_device.h @@ -24,6 +24,7 @@ #include #include "libcamera/internal/formats.h" +#include "libcamera/internal/v4l2_event.h" #include "libcamera/internal/v4l2_request.h" namespace libcamera { @@ -46,7 +47,7 @@ public: const std::string &deviceNode() const { return deviceNode_; } std::string devicePath() const; - bool supportsFrameStartEvent(); + bool supportsEvents(V4L2EventSubscription &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..4d70d50ce50bb1b94e4c30c3fecfb78af678e592 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -45,6 +45,7 @@ #include "libcamera/internal/pipeline_handler.h" #include "libcamera/internal/request.h" #include "libcamera/internal/software_isp/software_isp.h" +#include "libcamera/internal/v4l2_event.h" #include "libcamera/internal/v4l2_subdevice.h" #include "libcamera/internal/v4l2_videodevice.h" @@ -668,7 +669,8 @@ int SimpleCameraData::init() frameStartEmitter_ = nullptr; for (const Entity &entity : entities_) { V4L2Subdevice *sd = pipe->subdev(entity.entity); - if (!sd || !sd->supportsFrameStartEvent()) + V4L2EventSubscription sub(V4L2Event::Type::FrameSync); + 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..654472286d4645ff96a404a09ff8de382a49678c 100644 --- a/src/libcamera/v4l2_device.cpp +++ b/src/libcamera/v4l2_device.cpp @@ -25,6 +25,7 @@ #include "libcamera/internal/formats.h" #include "libcamera/internal/sysfs.h" +#include "libcamera/internal/v4l2_event.h" /** * \file v4l2_device.h @@ -470,18 +471,24 @@ 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(V4L2EventSubscription &sub) { struct v4l2_event_subscription event{}; - event.type = V4L2_EVENT_FRAME_SYNC; + + auto v4l2Type = V4L2Event::typeToV4L2(sub.type()); + if (!v4l2Type) + return false; + + event.type = *v4l2Type; + event.id = sub.id(); int ret = ioctl(VIDIOC_SUBSCRIBE_EVENT, &event); if (ret) From patchwork Fri Sep 25 15:16:23 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Scally X-Patchwork-Id: 28379 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 1AFF4BDCBD for ; Fri, 25 Sep 2026 15:16:38 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C66BF689D0; Fri, 25 Sep 2026 17:16:35 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="V549uhg/"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0714768983 for ; Fri, 25 Sep 2026 17:16:31 +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 CC1BEC1; Fri, 25 Sep 2026 17:14:42 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1790349282; bh=IXgfCSkPphXpGKYeviQkrjnOKI7HtKvIj5Wmq/Tagi8=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=V549uhg/4lo4a4ceXfxALrbl2VMl6ESo0Zov///5fqCo7e9ZpKVp9twfZKGYNzfhU ZjhAjlOzp2G41mKvnToPxeXP1UhgE4Sd+MlGsTeAkFUFEcWsdNarln+b+kChlDsPZd 6CFHpSVy50+CkP4B55KZQ6YLZgUKFhiZCEor4XYk= From: Daniel Scally Date: Fri, 25 Sep 2026 16:16:23 +0100 Subject: [PATCH 3/4] libcamera: v4l2_device: Emit signals for V4L2Events generically MIME-Version: 1.0 Message-Id: <20260925-v4l2-events-v1-3-b2ec212eb5d0@ideasonboard.com> References: <20260925-v4l2-events-v1-0-b2ec212eb5d0@ideasonboard.com> In-Reply-To: <20260925-v4l2-events-v1-0-b2ec212eb5d0@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=15278; i=dan.scally@ideasonboard.com; h=from:subject:message-id; bh=IXgfCSkPphXpGKYeviQkrjnOKI7HtKvIj5Wmq/Tagi8=; b=owEBbQKS/ZANAwAKAchJV3psRXUyAcsmYgBqtpBNKSSyq+AJQJHK53hYACgaOxqRIQ7xx2P+Z uYSVtIaPEWJAjMEAAEKAB0WIQQqyuwyDnZdb+mxmm/ISVd6bEV1MgUCaraQTQAKCRDISVd6bEV1 Mr/eEACaHFuyfNZ5oxisHRkr6wED0tDsDTgnagKbo2L8+n+3piDt1XTgb9aHIXcSLmlImcKitPa nyP7GZj8L1F4vyVGo3hGfsXFJGcJikPhFpvk+lrgRKQbG4ENGBCExxsfpUwUF+S4/8ZAp2kIPhR Njza71jBkG5AsP6pFywjpYr+UzqkwKw1/uqdsOfkapVQsg9KxPDwXpzb2kbJ+QKYmZrizNEO1vk ZvBdHwMMprUlpNqsMWMjqTx6EohBwPI3bdAddckotR9+fCdeOmOdZeb+dtm0w5nBW7dp4MTidOe nHNebMK0e4aKZ95fzqxmw1XRi6bhqroaWkSELJ8UBW7cw3pxkPv8LgVBiKoeg0M9j+I3cuQ3/5/ hkm/R8bqhmVjLkxcA0yB+FPNCElcO5l3ANhdFmPsfPv0RWK5estahUPu4NwHuCjQ07NrQXBj2Qm KcHzgkM+4TJgEM9fIdqO9e0N6GJto5cqxZYaPrpfZEFXQQe3M8sOAPKnJvQcaR9WZg0JgPTCxlo Ozx35rhimFw1denl5ZY06VyYWvTr3ozOOz6XLnbYTO1QmmKopJJcH2sv1nW2pir+nTFAf+GAFbr kDqSlRU3agowk/VitoZrsRRPODweGjBLQ7TGAskbLG4Hqcek+GLQXH8y/UfffNHlJTmBsd/cvok 26P9dgRyznQDEYg== 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. Signed-off-by: Daniel Scally --- With this change, in V4L2Device::eventAvailable() I am creating a std::shared_ptr and emitting the signal to the handler for it. I *think* this is ok and that once eventAvailable() and the Slot handler have returned it is cleaned up, but I thought I'd mention it incase I'm wrong. --- include/libcamera/internal/v4l2_device.h | 2 +- src/libcamera/pipeline/ipu3/cio2.h | 3 ++- src/libcamera/pipeline/ipu3/ipu3.cpp | 17 +++++++++++------ src/libcamera/pipeline/mali-c55/mali-c55.cpp | 18 ++++++++++++++---- src/libcamera/pipeline/rkisp1/rkisp1.cpp | 15 +++++++++++++-- src/libcamera/pipeline/rpi/common/pipeline_base.cpp | 14 ++++++++++---- src/libcamera/pipeline/rpi/common/pipeline_base.h | 3 ++- src/libcamera/pipeline/simple/simple.cpp | 19 +++++++++++++++---- src/libcamera/v4l2_device.cpp | 14 +++++++------- 9 files changed, 75 insertions(+), 30 deletions(-) diff --git a/include/libcamera/internal/v4l2_device.h b/include/libcamera/internal/v4l2_device.h index cc8116874e9eeecac76f5c25e8b5719c07976781..0c3bbcf2df39f581ceffb9afa6b008368b302156 100644 --- a/include/libcamera/internal/v4l2_device.h +++ b/include/libcamera/internal/v4l2_device.h @@ -49,7 +49,7 @@ public: bool supportsEvents(V4L2EventSubscription &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..1d29f19e01ae16b7d5500d0584ee226ee4e6453e 100644 --- a/src/libcamera/pipeline/ipu3/cio2.h +++ b/src/libcamera/pipeline/ipu3/cio2.h @@ -13,6 +13,7 @@ #include +#include "libcamera/internal/v4l2_event.h" #include "libcamera/internal/v4l2_subdevice.h" #include "libcamera/internal/v4l2_videodevice.h" @@ -58,7 +59,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..63aa2aab20d3917b4acdaf508123a46e7fc1bec0 100644 --- a/src/libcamera/pipeline/ipu3/ipu3.cpp +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp @@ -63,7 +63,7 @@ public: void statBufferReady(FrameBuffer *buffer); void queuePendingRequests(); void cancelPendingRequests(); - void frameStart(uint32_t sequence); + void handleEvent(std::shared_ptr event); CIO2Device cio2_; ImgUDevice *imgu_; @@ -1089,8 +1089,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 +1313,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 FrameSync events. */ request->_d()->metadata().set(controls::SensorTimestamp, buffer->metadata().timestamp); @@ -1382,9 +1382,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(std::shared_ptr event) { - delayedCtrls_->applyControls(sequence); + if (event->type() != V4L2Event::Type::FrameSync) + return; + + auto frameSyncEvent = static_cast(event.get()); + delayedCtrls_->applyControls(frameSyncEvent->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..627882f194447912cbed7d97e20bd8baec0e982a 100644 --- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp @@ -42,6 +42,7 @@ #include "libcamera/internal/media_device.h" #include "libcamera/internal/pipeline_handler.h" #include "libcamera/internal/request.h" +#include "libcamera/internal/v4l2_event.h" #include "libcamera/internal/v4l2_subdevice.h" #include "libcamera/internal/v4l2_videodevice.h" @@ -198,6 +199,8 @@ public: PixelFormat adjustRawFormat(const PixelFormat &pixFmt) const; Size adjustRawSizes(const PixelFormat &pixFmt, const Size &rawSize) const; + void handleEvent(std::shared_ptr event); + Stream frStream_; Stream dsStream_; @@ -451,6 +454,15 @@ Size MaliC55CameraData::adjustRawSizes(const PixelFormat &rawFmt, const Size &si return bestSize; } +void MaliC55CameraData::handleEvent(std::shared_ptr event) +{ + if (event->type() != V4L2Event::Type::FrameSync) + return; + + auto frameSyncEvent = static_cast(event.get()); + delayedCtrls_->applyControls(frameSyncEvent->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..53ec0007cc95c5ab2da0a5d8a86b5a3330d55f99 100644 --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp @@ -44,6 +44,7 @@ #include "libcamera/internal/media_pipeline.h" #include "libcamera/internal/pipeline_handler.h" #include "libcamera/internal/request.h" +#include "libcamera/internal/v4l2_event.h" #include "libcamera/internal/v4l2_subdevice.h" #include "libcamera/internal/v4l2_videodevice.h" #include "libcamera/internal/yaml_parser.h" @@ -106,6 +107,8 @@ public: const PipelineHandlerRkISP1 *pipe() const; int loadIPA(unsigned int hwRevision, uint32_t supportedBlocks); + void handleEvent(std::shared_ptr event); + Stream mainPathStream_; Stream selfPathStream_; std::unique_ptr sensor_; @@ -430,6 +433,15 @@ int RkISP1CameraData::loadIPA(unsigned int hwRevision, uint32_t supportedBlocks) return 0; } +void RkISP1CameraData::handleEvent(std::shared_ptr event) +{ + if (event->type() != V4L2Event::Type::FrameSync) + return; + + auto frameSyncEvent = static_cast(event.get()); + delayedCtrls_->applyControls(frameSyncEvent->sequence()); +} + int RkISP1CameraData::loadTuningFile(const std::string &path) { int ret; @@ -1472,8 +1484,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..c6b51e8c335635788cdc99b7a33e37df7de8a1cb 100644 --- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp @@ -20,6 +20,7 @@ #include #include "libcamera/internal/camera_lens.h" +#include "libcamera/internal/v4l2_event.h" #include "libcamera/internal/v4l2_subdevice.h" #include "libcamera/internal/yaml_parser.h" @@ -873,7 +874,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 +1393,17 @@ void CameraData::cameraTimeout() clearIncompleteRequests(); } -void CameraData::frameStarted(uint32_t sequence) +void CameraData::handleEvent(std::shared_ptr event) { - LOG(RPI, Debug) << "Frame start " << sequence; + if (event->type() != V4L2Event::Type::FrameSync) + return; + + auto frameSyncEvent = static_cast(event.get()); + + LOG(RPI, Debug) << "Frame start " << frameSyncEvent->sequence(); /* Write any controls for the next frame as soon as we can. */ - delayedCtrls_->applyControls(sequence); + delayedCtrls_->applyControls(frameSyncEvent->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..eeee0d4414b819effb97c1d3d12771e50012be6a 100644 --- a/src/libcamera/pipeline/rpi/common/pipeline_base.h +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.h @@ -25,6 +25,7 @@ #include "libcamera/internal/media_object.h" #include "libcamera/internal/pipeline_handler.h" #include "libcamera/internal/request.h" +#include "libcamera/internal/v4l2_event.h" #include "libcamera/internal/v4l2_videodevice.h" #include "libcamera/internal/value_node.h" @@ -86,7 +87,7 @@ public: virtual void platformSetIspCrop(unsigned int index, const Rectangle &ispCrop) = 0; void cameraTimeout(); - void frameStarted(uint32_t sequence); + void handleEvent(std::shared_ptr 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 4d70d50ce50bb1b94e4c30c3fecfb78af678e592..af3caf74ff56d4e6cc45ad098a93e9fde45449c2 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(std::shared_ptr event); + struct Entity { /* The media entity, always valid. */ MediaEntity *entity; @@ -985,6 +987,15 @@ void SimpleCameraData::clearIncompleteRequests() } } +void SimpleCameraData::handleEvent(std::shared_ptr event) +{ + if (event->type() != V4L2Event::Type::FrameSync) + return; + + auto frameSyncEvent = static_cast(event.get()); + delayedCtrls_->applyControls(frameSyncEvent->sequence()); +} + void SimpleCameraData::tryCompleteRequest(Request *request) { if (request->hasPendingBuffers()) @@ -1673,8 +1684,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(); @@ -1713,8 +1724,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 654472286d4645ff96a404a09ff8de382a49678c..f231bbb17d8528aca274388b8dcaccc0dc339ad3 100644 --- a/src/libcamera/v4l2_device.cpp +++ b/src/libcamera/v4l2_device.cpp @@ -503,7 +503,7 @@ bool V4L2Device::supportsEvents(V4L2EventSubscription &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 */ @@ -528,8 +528,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 */ /** @@ -867,15 +867,15 @@ void V4L2Device::eventAvailable() return; } - if (event.type != V4L2_EVENT_FRAME_SYNC) { + auto v4l2Event = V4L2Event::createEvent(&event); + if (!v4l2Event) { LOG(V4L2, Error) - << "Spurious event (" << event.type - << "), disabling event notifier"; + << "Invalid V4L2 Event type, disabling event notifier"; fdEventNotifier_->setEnabled(false); return; } - frameStart.emit(event.u.frame_sync.frame_sequence); + eventReady.emit(v4l2Event); } static const std::map v4l2ToColorSpace = { From patchwork Fri Sep 25 15:16:24 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Scally X-Patchwork-Id: 28381 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 41AD8BDCBD for ; Fri, 25 Sep 2026 15:16:40 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A6C5C689C5; Fri, 25 Sep 2026 17:16:38 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="fc+KgTsE"; 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 3A87A689BC for ; Fri, 25 Sep 2026 17:16:31 +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 1A82A12F; Fri, 25 Sep 2026 17:14:43 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1790349283; bh=g2yHqCL0KCftbCHzgW5pVSWusJM+kuuWobw0fthKEZY=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=fc+KgTsE5tEUGKEl9YNBzBPbNaFwNR5GZ+Ey0uXzWlB9zT2yxCPj/DDD11282GG+q MY7bLfxm1DGrTr0J7tHOQCL+HK/cU3BFMkzbZhH9Na/ZtXb/BJpHlhD8X8UJ+Mzs7b S2ugX4E/vYOXn5wCaxIR4wRNLSZo89zMpe2nWeJQ= From: Daniel Scally Date: Fri, 25 Sep 2026 16:16:24 +0100 Subject: [PATCH 4/4] libcamera: v4l2_device: Make setFrameStartEnabled() generic MIME-Version: 1.0 Message-Id: <20260925-v4l2-events-v1-4-b2ec212eb5d0@ideasonboard.com> References: <20260925-v4l2-events-v1-0-b2ec212eb5d0@ideasonboard.com> In-Reply-To: <20260925-v4l2-events-v1-0-b2ec212eb5d0@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=9409; i=dan.scally@ideasonboard.com; h=from:subject:message-id; bh=g2yHqCL0KCftbCHzgW5pVSWusJM+kuuWobw0fthKEZY=; b=owEBbQKS/ZANAwAKAchJV3psRXUyAcsmYgBqtpBNQ7bWTP+nRMQhkkB/gRo8/HAmzrlIK9IHA 13U79doGfuJAjMEAAEKAB0WIQQqyuwyDnZdb+mxmm/ISVd6bEV1MgUCaraQTQAKCRDISVd6bEV1 Mt+2D/9LtfijexbWYXoDVwvVuliB/C3ND5vu7D0BVXcSEhyQJiiUdDAo6/1itCI6FPA4fcgAiR4 MCvrn2KPkCsK840S9xPs1qzt3hojeJy2P/oezm6mnVa4BOjrrKzthMyU/xiNj1NNh3L8CKF1I6d 3N6U3gfCZfJlnmiymaOB9IntwH9PbAq7fDi6sJArzESSznjZ9IWl/PlgTyZ1tcaM3UnNZdhRmAc Eyi/UvrMQwBAmzYih9P9H91fdi0ni41qfJxauknDpJZcw8f7FMG0fUDAjto7wGMlraiddubXyuY OU7+yntwxWO4GZBbr36VtsoM119Zzu2HmnvLBru3ezFIL1kJqvdDwTD+81F9nvdwFvnzK2U7FJz 5i8SVbTxE6vOt2ieH1gWl7H8Wg4FJzGm4XT6m59MLgGVMn7TivpYXdbbCouwJWDL1ewmat0Owpn pHhwJcNtCHwLkxfkxzTBvZr3yRBtJYtlw2ARe0QXO4KAzk3hVQiq6+9b9pWvkyORmtba8ovLzOV SiGAn9uz10aECjYHTC8o0FX7ooNefBqqInIBSEZ2+Pi65YTuvLibgEk1LxKW95nUdP6lVISMmtm 193DSBnBsDJMMvum0nAvLGwkeZjTp8qxb475HqpNH/M/VXS44yHJ/+5FWHBN96LEgHagWKdqAck 6t+Izq9lXPVYlaA== 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 subcription details are parametised. This will allow consumers to suscribe to any supported events. Signed-off-by: Daniel Scally --- include/libcamera/internal/v4l2_device.h | 5 ++-- src/libcamera/pipeline/ipu3/cio2.cpp | 6 ++-- src/libcamera/pipeline/mali-c55/mali-c55.cpp | 6 ++-- src/libcamera/pipeline/rkisp1/rkisp1.cpp | 8 ++++-- .../pipeline/rpi/common/pipeline_base.cpp | 6 ++-- src/libcamera/pipeline/simple/simple.cpp | 6 ++-- src/libcamera/v4l2_device.cpp | 32 ++++++++++++++++------ 7 files changed, 47 insertions(+), 22 deletions(-) diff --git a/include/libcamera/internal/v4l2_device.h b/include/libcamera/internal/v4l2_device.h index 0c3bbcf2df39f581ceffb9afa6b008368b302156..384323be6db1d46249200b4b4375ac4c85695502 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 @@ -48,7 +49,7 @@ public: std::string devicePath() const; bool supportsEvents(V4L2EventSubscription &sub); - int setFrameStartEnabled(bool enable); + int setEventsEnabled(V4L2EventSubscription &sub, bool enable); Signal> eventReady; void updateControlInfo(); @@ -91,7 +92,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..c5b51c222e8544fad6b6a0112dccae35ce3622f0 100644 --- a/src/libcamera/pipeline/ipu3/cio2.cpp +++ b/src/libcamera/pipeline/ipu3/cio2.cpp @@ -356,7 +356,8 @@ int CIO2Device::start() return ret; } - ret = csi2_->setFrameStartEnabled(true); + V4L2EventSubscription sub(V4L2Event::Type::FrameSync); + ret = csi2_->setEventsEnabled(sub, true); if (ret) { stop(); return ret; @@ -369,7 +370,8 @@ int CIO2Device::stop() { int ret; - csi2_->setFrameStartEnabled(false); + V4L2EventSubscription sub(V4L2Event::Type::FrameSync); + 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 627882f194447912cbed7d97e20bd8baec0e982a..ad5ed3d55958fb496d54da32d8bb3d5d9ec6a758 100644 --- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp @@ -1393,7 +1393,8 @@ int PipelineHandlerMaliC55::start(Camera *camera, [[maybe_unused]] const Control return ret; } - ret = isp_->setFrameStartEnabled(true); + V4L2EventSubscription sub(V4L2Event::Type::FrameSync); + ret = isp_->setEventsEnabled(sub, true); if (ret) LOG(MaliC55, Error) << "Failed to enable frame start events"; @@ -1404,7 +1405,8 @@ void PipelineHandlerMaliC55::stopDevice(Camera *camera) { MaliC55CameraData *data = cameraData(camera); - isp_->setFrameStartEnabled(false); + V4L2EventSubscription sub(V4L2Event::Type::FrameSync); + 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 53ec0007cc95c5ab2da0a5d8a86b5a3330d55f99..9b2ca055aa10f0b7ef5625ab06ec5b4b2ea38f66 100644 --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp @@ -1305,7 +1305,8 @@ int PipelineHandlerRkISP1::start(Camera *camera, [[maybe_unused]] const ControlL return ret; } - isp_->setFrameStartEnabled(true); + V4L2EventSubscription sub(V4L2Event::Type::FrameSync); + isp_->setEventsEnabled(sub, true); activeCamera_ = camera; @@ -1318,7 +1319,8 @@ void PipelineHandlerRkISP1::stopDevice(Camera *camera) RkISP1CameraData *data = cameraData(camera); int ret; - isp_->setFrameStartEnabled(false); + V4L2EventSubscription sub(V4L2Event::Type::FrameSync); + isp_->setEventsEnabled(sub, false); data->ipa_->stop(); @@ -1654,7 +1656,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 c6b51e8c335635788cdc99b7a33e37df7de8a1cb..8c5945cab31755025fad8fa849dc2935876252ca 100644 --- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp @@ -703,7 +703,8 @@ int PipelineHandlerBase::start(Camera *camera, const ControlList *controls) data->state_ = CameraData::State::Idle; /* Enable SOF event generation. */ - data->frontendDevice()->setFrameStartEnabled(true); + V4L2EventSubscription sub(V4L2Event::Type::FrameSync); + data->frontendDevice()->setEventsEnabled(sub, true); data->platformStart(); @@ -732,7 +733,8 @@ void PipelineHandlerBase::stopDevice(Camera *camera) } /* Disable SOF event generation. */ - data->frontendDevice()->setFrameStartEnabled(false); + V4L2EventSubscription sub(V4L2Event::Type::FrameSync); + data->frontendDevice()->setEventsEnabled(sub, false); data->clearIncompleteRequests(); diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp index af3caf74ff56d4e6cc45ad098a93e9fde45449c2..b6c2aa9592bdd27b517274e4d2bd99a59040b42b 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -1679,7 +1679,8 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL data->delayedCtrls_->reset(); if (frameStartEmitter) { - ret = frameStartEmitter->setFrameStartEnabled(true); + V4L2EventSubscription sub(V4L2Event::Type::FrameSync); + ret = frameStartEmitter->setEventsEnabled(sub, true); if (ret) { stop(camera); return ret; @@ -1723,7 +1724,8 @@ void SimplePipelineHandler::stopDevice(Camera *camera) V4L2Subdevice *frameStartEmitter = data->frameStartEmitter_; if (frameStartEmitter) { - frameStartEmitter->setFrameStartEnabled(false); + V4L2EventSubscription sub(V4L2Event::Type::FrameSync); + 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 f231bbb17d8528aca274388b8dcaccc0dc339ad3..b93f3fff7280be1f0357341f217931cd290ba97f 100644 --- a/src/libcamera/v4l2_device.cpp +++ b/src/libcamera/v4l2_device.cpp @@ -58,8 +58,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) { } @@ -499,21 +498,32 @@ bool V4L2Device::supportsEvents(V4L2EventSubscription &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(V4L2EventSubscription &sub, bool enable) { - if (frameStartEnabled_ == enable) + if (sub.type() >= V4L2Event::Type::NumberOfEventTypes) + return -EINVAL; + + if (subscribedEvents_.find(sub) != subscribedEvents_.end()) return 0; struct v4l2_event_subscription event{}; - event.type = V4L2_EVENT_FRAME_SYNC; + + auto v4l2EventType = V4L2Event::typeToV4L2(sub.type()); + if (!v4l2EventType) + return -EINVAL; + + event.type = *v4l2EventType; + event.id = sub.id(); unsigned long request = enable ? VIDIOC_SUBSCRIBE_EVENT : VIDIOC_UNSUBSCRIBE_EVENT; @@ -522,7 +532,11 @@ int V4L2Device::setFrameStartEnabled(bool enable) return ret; fdEventNotifier_->setEnabled(enable); - frameStartEnabled_ = enable; + + if (enable) + subscribedEvents_.insert(sub); + else + subscribedEvents_.erase(sub); return ret; }