From patchwork Tue Jan 6 16:57:33 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25631 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 2B253BDCBF for ; Tue, 6 Jan 2026 16:58:06 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6A1BD61FBE; Tue, 6 Jan 2026 17:58:01 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="FeX9bElW"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5395A61FA0 for ; Tue, 6 Jan 2026 17:57:58 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 1C12B82A; Tue, 6 Jan 2026 17:57:37 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1767718657; bh=oymtRdUtaDirWfbx+uaquRMj74Ww9jzgsaysSZQy18M=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=FeX9bElW0pYD6JhMZXBxFrFrkBkejNjg4NU21F3VEDwzLlv/Kyoy6aelOxIpNWscQ DJoMU8JB/H5O1Bv7D9gMIAlVfzotA9dHF7m+VCP1Yx3Uw1/GN5zX9BUlFakfQnIVZk DSPZ8rRWFapW2e6NLA2Pq9yEWpXc/I5n/4niDXj8= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Isaac Scott , Paul Elder Subject: [PATCH v4 01/22] libcamera: controls: Strings are arrays Date: Tue, 6 Jan 2026 17:57:33 +0100 Message-ID: <20260106165754.1759831-2-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> References: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" `ControlId::isArray()` and `ControlValue::isArray()` disagree in the case of strings. Fix it by setting the static size of a string to `libcamera::dynamic_extent` to denote a dynamically sized array-like value. One unfortunate side effect of this change is that if there were string controls (there are none at the moment), then `cam` would display them with an extra `Size: n` annotation. Closes: https://gitlab.freedesktop.org/camera/libcamera/-/issues/255 Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi Reviewed-by: Isaac Scott Reviewed-by: Paul Elder --- include/libcamera/controls.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h index 44ff49647..e35dd7376 100644 --- a/include/libcamera/controls.h +++ b/include/libcamera/controls.h @@ -98,7 +98,7 @@ struct control_type { template<> struct control_type { static constexpr ControlType value = ControlTypeString; - static constexpr std::size_t size = 0; + static constexpr std::size_t size = libcamera::dynamic_extent; }; template<> From patchwork Tue Jan 6 16:57:34 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25632 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 7E70FBDCBF for ; Tue, 6 Jan 2026 16:58:08 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 200AA61FD1; Tue, 6 Jan 2026 17:58:02 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="NJkPujGN"; 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 8505B61F9F for ; Tue, 6 Jan 2026 17:57:58 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5D2CAF01; Tue, 6 Jan 2026 17:57:37 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1767718657; bh=T4amu+oRL1icmh4Aw3kyGhF5Ips5bMN2VLJrgGtAuw8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=NJkPujGNSNXtmoFWSoQUJVF3ggEyIGQyaA4f/5T7eIMpH91JFOj5i+FwvjRXBeZC/ yhK0EZ7taG+l3ZOX+yehSZs7oemEiesGHx/KBwpCfA7PlyDVF6CfFq52WdaFz73yZs FWUkAKZ73MmburXFhCjmIs4a1KiQGYKSUzCbqBjA= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , Kieran Bingham Subject: [PATCH v4 02/22] libcamera: controls: Add `ControlValueView` Date: Tue, 6 Jan 2026 17:57:34 +0100 Message-ID: <20260106165754.1759831-3-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> References: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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 `ControlValueView`, which is a non-owning read-only handle to a control value with a very similar interface. Signed-off-by: Barnabás Pőcze Reviewed-by: Paul Elder Reviewed-by: Kieran Bingham --- changes in v3: * reword some documentation changes in v2: * rewrite `ControlValue::toString()` in terms of `operator<<(std::ostream&, const ControlValueView&) to avoid duplication --- include/libcamera/controls.h | 68 +++++++++ src/libcamera/controls.cpp | 273 +++++++++++++++++++++++++---------- 2 files changed, 263 insertions(+), 78 deletions(-) diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h index e35dd7376..5b4ab4aa7 100644 --- a/include/libcamera/controls.h +++ b/include/libcamera/controls.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include #include @@ -25,6 +26,7 @@ namespace libcamera { class ControlValidator; +class ControlValueView; enum ControlType { ControlTypeNone, @@ -160,6 +162,8 @@ public: value.data(), value.size(), sizeof(typename T::value_type)); } + explicit ControlValue(const ControlValueView &cvv); + ~ControlValue(); ControlValue(const ControlValue &other); @@ -247,6 +251,70 @@ private: std::size_t numElements, std::size_t elementSize); }; +class ControlValueView +{ +public: + constexpr ControlValueView() noexcept + : type_(ControlTypeNone) + { + } + + ControlValueView(const ControlValue &cv) noexcept + : ControlValueView(cv.type(), cv.isArray(), cv.numElements(), + reinterpret_cast(cv.data().data())) + { + } + +#ifndef __DOXYGEN__ + // TODO: should have restricted access? + ControlValueView(ControlType type, bool isArray, std::size_t numElements, + const std::byte *data) noexcept + : type_(type), isArray_(isArray), numElements_(numElements), + data_(data) + { + assert(isArray || numElements == 1); + } +#endif + + [[nodiscard]] explicit operator bool() const { return type_ != ControlTypeNone; } + [[nodiscard]] ControlType type() const { return type_; } + [[nodiscard]] bool isNone() const { return type_ == ControlTypeNone; } + [[nodiscard]] bool isArray() const { return isArray_; } + [[nodiscard]] std::size_t numElements() const { return numElements_; } + [[nodiscard]] Span data() const; + + [[nodiscard]] bool operator==(const ControlValueView &other) const; + + [[nodiscard]] bool operator!=(const ControlValueView &other) const + { + return !(*this == other); + } + + template + [[nodiscard]] auto get() const + { + using TypeInfo = details::control_type>; + + assert(type_ == TypeInfo::value); + assert(isArray_ == (TypeInfo::size > 0)); + + if constexpr (TypeInfo::size > 0) { + return T(reinterpret_cast(data().data()), numElements_); + } else { + assert(numElements_ == 1); + return *reinterpret_cast(data().data()); + } + } + +private: + ControlType type_ : 8; + bool isArray_ = false; + uint32_t numElements_ = 0; + const std::byte *data_ = nullptr; +}; + +std::ostream &operator<<(std::ostream &s, const ControlValueView &v); + class ControlId { public: diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp index 1e1b49e6b..15e6b58ef 100644 --- a/src/libcamera/controls.cpp +++ b/src/libcamera/controls.cpp @@ -106,6 +106,16 @@ ControlValue::ControlValue() { } +/** + * \brief Construct a ControlValue from a ControlValueView + */ +ControlValue::ControlValue(const ControlValueView &cvv) + : ControlValue() +{ + set(cvv.type(), cvv.isArray(), cvv.data().data(), + cvv.numElements(), ControlValueSize[cvv.type()]); +} + /** * \fn template T ControlValue::ControlValue(const T &value) * \brief Construct a ControlValue of type T @@ -213,84 +223,7 @@ Span ControlValue::data() */ std::string ControlValue::toString() const { - if (type_ == ControlTypeNone) - return ""; - - const uint8_t *data = ControlValue::data().data(); - - if (type_ == ControlTypeString) - return std::string(reinterpret_cast(data), - numElements_); - - std::string str(isArray_ ? "[ " : ""); - - for (unsigned int i = 0; i < numElements_; ++i) { - switch (type_) { - case ControlTypeBool: { - const bool *value = reinterpret_cast(data); - str += *value ? "true" : "false"; - break; - } - case ControlTypeByte: { - const uint8_t *value = reinterpret_cast(data); - str += std::to_string(*value); - break; - } - case ControlTypeUnsigned16: { - const uint16_t *value = reinterpret_cast(data); - str += std::to_string(*value); - break; - } - case ControlTypeUnsigned32: { - const uint32_t *value = reinterpret_cast(data); - str += std::to_string(*value); - break; - } - case ControlTypeInteger32: { - const int32_t *value = reinterpret_cast(data); - str += std::to_string(*value); - break; - } - case ControlTypeInteger64: { - const int64_t *value = reinterpret_cast(data); - str += std::to_string(*value); - break; - } - case ControlTypeFloat: { - const float *value = reinterpret_cast(data); - str += std::to_string(*value); - break; - } - case ControlTypeRectangle: { - const Rectangle *value = reinterpret_cast(data); - str += value->toString(); - break; - } - case ControlTypeSize: { - const Size *value = reinterpret_cast(data); - str += value->toString(); - break; - } - case ControlTypePoint: { - const Point *value = reinterpret_cast(data); - str += value->toString(); - break; - } - case ControlTypeNone: - case ControlTypeString: - break; - } - - if (i + 1 != numElements_) - str += ", "; - - data += ControlValueSize[type_]; - } - - if (isArray_) - str += " ]"; - - return str; + return static_cast(std::ostringstream{} << ControlValueView(*this)).str(); } /** @@ -395,6 +328,190 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen storage_ = reinterpret_cast(new uint8_t[newSize]); } +/** + * \class ControlValueView + * \brief A non-owning view-like type to the value of a control + */ + +/** + * \fn ControlValueView::ControlValueView() + * \brief Construct an empty view + * \sa ControlValue::ControlValue() + */ + +/** + * \fn ControlValueView::ControlValueView(const ControlValue &v) + * \brief Construct a view referring to \a v + * + * The constructed view will refer to the value stored by \a v, and thus + * \a v must not be modified or destroyed before the view is destroyed. + * + * \sa ControlValue::ControlValue() + */ + +/** + * \fn ControlValueView::operator bool() const + * \brief Determine if the referenced ControlValue is valid + * \sa ControlValueView::isNone() + */ + +/** + * \fn ControlType ControlValueView::type() const + * \copydoc ControlValue::type() + * \sa ControlValue::type() + */ + +/** + * \fn ControlValueView::isNone() const + * \copydoc ControlValue::isNone() + * \sa ControlValue::isNone() + */ + +/** + * \fn ControlValueView::isArray() const + * \copydoc ControlValue::isArray() + * \sa ControlValue::isArray() + */ + +/** + * \fn ControlValueView::numElements() const + * \copydoc ControlValue::numElements() + * \sa ControlValue::numElements() + */ + +/** + * \copydoc ControlValue::data() + * \sa ControlValue::data() + */ +Span ControlValueView::data() const +{ + return { data_, numElements_ * ControlValueSize[type_] }; +} + +/** + * \copydoc ControlValue::operator==() + * \sa ControlValue::operator==() + * \sa ControlValueView::operator!=() + */ +bool ControlValueView::operator==(const ControlValueView &other) const +{ + if (type_ != other.type_) + return false; + + if (numElements_ != other.numElements_) + return false; + + if (isArray_ != other.isArray_) + return false; + + const auto d = data(); + + return memcmp(d.data(), other.data_, d.size_bytes()) == 0; +} + +/** + * \fn ControlValueView::operator!=() const + * \copydoc ControlValue::operator!=() + * \sa ControlValue::operator!=() + * \sa ControlValueView::operator==() + */ + +/** + * \fn template T ControlValueView::get() const + * \copydoc ControlValue::get() + * \sa ControlValue::get() + */ + +/** + * \brief Insert a text representation of a value into an output stream + * \sa ControlValue::toString() + */ +std::ostream &operator<<(std::ostream &s, const ControlValueView &v) +{ + const auto type = v.type(); + if (type == ControlTypeNone) + return s << "None"; + + const auto *data = v.data().data(); + const auto numElements = v.numElements(); + + if (type == ControlTypeString) + return s << std::string_view(reinterpret_cast(data), + numElements); + + const bool isArray = v.isArray(); + if (isArray) + s << "[ "; + + for (std::size_t i = 0; i < numElements; ++i) { + if (i > 0) + s << ", "; + + switch (type) { + case ControlTypeBool: { + const bool *value = reinterpret_cast(data); + s << (*value ? "true" : "false"); + break; + } + case ControlTypeByte: { + const auto *value = reinterpret_cast(data); + s << static_cast(*value); + break; + } + case ControlTypeUnsigned16: { + const auto *value = reinterpret_cast(data); + s << *value; + break; + } + case ControlTypeUnsigned32: { + const auto *value = reinterpret_cast(data); + s << *value; + break; + } + case ControlTypeInteger32: { + const auto *value = reinterpret_cast(data); + s << *value; + break; + } + case ControlTypeInteger64: { + const auto *value = reinterpret_cast(data); + s << *value; + break; + } + case ControlTypeFloat: { + const auto *value = reinterpret_cast(data); + s << std::fixed << *value; + break; + } + case ControlTypeRectangle: { + const auto *value = reinterpret_cast(data); + s << *value; + break; + } + case ControlTypeSize: { + const auto *value = reinterpret_cast(data); + s << *value; + break; + } + case ControlTypePoint: { + const auto *value = reinterpret_cast(data); + s << *value; + break; + } + case ControlTypeNone: + case ControlTypeString: + break; + } + + data += ControlValueSize[type]; + } + + if (isArray) + s << " ]"; + + return s; +} + /** * \class ControlId * \brief Control static metadata From patchwork Tue Jan 6 16:57:35 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25633 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 98D56BDCBF for ; Tue, 6 Jan 2026 16:58:10 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id DD63B61FBC; Tue, 6 Jan 2026 17:58:02 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="kgqg4ARy"; 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 A4B9F61FBB for ; Tue, 6 Jan 2026 17:57:58 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A1810581; Tue, 6 Jan 2026 17:57:37 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1767718657; bh=HpL9nwKiEV/kFGATM9qZ6WShd3CqncqXLsxA9TlcVKc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=kgqg4ARygsdzxvcIvgl6H2WKbAXnD7scx1u8zAVdakQWT4okjoJ5l8qSFhViC035a Bmpy8m7Rm8vGMps2YgvH14iCTZ5ftDtNaQQWg7/aBASBqFzIKV//8E5Ef1ulCziENZ noGuIZ01CvwV8MPmGAWe9wrNs7BcsbUJpHpnhoiE= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham Subject: [PATCH v4 03/22] libcamera: base: Add file for C++20 polyfills Date: Tue, 6 Jan 2026 17:57:35 +0100 Message-ID: <20260106165754.1759831-4-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> References: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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 `cxx20.h` that will contain (possibly limited) C++17 implementations of some C++20 standard library features. Signed-off-by: Barnabás Pőcze Acked-by: Kieran Bingham --- include/libcamera/base/internal/cxx20.h | 18 ++++++++++++++++++ include/libcamera/base/meson.build | 7 +++++++ 2 files changed, 25 insertions(+) create mode 100644 include/libcamera/base/internal/cxx20.h diff --git a/include/libcamera/base/internal/cxx20.h b/include/libcamera/base/internal/cxx20.h new file mode 100644 index 000000000..70a43f83c --- /dev/null +++ b/include/libcamera/base/internal/cxx20.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas on Board Oy + * + * C++20 polyfills + */ + +#pragma once + +/** + * \internal + * \file cxx20.h + * \brief C++17 implementations of certain C++20 types and functions + */ + +namespace libcamera::internal::cxx20 { + +} /* namespace libcamera::internal::cxx20 */ diff --git a/include/libcamera/base/meson.build b/include/libcamera/base/meson.build index f84b51419..bb4ed556b 100644 --- a/include/libcamera/base/meson.build +++ b/include/libcamera/base/meson.build @@ -32,10 +32,17 @@ libcamera_base_private_headers = files([ 'utils.h', ]) +libcamera_base_internal_headers = files([ + 'internal/cxx20.h', +]) + libcamera_base_headers = [ libcamera_base_public_headers, libcamera_base_private_headers, + libcamera_base_internal_headers, ] install_headers(libcamera_base_public_headers, subdir : libcamera_base_include_dir) +install_headers(libcamera_base_internal_headers, + subdir : libcamera_base_include_dir / 'internal') From patchwork Tue Jan 6 16:57:36 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25634 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 E30B2BDCBF for ; Tue, 6 Jan 2026 16:58:11 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id CEB8661FC4; Tue, 6 Jan 2026 17:58:03 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ABelmhwS"; 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 EBD8161FA0 for ; Tue, 6 Jan 2026 17:57:58 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DAC6D1340; Tue, 6 Jan 2026 17:57:37 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1767718658; bh=3Wv7dFN/RmtdtVgzwW1D/1+hp3VVAILss1MVsZ07raU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ABelmhwSBjFGKJl8iPRMSxHhGMmdZGhAPEWM+cxY6fxIP3p65LiZ57a5IG5ar+dLH ceZNM/yRyA2uhF75uRyttRs6LXXIYVFVdU5cqjgQBAdLOQqCrdm8qt7kuvKScjwrFd V/SdGkoCBE59pBZAtiE7yJA9YDDuSaWeiWlyLWYA= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder , Kieran Bingham Subject: [PATCH v4 04/22] libcamera: base: cxx20: Add `type_identity{,_t}` Date: Tue, 6 Jan 2026 17:57:36 +0100 Message-ID: <20260106165754.1759831-5-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> References: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" `type_identity_t` can be used to force non-deduced contexts in templates, such as: void f(T x, type_identity_t y) when calling `f(1u, 2)`, without `type_identity_t`, the compiler could not unambiguously deduce `T` (unsigned int vs int). However, with `type_identity_t`, the type of the argument passed to `y` will not be used to deduce `T`, only the argument passed to `x`. See https://en.cppreference.com/w/cpp/types/type_identity Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi Reviewed-by: Paul Elder Reviewed-by: Kieran Bingham --- include/libcamera/base/internal/cxx20.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/include/libcamera/base/internal/cxx20.h b/include/libcamera/base/internal/cxx20.h index 70a43f83c..1f4caf56f 100644 --- a/include/libcamera/base/internal/cxx20.h +++ b/include/libcamera/base/internal/cxx20.h @@ -15,4 +15,28 @@ namespace libcamera::internal::cxx20 { +/** + * \internal + * \brief std::type_identity + * + * Implementation of std::type_identity for C++17. + */ +template struct type_identity { + /** + * \internal + * \brief std::type_identity::type + * + * Type alias matching the template parameter. + */ + using type = T; +}; + +/** + * \internal + * \brief std::type_identity_t + * + * Implementation of std::type_identity_t for C++17. + */ +template using type_identity_t = typename type_identity::type; + } /* namespace libcamera::internal::cxx20 */ From patchwork Tue Jan 6 16:57:37 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25635 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 19FB8BDCBF for ; Tue, 6 Jan 2026 16:58:13 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A71D261FD6; Tue, 6 Jan 2026 17:58:04 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ngl+TCMg"; 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 393FC61F9F for ; Tue, 6 Jan 2026 17:57:59 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 2DCDD16CD; Tue, 6 Jan 2026 17:57:38 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1767718658; bh=mkcW4kOYD2jP66GKK3ji8SaLitSezNm7hJvxYbBYpC0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ngl+TCMgutPonxKXPhXK/PTSS1GuqGqmSESqTEg4MGyDlqtPfVmvnLh+UjEySgqtP 2cJVJnFPgoi6gZuW8i3kE5uqJC277NQka5oj6+23quUs1tXEFUJ6X1b7NzH+B1GTbW Yo5k5I5CBQni4Z8uyolLHK8G4XC7H/xTNuYGG5/M= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , Kieran Bingham Subject: [PATCH v4 05/22] libcamera: base: cxx20: Add `has_single_bit()` Date: Tue, 6 Jan 2026 17:57:37 +0100 Message-ID: <20260106165754.1759831-6-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> References: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" `has_single_bit()` checks if the given unsigned integer has a single bit set, that is, whether the number is a power of two or not. Not all requirements of the C++20 standard are implemented. See https://en.cppreference.com/w/cpp/numeric/has_single_bit Signed-off-by: Barnabás Pőcze Reviewed-by: Paul Elder Reviewed-by: Kieran Bingham --- include/libcamera/base/internal/cxx20.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/include/libcamera/base/internal/cxx20.h b/include/libcamera/base/internal/cxx20.h index 1f4caf56f..d47168a70 100644 --- a/include/libcamera/base/internal/cxx20.h +++ b/include/libcamera/base/internal/cxx20.h @@ -7,6 +7,8 @@ #pragma once +#include + /** * \internal * \file cxx20.h @@ -39,4 +41,18 @@ template struct type_identity { */ template using type_identity_t = typename type_identity::type; +/** + * \internal + * \brief std::has_single_bit() + * + * Implementation of std::has_single_bit() for C++17. + */ +template +constexpr bool has_single_bit(T x) noexcept +{ + static_assert(std::is_unsigned_v); + + return x != 0 && (x & (x - 1)) == 0; +} + } /* namespace libcamera::internal::cxx20 */ From patchwork Tue Jan 6 16:57:38 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25636 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 2974BBDCBF for ; Tue, 6 Jan 2026 16:58:14 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 58A0B61FC1; Tue, 6 Jan 2026 17:58:05 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="KoNuWvF4"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 77E3A61FC1 for ; Tue, 6 Jan 2026 17:57:59 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 697A51733; Tue, 6 Jan 2026 17:57:38 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1767718658; bh=7ADgsOd5Kh8W+29vzIwsn64OFI56wj3oNkbV31uALAA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=KoNuWvF4YeRFMqEpxIrsV5QuXWkEunvkaTXBXVf0vRvbMA7ONQtI+cfHbsizYitRo sBaya+YfCkV2/miXujvMCnJqVP9jo1Ifwc18kks/d1Zhe1NmpUYS7dRp/49TRPQHsC 2kqHz9UrVLHXFuypzD2D8B/5dpqyXIjpetxYh4gA= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , Kieran Bingham Subject: [PATCH v4 06/22] libcamera: base: Add alignment utility functions Date: Tue, 6 Jan 2026 17:57:38 +0100 Message-ID: <20260106165754.1759831-7-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> References: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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 couple internal functions to check alignment, and to align integers, pointers up to a given alignment. Signed-off-by: Barnabás Pőcze Reviewed-by: Paul Elder Reviewed-by: Kieran Bingham --- changes in v3: * documentation --- include/libcamera/base/internal/align.h | 135 ++++++++++++++++++++++++ include/libcamera/base/meson.build | 1 + 2 files changed, 136 insertions(+) create mode 100644 include/libcamera/base/internal/align.h diff --git a/include/libcamera/base/internal/align.h b/include/libcamera/base/internal/align.h new file mode 100644 index 000000000..d8ee4e369 --- /dev/null +++ b/include/libcamera/base/internal/align.h @@ -0,0 +1,135 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas on Board Oy + * + * Alignment utilities + */ + +#pragma once + +#include +#include +#include + +#include + +/** + * \internal + * \file align.h + * \brief Utilities for handling alignment + */ + +namespace libcamera::internal::align { + +/** + * \internal + * \brief Check if pointer is aligned + * \param[in] p pointer to check + * \param[in] alignment desired alignment + * \return true if \a p is at least \a alignment aligned, false otherwise + */ +inline bool is(const void *p, std::uintptr_t alignment) +{ + assert(alignment > 0); + + return reinterpret_cast(p) % alignment == 0; +} + +/** + * \internal + * \brief Align number up + * \param[in] x number to check + * \param[in] alignment desired alignment + * \return true if \a p is at least \a alignment aligned, false otherwise + */ +template +constexpr T up(T x, cxx20::type_identity_t alignment) +{ + static_assert(std::is_unsigned_v); + assert(alignment > 0); + + const auto padding = (alignment - (x % alignment)) % alignment; + assert(x + padding >= x); + + return x + padding; +} + +/** + * \internal + * \brief Align pointer up + * \param[in] p pointer to align + * \param[in] alignment desired alignment + * \return \a p up-aligned to \a alignment + */ +template +auto *up(T *p, std::uintptr_t alignment) +{ + using U = std::conditional_t< + std::is_const_v, + const std::byte, + std::byte + >; + + return reinterpret_cast(up(reinterpret_cast(p), alignment)); +} + +/** + * \internal + * \brief Align pointer up + * \param[in] size required number of bytes + * \param[in] alignment required alignment + * \param[in] ptr base pointer + * \param[in] avail number of available bytes + * + * This function checks if the storage starting at \a ptr and continuing for \a avail + * bytes (if \a avail is nullptr, then no size checking is done) can accommodate \a size + * bytes of data aligned to \a alignment. If so, then a pointer to the start is the returned + * and \a ptr is adjusted to point right after the section of \a size bytes. If present, + * \a avail is also adjusted. + * + * Similar to std::align(). + * + * \return the appropriately aligned pointer, or nullptr if there is not enough space + */ +template +T *up(std::size_t size, std::size_t alignment, T *&ptr, std::size_t *avail = nullptr) +{ + assert(alignment > 0); + + auto p = reinterpret_cast(ptr); + const auto padding = (alignment - (p % alignment)) % alignment; + + if (avail) { + if (size > *avail || padding > *avail - size) + return nullptr; + + *avail -= size + padding; + } + + p += padding; + ptr = reinterpret_cast(p + size); + + return reinterpret_cast(p); +} + +/** + * \internal + * \brief Align pointer up + * \tparam U desired type + * \param[in] ptr base pointer + * \param[in] avail number of available bytes + * + * A convenience wrapper around libcamera::internal::align::up(std::size_t, std::size_t, T *&, std::size_t *) + * that takes the size and alignment information from the type \a U. + * + * \sa libcamera::internal::align::up(std::size_t, std::size_t, T *&, std::size_t *) + */ +template +U *up(T *&ptr, std::size_t *avail = nullptr) +{ + return reinterpret_cast, const U, U> *>( + up(sizeof(U), alignof(U), ptr, avail) + ); +} + +} /* namespace libcamera::internal::align */ diff --git a/include/libcamera/base/meson.build b/include/libcamera/base/meson.build index bb4ed556b..305dfa0dd 100644 --- a/include/libcamera/base/meson.build +++ b/include/libcamera/base/meson.build @@ -34,6 +34,7 @@ libcamera_base_private_headers = files([ libcamera_base_internal_headers = files([ 'internal/cxx20.h', + 'internal/align.h', ]) libcamera_base_headers = [ From patchwork Tue Jan 6 16:57:39 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25637 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 E7B74C3213 for ; Tue, 6 Jan 2026 16:58:14 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 26D1161FD8; Tue, 6 Jan 2026 17:58:06 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="aZ0dLCWf"; 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 D183E61F9F for ; Tue, 6 Jan 2026 17:57:59 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A8F32177D for ; Tue, 6 Jan 2026 17:57:38 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1767718658; bh=DHxT6wlOPt4shwXhJw6sA0nNb4bsyxvIbEuYIdqiVYQ=; h=From:To:Subject:Date:In-Reply-To:References:From; b=aZ0dLCWfTzWuB8NitK/vEjgOIB9PS04XPXTSTgJDUtTdgPapToV3N22x4RQWUj22d 5/MpKR/6nyQPHTUE5k+YCikz7HO6ma3yIj9I5VwkFrRwxa5lu/ANG7L2X0WRBDbCLL B44OzrdJdoCxz9mJIwJyGMsBeFqMMBdrcEz2pn9A= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [PATCH v4 07/22] libcamera: Add `MetadataList` Date: Tue, 6 Jan 2026 17:57:39 +0100 Message-ID: <20260106165754.1759831-8-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> References: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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 dedicated `MetadataList` type, whose purpose is to store the metadata reported by a camera for a given request. Previously, a `ControlList` was used for this purpose. The reason for introducing a separate type is to simplify the access to the returned metadata during the entire lifetime of a request. Specifically, for early metadata completion to be easily usable it should be guaranteed that any completed metadata item can be accessed and looked up at least until the associated requested is reused with `Request::reuse()`. However, when a metadata item is completed early, the pipeline handler might still work on the request in the `CameraManager`'s private thread, therefore there is an inherent synchronization issue when an application accesses early metadata. Restricting the user to only access the metadata items of a not yet completed request in the early metadata availability signal handler by ways of documenting or enforcing it at runtime could be an option, but it is not too convenient for the user. The current `ControlList` implementation employs an `std::unordered_map`, so pointers remain stable when the container is modified, so an application could keep accessing particular metadata items outside the signal handler, but this fact is far from obvious, and the user would still not be able to make a copy of all metadata or do lookups based on the numeric ids or the usual `libcamera::Control<>` objects, thus some type safety is lost. The above also requires that each metadata item is only completed once for a given request, but this does not appear to be serious limitation, and in fact, this restriction is enforced by `MetadataList`. The introduced `MetadataList` supports single writer - multiple reader scenarios, and it can be set, looked-up, and copied in a wait-free fashion without introducing data races or other synchronization issues. This is achieved by requiring the possible set of metadata items to be known (such set is stored in a `MetadataListPlan` object). Based on the this plan, a single contiguous allocation is made to accommodate all potential metadata items. Due to this single contiguous allocation that is not modified during the lifetime of a `MetadataList` and atomic modifications, it is possible to easily gaurantee thread-safe set, lookup, and copy; assuming there is only ever a single writer. Signed-off-by: Barnabás Pőcze --- changes in v3: * make `merge()` transactional * move certain functions out of the header * documentation adjustments changes in v2: * remove multiple not strictly necessary functions --- include/libcamera/meson.build | 2 + include/libcamera/metadata_list.h | 529 ++++++++++++++++++++ include/libcamera/metadata_list_plan.h | 110 +++++ src/libcamera/meson.build | 1 + src/libcamera/metadata_list.cpp | 594 +++++++++++++++++++++++ test/controls/meson.build | 12 +- test/controls/metadata_list.cpp | 205 ++++++++ test/controls/metadata_list_iter_uaf.cpp | 36 ++ 8 files changed, 1488 insertions(+), 1 deletion(-) create mode 100644 include/libcamera/metadata_list.h create mode 100644 include/libcamera/metadata_list_plan.h create mode 100644 src/libcamera/metadata_list.cpp create mode 100644 test/controls/metadata_list.cpp create mode 100644 test/controls/metadata_list_iter_uaf.cpp diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build index 30ea76f94..410b548dd 100644 --- a/include/libcamera/meson.build +++ b/include/libcamera/meson.build @@ -12,6 +12,8 @@ libcamera_public_headers = files([ 'framebuffer_allocator.h', 'geometry.h', 'logging.h', + 'metadata_list.h', + 'metadata_list_plan.h', 'orientation.h', 'pixel_format.h', 'request.h', diff --git a/include/libcamera/metadata_list.h b/include/libcamera/metadata_list.h new file mode 100644 index 000000000..9366dcbc3 --- /dev/null +++ b/include/libcamera/metadata_list.h @@ -0,0 +1,529 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas On Board Oy + * + * Metadata list + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace libcamera { + +class MetadataListPlan; + +class MetadataList +{ +private: + /** + * \brief The entry corresponding to a potential value in the list + */ + struct Entry { + static constexpr uint32_t kInvalidOffset = -1; + + /** + * \brief Numeric identifier in the list + */ + const uint32_t tag; + + /** + * \brief Number of bytes available for the value + */ + const uint32_t capacity; + + /** + * \brief Alignment of the value + */ + const uint32_t alignment; + + const ControlType type; + const bool isArray; + + /** + * \brief Offset of the ValueHeader of the value pertaining to this entry + * + * Offset from the beginning of the allocation, and + * and _not_ relative to `contentOffset_`. + */ + std::atomic_uint32_t headerOffset = kInvalidOffset; + + [[nodiscard]] + std::optional hasValue() const + { + auto offset = headerOffset.load(std::memory_order_relaxed); + if (offset == kInvalidOffset) + return {}; + + return offset; + } + + [[nodiscard]] + std::optional acquireData() const + { + auto offset = hasValue(); + if (offset) { + /* sync with release-store on `headerOffset` in `MetadataList::set()` */ + std::atomic_thread_fence(std::memory_order_acquire); + } + + return offset; + } + }; + + /** + * \brief The header describing a value in the list + */ + struct ValueHeader { + /** + * \brief Numeric identifier of the value in the list + */ + uint32_t tag; + + /** + * \brief Number of bytes used by the value + * + * This can be calculated using type and numElements, it is stored + * here to facilitate easier iteration in the buffer. + */ + uint32_t size; + + /** + * \brief Alignment of the value + */ + uint32_t alignment; + + /** + * \brief Type of the value + */ + ControlType type; + + /** + * \brief Whether the value is an array + */ + bool isArray; + + /** + * \brief Number of elements in the value + */ + uint32_t numElements; + }; + + struct State { + /** + * \brief Number of items present in the list + */ + uint32_t count; + + /** + * \brief Number of bytes used in the buffer + */ + uint32_t fill; + }; + +public: + explicit MetadataList(const MetadataListPlan &plan); + + MetadataList(const MetadataList &) = delete; + MetadataList(MetadataList &&) = delete; + + MetadataList &operator=(const MetadataList &) = delete; + MetadataList &operator=(MetadataList &&) = delete; + + ~MetadataList(); + + // \todo want these? + [[nodiscard]] std::size_t size() const { return state_.load(std::memory_order_relaxed).count; } + [[nodiscard]] bool empty() const { return state_.load(std::memory_order_relaxed).fill == 0; } + + enum class SetError { + UnknownTag = 1, + AlreadySet, + SizeMismatch, + TypeMismatch, + }; + + [[nodiscard]] + SetError set(uint32_t tag, ControlValueView v) + { + auto *e = find(tag); + if (!e) + return SetError::UnknownTag; + + return set(*e, v); + } + + template + [[nodiscard]] + SetError set(const Control &ctrl, const internal::cxx20::type_identity_t &value) + { + using TypeInfo = libcamera::details::control_type; + + if constexpr (TypeInfo::size > 0) { + static_assert(std::is_trivially_copyable_v); + + return set(ctrl.id(), { + TypeInfo::value, + true, + value.size(), + reinterpret_cast(value.data()), + }); + } else { + static_assert(std::is_trivially_copyable_v); + + return set(ctrl.id(), { + TypeInfo::value, + false, + 1, + reinterpret_cast(&value), + }); + } + } + + template + [[nodiscard]] + std::optional get(const Control &ctrl) const + { + ControlValueView v = get(ctrl.id()); + if (!v) + return {}; + + return v.get(); + } + + // \todo operator ControlListView() const ? + // \todo explicit operator ControlList() const ? + + [[nodiscard]] + ControlValueView get(uint32_t tag) const + { + const auto *e = find(tag); + if (!e) + return {}; + + return dataOf(*e); + } + + void clear(); + + class iterator + { + public: + using difference_type = std::ptrdiff_t; + using value_type = std::pair; + using pointer = void; + using reference = value_type; + using iterator_category = std::forward_iterator_tag; + + iterator() = default; + + iterator& operator++() + { + const auto &h = header(); + + p_ += sizeof(h); + p_ = internal::align::up(p_, h.alignment); + p_ += h.size; + p_ = internal::align::up(p_, alignof(decltype(h))); + + return *this; + } + + iterator operator++(int) + { + auto copy = *this; + ++*this; + return copy; + } + + [[nodiscard]] + reference operator*() const + { + const auto &h = header(); + const auto *data = internal::align::up(p_ + sizeof(h), h.alignment); + + return { h.tag, { h.type, h.isArray, h.numElements, data } }; + } + + [[nodiscard]] + bool operator==(const iterator &other) const + { + return p_ == other.p_; + } + + [[nodiscard]] + bool operator!=(const iterator &other) const + { + return !(*this == other); + } + + private: + iterator(const std::byte *p) + : p_(p) + { + } + + [[nodiscard]] + const ValueHeader &header() const + { + return *reinterpret_cast(p_); + } + + friend MetadataList; + + const std::byte *p_ = nullptr; + }; + + [[nodiscard]] + iterator begin() const + { + return { p_ + contentOffset_ }; + } + + [[nodiscard]] + iterator end() const + { + return { p_ + contentOffset_ + state_.load(std::memory_order_acquire).fill }; + } + + class Diff + { + public: + // \todo want these? + [[nodiscard]] explicit operator bool() const { return !empty(); } + [[nodiscard]] bool empty() const { return start_ == stop_; } + [[nodiscard]] std::size_t size() const { return changed_; } + [[nodiscard]] const MetadataList &list() const { return *list_; } + + [[nodiscard]] + ControlValueView get(uint32_t tag) const + { + const auto *e = list_->find(tag); + if (!e) + return {}; + + auto o = e->acquireData(); + if (!o) + return {}; + + if (!(start_ <= *o && *o < stop_)) + return {}; + + return list_->dataOf(*o); + } + + template + [[nodiscard]] + std::optional get(const Control &ctrl) const + { + ControlValueView v = get(ctrl.id()); + if (!v) + return {}; + + return v.get(); + } + + [[nodiscard]] + iterator begin() const + { + return { list_->p_ + start_ }; + } + + [[nodiscard]] + iterator end() const + { + return { list_->p_ + stop_ }; + } + + private: + Diff(const MetadataList &list, std::size_t changed, std::size_t oldFill, std::size_t newFill) + : list_(&list), + changed_(changed), + start_(list.contentOffset_ + oldFill), + stop_(list.contentOffset_ + newFill) + { + } + + friend MetadataList; + friend struct Checkpoint; + + /** + * \brief Source lits of the checkpoint + */ + const MetadataList *list_ = nullptr; + + /** + * \brief Number of items contained in the diff + */ + std::size_t changed_; + + /** + * \brief Offset of the ValueHeader of the first value in the diff + */ + std::size_t start_; + + /** + * \brief Offset of the "past-the-end" ValueHeader of the diff + */ + std::size_t stop_; + }; + + [[nodiscard]] std::optional merge(const ControlList &other); + + class Checkpoint + { + public: + [[nodiscard]] + Diff diffSince() const + { + /* sync with release-store on `state_` in `set()` */ + const auto curr = list_->state_.load(std::memory_order_acquire); + + assert(state_.count <= curr.count); + assert(state_.fill <= curr.fill); + + return { + *list_, + curr.count - state_.count, + state_.fill, + curr.fill, + }; + } + + private: + Checkpoint(const MetadataList &list) + : list_(&list), + state_(list.state_.load(std::memory_order_relaxed)) + { + } + + friend MetadataList; + + /** + * \brief Source list of the checkpoint + */ + const MetadataList *list_ = nullptr; + + /** + * \brief State of the list when the checkpoint was created + */ + State state_ = {}; + }; + + [[nodiscard]] + Checkpoint checkpoint() const + { + return { *this }; + } + +private: + [[nodiscard]] + static constexpr std::size_t entriesOffset() + { + return 0; + } + + [[nodiscard]] + static constexpr std::size_t contentOffset(std::size_t entries) + { + return internal::align::up(entriesOffset() + entries * sizeof(Entry), alignof(ValueHeader)); + } + + [[nodiscard]] + Span entries() const + { + return { reinterpret_cast(p_ + entriesOffset()), capacity_ }; + } + + [[nodiscard]] + Entry *find(uint32_t tag) const + { + const auto entries = this->entries(); + auto it = std::partition_point(entries.begin(), entries.end(), [&](const auto &e) { + return e.tag < tag; + }); + + if (it == entries.end() || it->tag != tag) + return nullptr; + + return &*it; + } + + [[nodiscard]] + ControlValueView dataOf(const Entry &e) const + { + const auto o = e.acquireData(); + return o ? dataOf(*o) : ControlValueView{ }; + } + + [[nodiscard]] + ControlValueView dataOf(std::size_t headerOffset) const + { + assert(headerOffset <= alloc_ - sizeof(ValueHeader)); + assert(internal::align::is(p_ + headerOffset, alignof(ValueHeader))); + + const auto *vh = reinterpret_cast(p_ + headerOffset); + const auto *p = reinterpret_cast(vh) + sizeof(*vh); + std::size_t avail = p_ + alloc_ - p; + + const auto *data = internal::align::up(vh->size, vh->alignment, p, &avail); + assert(data); + + return { vh->type, vh->isArray, vh->numElements, data }; + } + + [[nodiscard]] SetError set(Entry &e, ControlValueView v); + + [[nodiscard]] + std::pair + set(const Entry &e, ControlValueView v, State &s); + + /** + * \brief Number of \ref Entry "entries" + */ + std::size_t capacity_ = 0; + + /** + * \brief Offset of the first ValueHeader + */ + std::size_t contentOffset_ = -1; + + /** + * \brief Pointer to the allocation + */ + std::byte *p_ = nullptr; + + /** + * \brief Size of the allocation in bytes + */ + std::size_t alloc_ = 0; + + /** + * \brief Current state of the list + */ + std::atomic state_ = State{}; + + // \todo ControlIdMap in any way shape or form? + + /* + * If this is problematic on a 32-bit architecture, then + * `count` can be stored in a separate atomic variable + * but then `Diff::changed_` must be removed since the fill + * level and item count cannot be retrieved atomically. + */ + static_assert(decltype(state_)::is_always_lock_free); +}; + +} /* namespace libcamera */ diff --git a/include/libcamera/metadata_list_plan.h b/include/libcamera/metadata_list_plan.h new file mode 100644 index 000000000..8f058e5c0 --- /dev/null +++ b/include/libcamera/metadata_list_plan.h @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas On Board Oy + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include + +namespace libcamera { + +class MetadataListPlan +{ +public: + struct Entry { + uint32_t size; + uint32_t alignment; // \todo is this necessary? + uint32_t numElements; + ControlType type; + bool isArray; + }; + + [[nodiscard]] bool empty() const { return items_.empty(); } + [[nodiscard]] std::size_t size() const { return items_.size(); } + [[nodiscard]] decltype(auto) begin() const { return items_.begin(); } + [[nodiscard]] decltype(auto) end() const { return items_.end(); } + void clear() { items_.clear(); } + + template< + typename T, + std::enable_if_t::size != libcamera::dynamic_extent> * = nullptr + > + MetadataListPlan &set(const Control &ctrl) + { + if constexpr (libcamera::details::control_type::size > 0) { + static_assert(libcamera::details::control_type::size != libcamera::dynamic_extent); + + return set( + ctrl.id(), + libcamera::details::control_type::size, + true + ); + } else { + return set(ctrl.id(), 1, false); + } + } + + template< + typename T, + std::enable_if_t::size == libcamera::dynamic_extent> * = nullptr + > + MetadataListPlan &set(const Control &ctrl, std::size_t numElements) + { + return set(ctrl.id(), numElements, true); + } + + [[nodiscard]] + bool set(uint32_t tag, + std::size_t size, std::size_t alignment, + std::size_t numElements, ControlType type, bool isArray); + + [[nodiscard]] + const Entry *get(uint32_t tag) const + { + auto it = items_.find(tag); + if (it == items_.end()) + return nullptr; + + return &it->second; + } + + [[nodiscard]] + const Entry *get(const ControlId &cid) const + { + const auto *e = get(cid.id()); + if (!e) + return nullptr; + + if (e->type != cid.type() || e->isArray != cid.isArray()) + return nullptr; + + return e; + } + +private: + std::map items_; + + template + MetadataListPlan &set(uint32_t tag, std::size_t numElements, bool isArray) + { + static_assert(std::is_trivially_copyable_v); + + [[maybe_unused]] bool ok = set(tag, + sizeof(T), alignof(T), + numElements, details::control_type::value, isArray); + assert(ok); + + return *this; + } +}; + +} /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index 575408b2c..b569996bb 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -9,6 +9,7 @@ libcamera_public_sources = files([ 'framebuffer.cpp', 'framebuffer_allocator.cpp', 'geometry.cpp', + 'metadata_list.cpp', 'orientation.cpp', 'pixel_format.cpp', 'request.cpp', diff --git a/src/libcamera/metadata_list.cpp b/src/libcamera/metadata_list.cpp new file mode 100644 index 000000000..5a5114fc7 --- /dev/null +++ b/src/libcamera/metadata_list.cpp @@ -0,0 +1,594 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas On Board Oy + */ + +#include + +#include +#include +#include + +#include +#include + +#include + +#if __has_include() +#if __SANITIZE_ADDRESS__ /* gcc */ +#include +#define HAS_ASAN 1 +#elif defined(__has_feature) +#if __has_feature(address_sanitizer) /* clang */ +#include +#define HAS_ASAN 1 +#endif +#endif +#endif + +namespace libcamera { + +/** + * \class MetadataListPlan + * \brief Class to hold the possible set of metadata items for a MetadataList + */ + +/** + * \class MetadataListPlan::Entry + * \brief Details of a metadata item + */ + +/** + * \internal + * \var MetadataListPlan::Entry::size + * \brief Number of bytes in a single element + * + * \var MetadataListPlan::Entry::alignment + * \brief Required alignment of the elements + * \endinternal + * + * \var MetadataListPlan::Entry::numElements + * \brief Number of elements in the value + * \sa ControlValueView::numElements() + * + * \var MetadataListPlan::Entry::type + * \brief The type of the value + * \sa ControlValueView::type() + * + * \var MetadataListPlan::Entry::isArray + * \brief Whether or not the value is array-like + * \sa ControlValueView::isArray() + */ + +/** + * \fn MetadataListPlan::begin() const + * \brief Retrieve the begin iterator + */ + +/** + * \fn MetadataListPlan::end() const + * \brief Retrieve the end iterator + */ + +/** + * \fn MetadataListPlan::size() const + * \brief Retrieve the number of entries + */ + +/** + * \fn MetadataListPlan::empty() const + * \brief Check if empty + */ + +/** + * \internal + * \fn MetadataListPlan::clear() + * \brief Remove all controls + */ + +/** + * \internal + * \fn MetadataListPlan::set(const Control &ctrl) + * \brief Add an entry for the given control to the metadata list plan + * \param[in] ctrl The control + */ + +/** + * \internal + * \fn MetadataListPlan::set(const Control &ctrl, std::size_t count) + * \brief Add an entry for the given dynamically-sized control to the metadata list plan + * \param[in] ctrl The control + * \param[in] count The maximum number of elements + * + * Add the dynamically-sized control \a ctrl to the metadata list plan with a maximum + * capacity of \a count elements. + */ + +/** + * \internal + * \brief Add an entry to the metadata list plan + * \return \a true if the entry has been added, or \a false if the given parameters + * would result in an invalid entry + * + * This functions adds an entry with essentially arbitrary parameters, without deriving + * them from a given ControlId instance. This is mainly used when deserializing. + */ +bool MetadataListPlan::set(uint32_t tag, + std::size_t size, std::size_t alignment, + std::size_t numElements, ControlType type, bool isArray) +{ + if (size == 0 || size > std::numeric_limits::max()) + return false; + if (alignment > std::numeric_limits::max()) + return false; + if (!internal::cxx20::has_single_bit(alignment)) + return false; + if (numElements > std::numeric_limits::max() / size) + return false; + if (!isArray && numElements != 1) + return false; + + items_[tag] = { + .size = uint32_t(size), + .alignment = uint32_t(alignment), + .numElements = uint32_t(numElements), + .type = type, + .isArray = isArray, + }; + + return true; +} + +/** + * \fn MetadataListPlan::get(uint32_t tag) const + * \brief Find the \ref Entry "entry" with the given identifier + */ + +/** + * \fn MetadataListPlan::get(const ControlId &cid) const + * \brief Find the \ref Entry "entry" for the given ControlId + * + * The \ref Entry "entry" is only returned if ControlId::type() and ControlId::isArray() + * of \a cid matches Entry::type and Entry::isArray, respectively. + */ + +/** + * \class MetadataList + * \brief Class to hold metadata items + * + * Similarly to a ControlList, a MetadataList provides a way for applications to + * query and enumerate the values of controls. However, a MetadataList allows + * thread-safe access to the data for applications, which is needed so that + * applications can process the metadata of in-flight \ref Request "requests" + * (for which purposes ControlList is not suitable). + * + * \internal + * A MetadataList is essentially an append-only list of values. Internally, it + * contains a single allocation that is divided into two parts: + * + * * a list of entries sorted by their numeric identifiers + * (each corresponding to an entry in the MetadataListPlan); + * * a series of ValueHeader + data bytes that contain the actual data. + * + * When a value is added to the list, the corresponding Entry is updated, and the + * ValueHeader and the data bytes are appended to the end of the second part. + * + * The reason for the redundancy is the following: the first part enables quick + * lookups (binary search); the second part provides a self-contained flat buffer + * of all the data. + */ + +/** + * \internal + * \brief Construct a metadata list according to \a plan + * + * Construct a metadata list according to the provided \a plan. + */ +MetadataList::MetadataList(const MetadataListPlan &plan) + : capacity_(plan.size()), + contentOffset_(MetadataList::contentOffset(capacity_)), + alloc_(contentOffset_) +{ + for (const auto &[tag, e] : plan) { + alloc_ += sizeof(ValueHeader); + alloc_ += e.alignment - 1; // XXX: this is the maximum + alloc_ += e.size * e.numElements; + alloc_ += alignof(ValueHeader) - 1; // XXX: this is the maximum + } + + p_ = static_cast(::operator new(alloc_)); + + auto *entries = reinterpret_cast(p_ + entriesOffset()); + auto it = plan.begin(); + + for (std::size_t i = 0; i < capacity_; i++, ++it) { + const auto &[tag, e] = *it; + + new (static_cast(&entries[i])) Entry{ + .tag = tag, + .capacity = e.size * e.numElements, + .alignment = e.alignment, + .type = e.type, + .isArray = e.isArray, + }; + } + +#if HAS_ASAN + ::__sanitizer_annotate_contiguous_container( + p_ + contentOffset_, p_ + alloc_, + p_ + alloc_, p_ + contentOffset_ + ); +#endif +} + +MetadataList::~MetadataList() +{ + for (auto &e : entries()) + std::destroy_at(&e); + +#if HAS_ASAN + /* + * The documentation says the range apparently has to be + * restored to its initial state before it is deallocated. + */ + ::__sanitizer_annotate_contiguous_container( + p_ + contentOffset_, p_ + alloc_, + p_ + contentOffset_ + state_.load(std::memory_order_relaxed).fill, p_ + alloc_ + ); +#endif + + ::operator delete(p_, alloc_); +} + +/** + * \fn MetadataList::size() const + * \brief Retrieve the number of controls + * \context This function is \threadsafe. + * \note If the list is being modified, the return value may be out of + * date by the time the function returns + */ + +/** + * \fn MetadataList::empty() const + * \brief Check if empty + * \context This function is \threadsafe. + * \note If the list is being modified, the return value may be out of + * date by the time the function returns + */ + +/** + * \internal + * \brief Remove all items from the list + * \note This function in effect resets the list to its original state. As a consequence it invalidates - among others - + * all iterators, Checkpoint, and Diff objects that are associated with the list. No readers must exist + * when this function is called. + */ +void MetadataList::clear() +{ + for (auto &e : entries()) + e.headerOffset.store(Entry::kInvalidOffset, std::memory_order_relaxed); + + [[maybe_unused]] State s = state_.exchange({}, std::memory_order_relaxed); + +#if HAS_ASAN + ::__sanitizer_annotate_contiguous_container( + p_ + contentOffset_, p_ + alloc_, + p_ + contentOffset_ + s.fill, p_ + contentOffset_ + ); +#endif +} + + +/** + * \fn MetadataList::begin() const + * \brief Retrieve begin iterator + * \context This function is \threadsafe. + */ + +/** + * \fn MetadataList::end() const + * \brief Retrieve end iterator + * \context This function is \threadsafe. + */ + +/** + * \fn MetadataList::get(const Control &ctrl) const + * \brief Get the value of control \a ctrl + * \return A std::optional containing the control value, or std::nullopt if + * the control \a ctrl is not present in the list + * \context This function is \threadsafe. + */ + +/** + * \fn MetadataList::get(uint32_t tag) const + * \brief Get the value of pertaining to the numeric identifier \a tag + * \return A std::optional containing the control value, or std::nullopt if + * the control is not present in the list + * \context This function is \threadsafe. + */ + +/** + * \internal + * \fn MetadataList::set(const Control &ctrl, const internal::cxx20::type_identity_t &value) + * \brief Set the value of control \a ctrl to \a value + */ + +/** + * \internal + * \fn MetadataList::set(uint32_t tag, ControlValueView v) + * \brief Set the value of pertaining to the numeric identifier \a tag to \a v + */ + +/** + * \internal + * \brief Add items from \a other + * + * If any of them items cannot be added, then an empty optional is returned, + * and this function has no effects. + */ +std::optional MetadataList::merge(const ControlList &other) +{ + // \todo check id map of `other`? + + /* Copy the data and update a temporary state (`newState`) */ + + const auto oldState = state_.load(std::memory_order_relaxed); + auto newState = oldState; + const auto entries = this->entries(); + + for (const auto &[tag, value] : other) { + auto *e = find(tag); + if (!e) + return {}; + + auto [ err, header ] = set(*e, value, newState); + if (err != SetError()) + return {}; + + /* HACK: temporarily use the `tag` member to store the entry index */ + header->tag = e - entries.data(); + } + + /* + * At this point the data is already in place and every item has been validated + * to have a known id, appropriate size and type, etc., but they are not visible + * in any way. The next step is to make them visible by updating `headerOffset` + * in each affected `Entry` and `state_` in `*this`. + */ + + iterator it(p_ + contentOffset_ + oldState.fill); + const iterator end(p_ + contentOffset_ + newState.fill); + + for (; it != end; ++it) { + auto &header = const_cast(it.header()); + auto &e = entries[header.tag]; /* HACK: header.tag is temporarily the Entry index */ + + header.tag = e.tag; /* HACK: restore */ + + e.headerOffset.store( + reinterpret_cast(&header) - p_, + std::memory_order_release + ); + } + + state_.store(newState, std::memory_order_release); + + return {{ *this, newState.count - oldState.count, oldState.fill, newState.fill }}; +} + +/** + * \internal + * \enum MetadataList::SetError + * \brief Error code returned by a set operation + * + * \var MetadataList::SetError::UnknownTag + * \brief The tag is not supported by the metadata list + * \var MetadataList::SetError::AlreadySet + * \brief A value has already been added with the given tag + * \var MetadataList::SetError::SizeMismatch + * \brief The size of the data is not appropriate for the given tag + * \var MetadataList::SetError::TypeMismatch + * \brief The type of the value does not match the expected type + */ + +/** + * \internal + * \fn MetadataList::checkpoint() const + * \brief Create a checkpoint + * \context This function is \threadsafe. + */ + +MetadataList::SetError MetadataList::set(Entry &e, ControlValueView v) +{ + auto s = state_.load(std::memory_order_relaxed); + + auto [ err, header ] = set(e, v, s); + if (err != SetError()) + return err; + + e.headerOffset.store( + reinterpret_cast(header) - p_, + std::memory_order_release + ); + state_.store(s, std::memory_order_release); + + return {}; +} + +std::pair +MetadataList::set(const Entry &e, ControlValueView v, State &s) +{ + if (e.hasValue()) + return { SetError::AlreadySet, {} }; + if (e.type != v.type() || e.isArray != v.isArray()) + return { SetError::TypeMismatch, {} }; + + const auto src = v.data(); + if (e.isArray) { + if (src.size_bytes() > e.capacity) + return { SetError::SizeMismatch, {} }; + } else { + if (src.size_bytes() != e.capacity) + return { SetError::SizeMismatch, {} }; + } + + std::byte *oldEnd = p_ + contentOffset_ + s.fill; + std::byte *p = oldEnd; + + auto *headerPtr = internal::align::up(p); + auto *dataPtr = internal::align::up(src.size_bytes(), e.alignment, p); + internal::align::up(0, alignof(ValueHeader), p); + +#if HAS_ASAN + ::__sanitizer_annotate_contiguous_container( + p_ + contentOffset_, p_ + alloc_, + oldEnd, p + ); +#endif + + auto *header = new (headerPtr) ValueHeader{ + .tag = e.tag, + .size = uint32_t(src.size_bytes()), + .alignment = e.alignment, + .type = v.type(), + .isArray = v.isArray(), + .numElements = uint32_t(v.numElements()), + }; + std::memcpy(dataPtr, src.data(), src.size_bytes()); + + s.fill += p - oldEnd; + s.count += 1; + + return { {}, header }; +} + +/** + * \class MetadataList::iterator + * \brief Iterator + */ + +/** + * \typedef MetadataList::iterator::difference_type + * \brief iterator's difference type + */ + +/** + * \typedef MetadataList::iterator::value_type + * \brief iterator's value type + */ + +/** + * \typedef MetadataList::iterator::pointer + * \brief iterator's pointer type + */ + +/** + * \typedef MetadataList::iterator::reference + * \brief iterator's reference type + */ + +/** + * \typedef MetadataList::iterator::iterator_category + * \brief iterator's category + */ + +/** + * \fn MetadataList::iterator::operator*() + * \brief Retrieve value at iterator + * \return A \a ControlListView representing the value + */ + +/** + * \fn MetadataList::iterator::operator==(const iterator &other) const + * \brief Check if two iterators are equal + */ + +/** + * \fn MetadataList::iterator::operator!=(const iterator &other) const + * \brief Check if two iterators are not equal + */ + +/** + * \fn MetadataList::iterator::operator++(int) + * \brief Advance the iterator + */ + +/** + * \fn MetadataList::iterator::operator++() + * \brief Advance the iterator + */ + +/** + * \class MetadataList::Diff + * \brief Designates a series of consecutively added metadata items + * + * A Diff object provides a partial view into a MetadataList, it designates + * a series of consecutively added metadata items. Its main purposes is to + * enable applications to receive a list of changes made to a MetadataList. + * + * \sa Camera::metadataAvailable + * \internal + * \sa MetadataList::Checkpoint::diffSince() + */ + +/** + * \fn MetadataList::Diff::list() const + * \brief Retrieve the associated MetadataList + */ + +/** + * \fn MetadataList::Diff::size() const + * \brief Retrieve the number of metadata items designated + */ + +/** + * \fn MetadataList::Diff::empty() const + * \brief Check if any metadata items are designated + */ + +/** + * \fn MetadataList::Diff::operator bool() const + * \copydoc MetadataList::Diff::empty() const + */ + +/** + * \fn MetadataList::Diff::get(const Control &ctrl) const + * \copydoc MetadataList::get(const Control &ctrl) const + * \note The lookup will fail if the metadata item is not designated by this Diff object, + * even if it is otherwise present in the backing MetadataList. + */ + +/** + * \fn MetadataList::Diff::get(uint32_t tag) const + * \copydoc MetadataList::get(uint32_t tag) const + * \note The lookup will fail if the metadata item is not designated by this Diff object, + * even if it is otherwise present in the backing MetadataList. + */ + +/** + * \fn MetadataList::Diff::begin() const + * \brief Retrieve the begin iterator + */ + +/** + * \fn MetadataList::Diff::end() const + * \brief Retrieve the end iterator + */ + +/** + * \internal + * \class MetadataList::Checkpoint + * \brief Designates a particular state of a MetadataList + * + * A Checkpoint object designates a point in the stream of metadata items in the associated + * MetadataList. Its main use to be able to retrieve the set of metadata items that were + * added to the list after the designated point using diffSince(). + */ + +/** + * \internal + * \fn MetadataList::Checkpoint::diffSince() const + * \brief Retrieve the set of metadata items added since the checkpoint was created + */ + +} /* namespace libcamera */ diff --git a/test/controls/meson.build b/test/controls/meson.build index 763f8905e..ff635454b 100644 --- a/test/controls/meson.build +++ b/test/controls/meson.build @@ -5,12 +5,22 @@ control_tests = [ {'name': 'control_info_map', 'sources': ['control_info_map.cpp']}, {'name': 'control_list', 'sources': ['control_list.cpp']}, {'name': 'control_value', 'sources': ['control_value.cpp']}, + {'name': 'metadata_list', 'sources': ['metadata_list.cpp']}, ] +if asan_enabled + control_tests += { + 'name': 'metadata_list_iter_uaf', + 'sources': ['metadata_list_iter_uaf.cpp'], + 'should_fail': true, + } +endif + foreach test : control_tests exe = executable(test['name'], test['sources'], dependencies : libcamera_public, link_with : test_libraries, include_directories : test_includes_internal) - test(test['name'], exe, suite : 'controls', is_parallel : false) + test(test['name'], exe, suite : 'controls', is_parallel : false, + should_fail : test.get('should_fail', false)) endforeach diff --git a/test/controls/metadata_list.cpp b/test/controls/metadata_list.cpp new file mode 100644 index 000000000..b0eddde43 --- /dev/null +++ b/test/controls/metadata_list.cpp @@ -0,0 +1,205 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2025, Ideas On Board Oy + * + * MetadataList tests + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "test.h" + +using namespace std; +using namespace libcamera; + +#define ASSERT(x) do { \ + if (!static_cast(x)) { \ + std::cerr << '`' << #x << "` failed" << std::endl; \ + return TestFail; \ + } \ +} while (false) + +class MetadataListTest : public Test +{ +public: + MetadataListTest() = default; + +protected: + int run() override + { + MetadataListPlan mlp; + mlp.set(controls::ExposureTime); + mlp.set(controls::ExposureValue); + mlp.set(controls::ColourGains); + mlp.set(controls::AfWindows, 10); + mlp.set(controls::AeEnable); + mlp.set(controls::SensorTimestamp); + + MetadataList ml(mlp); + + /* + *`properties::Location` has the same numeric id as `controls::AeEnable` (checked by the `static_assert` + * below), but they have different types; check that this is detected. + */ + static_assert(static_cast(properties::LOCATION) == controls::AE_ENABLE); + ASSERT(ml.set(properties::Location, 0xCDCD) == MetadataList::SetError::TypeMismatch); + + ASSERT(ml.set(controls::AfWindows, std::array{}) == MetadataList::SetError::SizeMismatch); + ASSERT(ml.set(controls::ColourTemperature, 123) == MetadataList::SetError::UnknownTag); + + auto f1 = std::async(std::launch::async, [&] { + using namespace std::chrono_literals; + + std::this_thread::sleep_for(500ms); + ASSERT(ml.set(controls::ExposureTime, 0x1111) == MetadataList::SetError()); + + std::this_thread::sleep_for(500ms); + ASSERT(ml.set(controls::ExposureValue, 1) == MetadataList::SetError()); + + std::this_thread::sleep_for(500ms); + ASSERT(ml.set(controls::ColourGains, std::array{ + 123.f, + 456.f + }) == MetadataList::SetError()); + + std::this_thread::sleep_for(500ms); + ASSERT(ml.set(controls::AfWindows, std::array{ + Rectangle(), + Rectangle(1, 2, 3, 4), + Rectangle(0x1111, 0x2222, 0x3333, 0x4444), + }) == MetadataList::SetError()); + + return TestPass; + }); + + auto f2 = std::async(std::launch::async, [&] { + for (;;) { + const auto x = ml.get(controls::ExposureTime); + const auto y = ml.get(controls::ExposureValue); + const auto z = ml.get(controls::ColourGains); + const auto w = ml.get(controls::AfWindows); + + if (x) + ASSERT(*x == 0x1111); + + if (y) + ASSERT(*y == 1.0f); + + if (z) { + ASSERT(z->size() == 2); + ASSERT((*z)[0] == 123.f); + ASSERT((*z)[1] == 456.f); + } + + if (w) { + ASSERT(w->size() == 3); + ASSERT((*w)[0].isNull()); + ASSERT((*w)[1] == Rectangle(1, 2, 3, 4)); + ASSERT((*w)[2] == Rectangle(0x1111, 0x2222, 0x3333, 0x4444)); + } + + if (x && y && z && w) + break; + } + + return TestPass; + }); + + ASSERT(f1.get() == TestPass); + ASSERT(f2.get() == TestPass); + + ASSERT(ml.set(controls::ExposureTime, 0x2222) == MetadataList::SetError::AlreadySet); + ASSERT(ml.set(controls::ExposureValue, 2) == MetadataList::SetError::AlreadySet); + + ASSERT(ml.get(controls::ExposureTime) == 0x1111); + ASSERT(ml.get(controls::ExposureValue) == 1); + + for (auto &&[tag, v] : ml) + std::cout << "[" << tag << "] -> " << v << '\n'; + + std::cout << std::endl; + + /* Test MetadataList::Diff */ + { + ml.clear(); + ASSERT(ml.empty()); + ASSERT(ml.size() == 0); + + ASSERT(ml.set(controls::ExposureTime, 0x2222) == MetadataList::SetError()); + ASSERT(ml.get(controls::ExposureTime) == 0x2222); + + auto c = ml.checkpoint(); + + ASSERT(ml.set(controls::ExposureValue, 2) == MetadataList::SetError()); + ASSERT(ml.set(controls::SensorTimestamp, 0x99999999) == MetadataList::SetError()); + + auto d = c.diffSince(); + ASSERT(&d.list() == &ml); + + ASSERT(ml.set(controls::ColourGains, std::array{ 1.f, 2.f }) == MetadataList::SetError()); + + ASSERT(d); + ASSERT(!d.empty()); + ASSERT(d.size() == 2); + ASSERT(!d.get(controls::ExposureTime)); + ASSERT(!d.get(controls::ColourGains)); + ASSERT(!d.get(controls::AfWindows)); + ASSERT(d.get(controls::ExposureValue) == 2); + ASSERT(d.get(controls::SensorTimestamp) == 0x99999999); + + for (auto &&[tag, v] : d) + std::cout << "[" << tag << "] -> " << v << '\n'; + + /* Test if iterators work with algorithms. */ + std::ignore = std::find_if(d.begin(), d.end(), [](const auto &) { + return false; + }); + } + + /* Test transactional behaviour of MetadataList::merge() */ + { + ml.clear(); + ASSERT(ml.empty()); + ASSERT(ml.size() == 0); + + { + ControlList cl; + cl.set(controls::ExposureTime, 0xFEFE); + cl.set(controls::ColourGains, std::array{ 1.1f, 2.2f }); + + auto d = ml.merge(cl); + ASSERT(d); + ASSERT(d->size() == cl.size()); + ASSERT(d->get(controls::ExposureTime) == 0xFEFE); + ASSERT(ml.size() == d->size()); + } + + ASSERT(ml.get(controls::ExposureTime) == 0xFEFE); + + { + ControlList cl; + cl.set(999, 999); /* not part of plan */ + cl.set(controls::ExposureTime, 0xEFEF); /* already set */ + cl.set(properties::Location, 0xCDCD); /* type mismatch */ + cl.set(controls::SensorTimestamp, 0xABAB); /* ok */ + + auto c = ml.checkpoint(); + auto oldSize = ml.size(); + ASSERT(!ml.merge(cl)); + ASSERT(c.diffSince().empty()); + ASSERT(ml.size() == oldSize); + } + } + + return TestPass; + } +}; + +TEST_REGISTER(MetadataListTest) diff --git a/test/controls/metadata_list_iter_uaf.cpp b/test/controls/metadata_list_iter_uaf.cpp new file mode 100644 index 000000000..66b31136e --- /dev/null +++ b/test/controls/metadata_list_iter_uaf.cpp @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2025, Ideas On Board Oy + * + * MetadataList tests + */ + +#include +#include +#include +#include + +#include "test.h" + +using namespace std; +using namespace libcamera; + +class MetadataListIterUAFTest : public Test +{ +public: + MetadataListIterUAFTest() = default; + +protected: + int run() override + { + MetadataListPlan mlp; + mlp.set(controls::AeEnable); + + MetadataList ml(mlp); + std::ignore = *ml.begin(); /* Trigger ASAN. */ + + return TestPass; + } +}; + +TEST_REGISTER(MetadataListIterUAFTest) From patchwork Tue Jan 6 16:57:40 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25638 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 BDF42BDCBF for ; Tue, 6 Jan 2026 16:58:15 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4E18561FCE; Tue, 6 Jan 2026 17:58:07 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="oAc2GE9X"; 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 19EDC61FC1 for ; Tue, 6 Jan 2026 17:58:00 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 0E88B1783; Tue, 6 Jan 2026 17:57:39 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1767718659; bh=VUn4HsxeQxhwjHKZEiVuk/d+exiiPnac9+CCNibKqtw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=oAc2GE9X3SOSZxEazpeC33P2EQfQPG62canaVoJ/askWPj2EMm2R6PRikAP9y6dYp il+fcWtBvFqyZue7+dn3VOlbkgU8zVnDJvgMDOOod35JSQ1FQUqkdA/LTQMEuT1KxY U7WmablFPhZyCUXjHFq5OigyAMo7LzYwTDkkLP+0= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , Kieran Bingham Subject: [PATCH v4 08/22] Documentation: design: Document `MetadataList` Date: Tue, 6 Jan 2026 17:57:40 +0100 Message-ID: <20260106165754.1759831-9-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> References: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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 document describing the problem, the choices, and the design of the separate metadata list type. Signed-off-by: Barnabás Pőcze Reviewed-by: Paul Elder Reviewed-by: Kieran Bingham --- changes in v3: * use ``text`` for preformatted text * use doxy-int to add references * adjust some parts changes in v2: * rewrite "Thread safety" section --- Documentation/design/metadata-list.rst | 264 +++++++++++++++++++++++++ Documentation/index.rst | 3 +- Documentation/meson.build | 1 + include/libcamera/controls.h | 3 - src/libcamera/controls.cpp | 10 + src/libcamera/metadata_list.cpp | 1 - 6 files changed, 276 insertions(+), 6 deletions(-) create mode 100644 Documentation/design/metadata-list.rst diff --git a/Documentation/design/metadata-list.rst b/Documentation/design/metadata-list.rst new file mode 100644 index 000000000..f535db008 --- /dev/null +++ b/Documentation/design/metadata-list.rst @@ -0,0 +1,264 @@ +.. SPDX-License-Identifier: CC-BY-SA-4.0 + +Design of the metadata list +=========================== + +This document explains the design and rationale of the :doxy-int:`MetadataList` type. + +Description of the problem +-------------------------- + +Early metadata +^^^^^^^^^^^^^^ + +A pipeline handler might report numerous metadata items to the application about +a single request. It is likely that different metadata items become available at +different points in time while a request is being processed. + +Simultaneously, an application might desire to carry out potentially non-trivial +extra processing on the image, etc. using certain metadata items. For such an +application it is likely best if the value of each metadata item is reported as +soon as possible, thus allowing it to start processing as soon as possible. + +For this reason, libcamera provides the ``Camera::metadataAvailable`` signal. +This signal is dispatched whenever new metadata items become available for a +queued request. This mechanism is completely optional, only interested applications +need to subscribe, others are free to ignore it completely. :doxy-int:`Request::metadata` +will contain the sum of all early metadata items at request completion. + +Thread safety +^^^^^^^^^^^^^ + +The application and libcamera are operating in separate threads. This means that while +a request is being processed, accessing the request's metadata list brings up the +question of concurrent access. Previously, the metadata list was implemented using a +:doxy-int:`ControlList`, which uses ``std::unordered_map`` as its backing storage. That type +does not provide strong thread-safety guarantees. As a consequence, accessing the +metadata list was only allowed in certain contexts: + +1. before request submission +2. after request completion +3. in libcamera signal handler + +Contexts (1) and (2) are most likely assumed (and expected) by users of libcamera, and +they are not too interesting because they do not overlap with request processing, where a +pipeline handler could be modifying the list in parallel. + +Context (3) is merely an implementation detail of the libcamera signal-slot event handling +mechanism (libcamera users cannot use the asynchronous event delivery mechanism). + +Naturally, in a context where accessing the metadata list is safe, querying the metadata +items of interest and storing them in an application specific manner is a good and safe +approach. However, in (3) keeping the libcamera internal thread blocked for too long +will have detrimental effects, so processing must be kept to a minimum. + +As a consequence, if an application is unable to query the metadata items of interest +in a safe context (potentially because it does not know) but wants delegate work (that +needs access to metadata) to separate worker threads, it is forced to create a copy of +the entire metadata list, which is hardly optimal. The introduction of early metadata +completion only makes it worse (due to potentially multiple completion events). + +Requirements +------------ + +We wish to provide a simple, easy-to-use, and hard-to-misuse interface for +applications. Notably, applications should be able to perform early metadata +processing safely wrt. any concurrent modifications libcamera might perform. + +Secondly, efficiency should be considered: copies, locks, reference counting, +etc. should be avoided if possible. + +Preferably, it should be possible to refer to a contiguous (in insertion order) +subset of values reasonably efficiently so that applications can be notified +about the just inserted metadata items without creating separate data structures +(i.e. a set of numeric ids). + +Options +------- + +Several options have been considered for making use of already existing mechanisms, +to avoid the introduction of a new type. These all fell short of some or all of the +requirements proposed above. Some ideas that use the existing ``ControlList`` type +are discussed below. + +Send a copy +^^^^^^^^^^^ + +Passing a separate ``ControlList`` containing the just completed metadata, and +disallowing access to the request's metadata list until completion works fine, and +avoids the synchronization issues on the libcamera side. Nonetheless, it has two +significant drawbacks: + +1. It moves the issue of synchronization from libcamera to the application: the + application still has to access its own data in a thread-safe manner and/or + transfer the partial metadata list to its intended thread of execution. +2. The metadata list may contain potentially large data, copying which may be + a non-negligible performance cost (especially if it does not even end up needed). + +Keep using ``ControlList`` +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Using a ``ControlList`` (and hence ``std::unordered_map``) with early metadata completion +would be possible, but it would place a number of potentially non-intuitive and +easy to violate restrictions on applications, making it harder to use safely. +Specifically, the application would have to retrieve a pointer to the :doxy-int:`ControlValue` +object in the metadata ``ControlList``, and then access it only through that pointer. +(This is guaranteed to work since ``std::unordered_map`` provides pointer stability +wrt. insertions.) + +However, it wouldn't be able to do lookups on the metadata list outside the event +handler, thus a pointer or iterator to every potentially accessed metadata item +has to be retrieved and saved in the event handler. Additionally, the usual way +of retrieving metadata using the pre-defined ``Control`` objects would no longer +be possible, losing type-safety. (Although the ``ControlValue`` type could be extended +to support that.) + +Design +------ + +A separate data structure is introduced to contain the metadata items pertaining +to a given request. It is referred to as "metadata list" from now on. + +A metadata list is backed by a pre-allocated (at construction time) contiguous +block of memory sized appropriately to contain all possible metadata items. This +means that the number and size of metadata items that a camera can report must +be known in advance. The newly introduced ``MetadataListPlan`` type is used for +that purpose. At the time of writing this does not appear to be a significant +limitation since most metadata has a fixed size, and each pipeline handler (and +IPA) has a fixed set of metadata that it can report. There are, however, metadata +items that have a variably-sized array type. In those cases an upper bound on the +number of elements must be provided. + +``MetadataListPlan`` +^^^^^^^^^^^^^^^^^^^^ + +A :doxy-int:`MetadataListPlan` collects the set of possible metadata items. It maps the +numeric id of the control to a collection of static information (size, etc.). This +is most importantly used to calculate the size required to store all possible +metadata items. + +Each camera has its own ``MetadataListPlan`` object similarly to its ``ControlInfoMap``. +It is used to create requests for the camera with an appropriately sized ``MetadataList``. +Pipeline handlers should fill it during camera initialization or configuration, +and they are allowed to modify it before and during camera configuration. + +``MetadataList`` +^^^^^^^^^^^^^^^^ + +The current metadata list implementation is a single-writer multiple-readers +thread-safe data structure that provides lock-free lookup and access for any number +of threads, while allowing a single thread at a time to add metadata items. + +The implemented metadata list has two main parts. The first part essentially +contains a copy of the ``MetadataListPlan`` used to construct the ``MetadataList``. In +addition to the static information about the metadata item, it contains dynamic +information such as whether the metadata item has been added to the list or not. +These entries are sorted by the numeric identifier to facilitate faster lookup. + +The second part of a metadata list is a completely self-contained serialized list +of metadata items. The number of bytes used for actually storing metadata items +in this second part will be referred to as the "fill level" from now on. The +self-contained nature of the second part leads to a certain level of data duplication +between the two parts, however, the end goal is to have a serialized version of +``ControlList`` with the same serialized format. This would allow a ``MetadataList`` +to be "trivially" reinterpreted as a control list at any point of its lifetime, +simplifying the interoperability between the two. +TODO: do we really want that? + +A metadata list, at construction time, calculates the number of bytes necessary to +store all possible metadata items according to the supplied ``MetadataListPlan``. +Storage, for all possible metadata items and the necessary auxiliary structures, +is then allocated. This allocation remains fixed for the entire lifetime of a +``MetadataList``, which is crucial to satisfy the earlier requirements. + +Each metadata item can only be added to a metadata list once. This constraint +does not pose a significant limitation, instead, it simplifies the interface and +implementation; it is essentially an append-only list. + +Serialization +''''''''''''' + +The actual values are encoded in the "second part" of the metadata list in a fairly +simple fashion. Each control value is encoded as header + data bytes + padding. +Each value has a header, which contains information such as the size, alignment, +type, etc. of the value. The data bytes are aligned to the alignment specified +in the header, and padding may be inserted after the last data byte to guarantee +proper alignment for the next header. Padding is present even after the last entry. + +The minimum amount of state needed to describe such a serialized list of values is +merely the number of bytes used. This can reasonably be limited to 4 GiB, meaning +that a 32-bit unsigned integer is sufficient to store the fill level. This makes +it possible to easily update the state in a wait-free fashion. + +Lookup +'''''' + +Lookup in a metadata list is done using the metadata entries in the "first part". +These entries are sorted by their numeric identifiers, hence binary search is +used to find the appropriate entry. Then, it is checked whether the given control +id has already been added, and if it has, then its data can be returned in a +:doxy-int:`ControlValueView` object. + +Insertion +''''''''' + +Similarly to lookup, insertion also starts with binary searching the entry +corresponding to the given numeric identifier. If an entry is present for the +given id and no value has already been stored with that id, then insertion can +proceed. The value is appended to the serialized list of control values according +to the format described earlier. Then the fill level is atomically incremented, +and the entry is marked as set. After that the new value is available for readers +to consume. + +Having a single writer is an essential requirement to be able to carry out insertion +in a reasonably efficient, and thread-safe manner. + +Iteration +''''''''' + +Iteration of a ``MetadataList`` is carried out only using the serialized list of +controls in the "second part" of the data structure. An iterator can be implemented +as a single pointer, pointing to the header of the current entry. The begin iterator +simply points to location of the header of the first value. The end iterator is +simply the end of the serialized list of values, which can be calculated from the +begin iterator and the fill level of the serialized list. + +The above iterator can model a `C++ forward iterator`_, that is, only increments +of 1 are possible in constant time, and going backwards is not possible. Advancing +to the next value can be simply implemented by reading the size and alignment from +the header, and adjusting the iterator's pointer by the necessary amount. + +.. _C++ forward iterator: https://en.cppreference.com/w/cpp/iterator/forward_iterator.html + +Clearing +'''''''' + +Removing a single value is not supported, but clearing the entire metadata list +is. This should only be done when there are no readers, otherwise readers might +run into data races if they keep reading the metadata when new entries are being +added after clearing it. + +Clearing is implemented by resetting each metadata entry in the "first part", as +well as resetting the stored fill level of the serialized buffer to 0. + +Partial view +'''''''''''' + +When multiple metadata items are completed early, it is important to provide a way +for the application to know exactly which metadata items have just been added. The +serialized values in the data structure are laid out sequentially. This makes it +possible for a simple byte range to denote a range of metadata items. Hence the +completed metadata items can be transferred to the application as a simple byte +range, without needing extra data structures (such as a set of numeric ids). + +The :doxy-int:`MetadataList::Checkpoint` type is used to store the state of the +serialized list (number of bytes and number of items) at a given point in time. +From such a checkpoint object a :doxy-int:`MetadataList::Diff` object can be +constructed, potentially at a later time, after some items have been added. Both +types represent are view-like non-owning references into the backing ``MetadataList``, +and are invalidated when that is destroyed or cleared. A ``MetadataList::Diff`` +represents all values added since the checkpoint. This *diff* object is reasonably small, +trivially copyable, making it easy to provide to the application; it provides thread-safe +access to the data. It has much of the same features as a ``MetadataList``, e.g. it can +be iterated and one can do lookups. Naturally, both iteration and lookups only consider +the values added after the checkpoint and before the creation of the ``MetadataList::Diff`` object. diff --git a/Documentation/index.rst b/Documentation/index.rst index 8109b4295..6680ff523 100644 --- a/Documentation/index.rst +++ b/Documentation/index.rst @@ -28,6 +28,7 @@ SoftwareISP Benchmarking Tracing guide Design document: AE + Design document: Metadata list Internal API .. toctree:: @@ -36,5 +37,3 @@ Lens driver requirements Sensor driver requirements - - diff --git a/Documentation/meson.build b/Documentation/meson.build index 51899c19c..96a88c22d 100644 --- a/Documentation/meson.build +++ b/Documentation/meson.build @@ -156,6 +156,7 @@ if sphinx.found() sphinx_conf, 'contributing.rst', 'design/ae.rst', + 'design/metadata-list.rst', 'feature_requirements.rst', 'guides/application-developer.rst', 'guides/ipa.rst', diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h index 5b4ab4aa7..16a57c54f 100644 --- a/include/libcamera/controls.h +++ b/include/libcamera/controls.h @@ -265,8 +265,6 @@ public: { } -#ifndef __DOXYGEN__ - // TODO: should have restricted access? ControlValueView(ControlType type, bool isArray, std::size_t numElements, const std::byte *data) noexcept : type_(type), isArray_(isArray), numElements_(numElements), @@ -274,7 +272,6 @@ public: { assert(isArray || numElements == 1); } -#endif [[nodiscard]] explicit operator bool() const { return type_ != ControlTypeNone; } [[nodiscard]] ControlType type() const { return type_; } diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp index 15e6b58ef..bddbac10c 100644 --- a/src/libcamera/controls.cpp +++ b/src/libcamera/controls.cpp @@ -349,6 +349,16 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen * \sa ControlValue::ControlValue() */ +/** + * \internal + * \fn ControlValueView::ControlValueView(ControlType type, bool isArray, + * std::size_t numElements, const std::byte *data) + * \brief Construct a view referring to \a data + * + * The constructed view will refer to the value stored in \a data, and thus + * \a data must not be modified or destroyed before the view is destroyed. + */ + /** * \fn ControlValueView::operator bool() const * \brief Determine if the referenced ControlValue is valid diff --git a/src/libcamera/metadata_list.cpp b/src/libcamera/metadata_list.cpp index 5a5114fc7..5b0a47cb4 100644 --- a/src/libcamera/metadata_list.cpp +++ b/src/libcamera/metadata_list.cpp @@ -526,7 +526,6 @@ MetadataList::set(const Entry &e, ControlValueView v, State &s) * a series of consecutively added metadata items. Its main purposes is to * enable applications to receive a list of changes made to a MetadataList. * - * \sa Camera::metadataAvailable * \internal * \sa MetadataList::Checkpoint::diffSince() */ From patchwork Tue Jan 6 16:57:41 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25639 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 8654DC3213 for ; Tue, 6 Jan 2026 16:58:16 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A1DE261FD9; Tue, 6 Jan 2026 17:58:08 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="rvw5OK71"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6BC6E61FCF for ; Tue, 6 Jan 2026 17:58:00 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 53BFC177D; Tue, 6 Jan 2026 17:57:39 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1767718659; bh=Z+Ag+mW3dBpEbw4rKOV9YdD8pbs3p1Fe6+MbJX0XVGQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=rvw5OK71I7zvqHBHau4KM6wN0ye3o2SXZm+3MeOp6BXpYtWS3Ah4eUe5gRiZb7DSK +82vKFVNVwXltF5zRaUf8OYDgKHDz+mVPWw0wxa46HRS6j5YLha5K0EzbwiR/cx3o+ YyayIVDcHnp7MNij24JibgCmTeY9qabh7qKUHvFo= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder Subject: [PATCH v4 09/22] libcamera: ipa_data_serializer: Support `MetadataListPlan` Date: Tue, 6 Jan 2026 17:57:41 +0100 Message-ID: <20260106165754.1759831-10-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> References: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" Define the type in `core.mojom` with external (de)serialization, and add the necessary `IPADataSerializer` template specialization. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi Reviewed-by: Paul Elder --- changes in v3: * return empty and log message in case of error instead of aborting --- include/libcamera/ipa/core.mojom | 1 + src/libcamera/ipa_data_serializer.cpp | 94 +++++++++++++++++++ .../core_ipa_interface.h.tmpl | 1 + 3 files changed, 96 insertions(+) diff --git a/include/libcamera/ipa/core.mojom b/include/libcamera/ipa/core.mojom index bce797245..754e4065c 100644 --- a/include/libcamera/ipa/core.mojom +++ b/include/libcamera/ipa/core.mojom @@ -83,6 +83,7 @@ module libcamera; [skipSerdes, skipHeader] struct ControlInfoMap {}; [skipSerdes, skipHeader] struct ControlList {}; [skipSerdes, skipHeader] struct SharedFD {}; +[skipSerdes, skipHeader] struct MetadataListPlan {}; [skipHeader] struct Point { int32 x; diff --git a/src/libcamera/ipa_data_serializer.cpp b/src/libcamera/ipa_data_serializer.cpp index 0537f785b..5de99b58f 100644 --- a/src/libcamera/ipa_data_serializer.cpp +++ b/src/libcamera/ipa_data_serializer.cpp @@ -11,6 +11,8 @@ #include +#include + #include "libcamera/internal/byte_stream_buffer.h" /** @@ -620,6 +622,98 @@ IPADataSerializer::deserialize(const std::vector &d return deserialize(data.cbegin(), data.end(), fds.cbegin(), fds.end(), cs); } +template<> +std::tuple, std::vector> +IPADataSerializer::serialize(const MetadataListPlan &data, + [[maybe_unused]] ControlSerializer *cs) +{ + std::vector dataVec; + + appendPOD(dataVec, data.size()); + + for (const auto &[tag, e] : data) { + appendPOD(dataVec, tag); + appendPOD(dataVec, e.size); + appendPOD(dataVec, e.alignment); + appendPOD(dataVec, e.numElements); + appendPOD(dataVec, e.type); + appendPOD(dataVec, e.isArray); + } + + return { dataVec, {} }; +} + +template<> +MetadataListPlan +IPADataSerializer::deserialize(std::vector::const_iterator dataBegin, + std::vector::const_iterator dataEnd, + [[maybe_unused]] std::vector::const_iterator fdsBegin, + [[maybe_unused]] std::vector::const_iterator fdsEnd, + [[maybe_unused]] ControlSerializer *cs) +{ + MetadataListPlan ret; + std::size_t offset = 0; + + auto n = readPOD(dataBegin, 0, dataEnd); + offset += sizeof(n); + + while (n--) { + auto tag = readPOD(dataBegin, offset, dataEnd); + offset += sizeof(tag); + + auto size = readPOD(dataBegin, offset, dataEnd); + offset += sizeof(size); + + auto alignment = readPOD(dataBegin, offset, dataEnd); + offset += sizeof(alignment); + + auto numElements = readPOD(dataBegin, offset, dataEnd); + offset += sizeof(numElements); + + auto type = readPOD(dataBegin, offset, dataEnd); + offset += sizeof(type); + + auto isArray = readPOD(dataBegin, offset, dataEnd); + offset += sizeof(isArray); + + [[maybe_unused]] bool ok = ret.set(tag, + size, alignment, + numElements, static_cast(type), isArray); + if (!ok) { + LOG(IPADataSerializer, Error) << "Failed to deserialize MetadataListPlan"; + return {}; + } + } + + return ret; +} + +template<> +MetadataListPlan +IPADataSerializer::deserialize(std::vector::const_iterator dataBegin, + std::vector::const_iterator dataEnd, + ControlSerializer *cs) +{ + return deserialize(dataBegin, dataEnd, {}, {}, cs); +} + +template<> +MetadataListPlan +IPADataSerializer::deserialize(const std::vector &data, + ControlSerializer *cs) +{ + return deserialize(data.cbegin(), data.end(), cs); +} + +template<> +MetadataListPlan +IPADataSerializer::deserialize(const std::vector &data, + const std::vector &fds, + ControlSerializer *cs) +{ + return deserialize(data.cbegin(), data.end(), fds.cbegin(), fds.end(), cs); +} + #endif /* __DOXYGEN__ */ } /* namespace libcamera */ diff --git a/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl b/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl index 93f988cd9..d4468e176 100644 --- a/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl +++ b/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl @@ -23,6 +23,7 @@ #include #include #include +#include #include From patchwork Tue Jan 6 16:57:42 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25640 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 10F67C323E for ; Tue, 6 Jan 2026 16:58:17 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A2F7661FF6; Tue, 6 Jan 2026 17:58:09 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="rI6cbcMx"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9DA8D61FCE for ; Tue, 6 Jan 2026 17:58:00 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8DDD01784; Tue, 6 Jan 2026 17:57:39 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1767718659; bh=RhWs6D7ApT7n2cQkxiWg81Uqr385M4ceeJuLW2TNMSs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=rI6cbcMxqkPjWdMQhGbbt7Kj/4huVVIdjNYRvT4T6RVevWwE5c1tl/Zs8sPMeWEOV AzQBiPLDU/7yuIq7QsHWM3xHq2MaZG5wagYsYnpYc5CRK4tMuiA9V4NkEP2F4cVhtF PoLUwmDnUO0UCjmJNp1ZJcRycDbjGTjzRRVzFSLo= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder , Kieran Bingham Subject: [PATCH v4 10/22] libcamera: camera: Store `MetadataListPlan` in `Camera::Private` Date: Tue, 6 Jan 2026 17:57:42 +0100 Message-ID: <20260106165754.1759831-11-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> References: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" Just like `ControlInfoMap controlInfo_` is a public member of the private camera data for pipeline handlers to populate, add a `MetadataListPlan` as well for the pipeline handlers to fill. This will be used to initialize the `MetadataList` of each request created by the camera. Also add `Camera::metadata()`, which makes it accessible for applications. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi Reviewed-by: Paul Elder Reviewed-by: Kieran Bingham --- changes in v2: * make it available in `Camera::metadata()` --- include/libcamera/camera.h | 2 ++ include/libcamera/internal/camera.h | 2 ++ src/libcamera/camera.cpp | 21 +++++++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h index b24a29740..53089282a 100644 --- a/include/libcamera/camera.h +++ b/include/libcamera/camera.h @@ -31,6 +31,7 @@ class FrameBuffer; class FrameBufferAllocator; class PipelineHandler; class Request; +class MetadataListPlan; class SensorConfiguration { @@ -131,6 +132,7 @@ public: const ControlInfoMap &controls() const; const ControlList &properties() const; + const MetadataListPlan &metadata() const; const std::set &streams() const; diff --git a/include/libcamera/internal/camera.h b/include/libcamera/internal/camera.h index 8a2e9ed58..016c1ae51 100644 --- a/include/libcamera/internal/camera.h +++ b/include/libcamera/internal/camera.h @@ -18,6 +18,7 @@ #include #include +#include namespace libcamera { @@ -40,6 +41,7 @@ public: std::queue waitingRequests_; ControlInfoMap controlInfo_; ControlList properties_; + MetadataListPlan metadataPlan_; uint32_t requestSequence_; diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp index 7c0e93ff4..88990c3c0 100644 --- a/src/libcamera/camera.cpp +++ b/src/libcamera/camera.cpp @@ -658,6 +658,14 @@ Camera::Private::~Private() * when creating the camera, and shall not be modified afterwards. */ +/** + * \var Camera::Private::metadataPlan_ + * \brief The set of metadata supported by the camera + * + * The metadata information shall be initialised by the pipeline handler when + * creating the camera. + */ + /** * \var Camera::Private::requestSequence_ * \brief The queuing sequence number of the request @@ -1074,6 +1082,19 @@ const ControlInfoMap &Camera::controls() const return _d()->controlInfo_; } +/** + * \brief Retrieve the set of metadata supported by the camera + * + * The list of metadata controls that may be reported by the camera + * for a \ref Request::metadata() "request". + * + * \return A MetadataListPlan listing the metadata controls supported by the camera + */ +const MetadataListPlan &Camera::metadata() const +{ + return _d()->metadataPlan_; +} + /** * \brief Retrieve the list of properties of the camera * From patchwork Tue Jan 6 16:57:43 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25641 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 8EF59BDCBF for ; Tue, 6 Jan 2026 16:58:17 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6FC8361FEC; Tue, 6 Jan 2026 17:58:10 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="dTfR8L/j"; 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 DB2E461FCC for ; Tue, 6 Jan 2026 17:58:00 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id CB5F51924; Tue, 6 Jan 2026 17:57:39 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1767718659; bh=uo7flEGhas2heoe8Xah3bBOF4KdVGGGD9l326H5HN2Y=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=dTfR8L/jSBfNvrm+fKmWpS/OrmufO/XdZVZhj75RFC4ZT4JYMh8MhGlERjNShV6N6 HHair4H+2tzoAm+B370lL+jL0d6+X5Ly7EXZGc4fMPTBjNmNxYHkDgamyXJRqf9lpJ EG47RXgRk1gYjPYHaNQvV0hhgRJth9NE8LTBIgmQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder Subject: [PATCH v4 11/22] libcamera: request: Store `MetadataList` Date: Tue, 6 Jan 2026 17:57:43 +0100 Message-ID: <20260106165754.1759831-12-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> References: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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 `MetadataList` to the `Request` object to store the metadata items returned by the camera. The metadata list is initialized using the camera's `MetadataListPlan` object, which is supposed to be filled by the pipeline handlers. This new list is exposed as `metadata2`, and the old `ControlList` is still present for now. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi Reviewed-by: Paul Elder --- include/libcamera/internal/request.h | 5 +++++ include/libcamera/request.h | 5 +++++ src/libcamera/request.cpp | 11 ++++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/include/libcamera/internal/request.h b/include/libcamera/internal/request.h index 693097ee9..3e10ae051 100644 --- a/include/libcamera/internal/request.h +++ b/include/libcamera/internal/request.h @@ -16,6 +16,7 @@ #include #include +#include #include using namespace std::chrono_literals; @@ -37,6 +38,9 @@ public: bool hasPendingBuffers() const; ControlList &metadata() { return metadata_; } +#ifndef __DOXYGEN__ + [[nodiscard]] MetadataList &metadata2() { return metadata2_; } +#endif bool completeBuffer(FrameBuffer *buffer); void complete(); @@ -64,6 +68,7 @@ private: std::map notifiers_; std::unique_ptr timer_; ControlList metadata_; + MetadataList metadata2_; }; } /* namespace libcamera */ diff --git a/include/libcamera/request.h b/include/libcamera/request.h index 290983f61..96755a655 100644 --- a/include/libcamera/request.h +++ b/include/libcamera/request.h @@ -24,6 +24,7 @@ namespace libcamera { class Camera; class CameraControlValidator; class FrameBuffer; +class MetadataList; class Stream; class Request : public Extensible @@ -51,6 +52,10 @@ public: ControlList &controls() { return controls_; } const ControlList &metadata() const; +#ifndef __DOXYGEN__ + [[nodiscard]] const MetadataList &metadata2() const; +#endif + const BufferMap &buffers() const { return bufferMap_; } int addBuffer(const Stream *stream, FrameBuffer *buffer, std::unique_ptr &&fence = {}); diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp index 57f1f060d..6d44c6749 100644 --- a/src/libcamera/request.cpp +++ b/src/libcamera/request.cpp @@ -57,7 +57,8 @@ LOG_DEFINE_CATEGORY(Request) * \todo Add a validator for metadata controls. */ Request::Private::Private(Camera *camera) - : camera_(camera), cancelled_(false), metadata_(controls::controls) + : camera_(camera), cancelled_(false), metadata_(controls::controls), + metadata2_(camera->metadata()) { } @@ -405,6 +406,7 @@ void Request::reuse(ReuseFlag flags) controls_.clear(); _d()->metadata_.clear(); + _d()->metadata2_.clear(); } /** @@ -432,6 +434,13 @@ const ControlList &Request::metadata() const return _d()->metadata_; } +#ifndef __DOXYGEN__ +const MetadataList &Request::metadata2() const +{ + return _d()->metadata2_; +} +#endif + /** * \fn Request::buffers() * \brief Retrieve the request's streams to buffers map From patchwork Tue Jan 6 16:57:44 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25642 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 EFB2FC3274 for ; Tue, 6 Jan 2026 16:58:17 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3BD7561FF4; Tue, 6 Jan 2026 17:58:11 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Oz/wXSYp"; 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 10B2561FCB for ; Tue, 6 Jan 2026 17:58:01 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 10FC01AAE for ; Tue, 6 Jan 2026 17:57:40 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1767718660; bh=eNhjY4XCauCfra8zs98bRl2vo67YbUrQYZDIVRMuTAw=; h=From:To:Subject:Date:In-Reply-To:References:From; b=Oz/wXSYpQzbYiC078ypi4bg777f6tlmq2SJWZxi660IU8LFXA9ahwFScTACWPdv/M V7csOFsSR0jFpuBuB/pBDw3t/QP1M881lK22biLdK0LrL3qwTTpMjOdzdWoRe2l8mm nRSFkOv7gA1PTcPIrr6h8sIzUbJ8ZjlIHwU/urzg= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [PATCH v4 12/22] [DNI] apps: cam: Print `MetadataListPlan` of camera Date: Tue, 6 Jan 2026 17:57:44 +0100 Message-ID: <20260106165754.1759831-13-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> References: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" When `--list-controls` is specified, print the set of supported metadata. --- changes in v3: * print number of elements separately instead of NxM --- src/apps/cam/camera_session.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/apps/cam/camera_session.cpp b/src/apps/cam/camera_session.cpp index 17444a217..170d7b17b 100644 --- a/src/apps/cam/camera_session.cpp +++ b/src/apps/cam/camera_session.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include "../common/event_loop.h" @@ -228,6 +229,20 @@ void CameraSession::listControls() const std::cout << std::endl; } } + + for (const auto &[id, info] : camera_->metadata()) { + const auto *cid = controls::controls.at(id); + + std::cout << "Metadata: [ out] " << cid->vendor() << "::" << cid->name() + << " type:" << info.type + << " size:" << info.size + << " alignment:" << info.alignment; + + if (info.isArray) + std::cout << " count:" << info.numElements; + + std::cout << std::endl; + } } void CameraSession::listProperties() const From patchwork Tue Jan 6 16:57:45 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25643 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 626C3C3285 for ; Tue, 6 Jan 2026 16:58:18 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D6ADF61FF3; Tue, 6 Jan 2026 17:58:11 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="bKdmCCge"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3F09A61FBB for ; Tue, 6 Jan 2026 17:58:01 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3D5FD27FB for ; Tue, 6 Jan 2026 17:57:40 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1767718660; bh=AnoUQSw5nM4ssiM/8J9Y3ZvGEcI3IhB/fMo7RdEEgTk=; h=From:To:Subject:Date:In-Reply-To:References:From; b=bKdmCCgeTWkL5puS7FTDdShhY+YNnRPQWgPHFNWriMUl+XE4M3jtvrePIV41kyal/ W6cnx2m9/fIol2DvOgY7ygjvVHchKpHp8pFCzRSKtiYgQn7y5oPuSYVp8aFgk36Zeh aXAMLtYIRjhEdugBpoRRiAp5sQX/HbZ8QIVP5QwM= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [PATCH v4 13/22] [DNI] apps: cam: Print `MetadataList` of `Request` as well Date: Tue, 6 Jan 2026 17:57:45 +0100 Message-ID: <20260106165754.1759831-14-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> References: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" When the "--metadata" option is enabled, print the dedicated `MetadataList` of the request as well. --- src/apps/cam/camera_session.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/apps/cam/camera_session.cpp b/src/apps/cam/camera_session.cpp index 170d7b17b..610279c24 100644 --- a/src/apps/cam/camera_session.cpp +++ b/src/apps/cam/camera_session.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -536,11 +537,19 @@ void CameraSession::processRequest(Request *request) if (printMetadata_) { const ControlList &requestMetadata = request->metadata(); + std::cout << "Metadata (" << requestMetadata.size() << " entries):\n"; for (const auto &[key, value] : requestMetadata) { const ControlId *id = controls::controls.at(key); std::cout << "\t" << id->name() << " = " << value.toString() << std::endl; } + + const auto &requestMetadata2 = request->metadata2(); + std::cout << "Metadata2 (" << requestMetadata2.size() << " entries):\n"; + for (const auto &[key, value] : requestMetadata2) { + const ControlId *id = controls::controls.at(key); + std::cout << '\t' << id->name() << " = " << value << std::endl; + } } /* From patchwork Tue Jan 6 16:57:46 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25644 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 B30D1C3213 for ; Tue, 6 Jan 2026 16:58:18 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9464462001; Tue, 6 Jan 2026 17:58:13 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Yt0b3l+a"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7D89161FC0 for ; Tue, 6 Jan 2026 17:58:01 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 69990581; Tue, 6 Jan 2026 17:57:40 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1767718660; bh=MzC4wpVJD9DyEjqljKdnn3f8t8g71dRuRgLZqKSdLNI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Yt0b3l+a/z5qtSvBYjnvP+nMNaFp2+MQ11Cu6XEIzXCdKmHX+IbMNTL4A3+nHNB8e i7g4vXo27x5S8iWAt7SMbLr0RjPFMY0aSOSLc3cVChG4MtSwmZ2BFCL0PPBYAJ1Izu EduijfKg4bs3p8Za0Wvc/sdl/qp3ZqsIYCD9LBPs= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder , Kieran Bingham Subject: [PATCH v4 14/22] libcamera: camera: Introduce metadataAvailable signal Date: Tue, 6 Jan 2026 17:57:46 +0100 Message-ID: <20260106165754.1759831-15-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> References: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" From: Jacopo Mondi Add a new signal to the Camera class that allows applications to receive notifications for early completion of metadata results. To avoid expensive copies of the metadata results the signal transports a view object of metadata items that are ready. The signal is an opt-in feature for applications and the sum of all metadata results notified through this signal is available in Request::metadata() at request completion time. Signed-off-by: Jacopo Mondi [Use `MetadataList::Diff`, change documentation accordingly.] Signed-off-by: Barnabás Pőcze Reviewed-by: Paul Elder Reviewed-by: Kieran Bingham --- Original: https://patchwork.libcamera.org/patch/22227/ --- Documentation/design/metadata-list.rst | 2 +- include/libcamera/camera.h | 2 + src/libcamera/camera.cpp | 54 ++++++++++++++++++++++++++ src/libcamera/metadata_list.cpp | 1 + 4 files changed, 58 insertions(+), 1 deletion(-) diff --git a/Documentation/design/metadata-list.rst b/Documentation/design/metadata-list.rst index f535db008..299e30a4a 100644 --- a/Documentation/design/metadata-list.rst +++ b/Documentation/design/metadata-list.rst @@ -20,7 +20,7 @@ extra processing on the image, etc. using certain metadata items. For such an application it is likely best if the value of each metadata item is reported as soon as possible, thus allowing it to start processing as soon as possible. -For this reason, libcamera provides the ``Camera::metadataAvailable`` signal. +For this reason, libcamera provides the :doxy-int:`Camera::metadataAvailable` signal. This signal is dispatched whenever new metadata items become available for a queued request. This mechanism is completely optional, only interested applications need to subscribe, others are free to ignore it completely. :doxy-int:`Request::metadata` diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h index 53089282a..51aee7ee4 100644 --- a/include/libcamera/camera.h +++ b/include/libcamera/camera.h @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -123,6 +124,7 @@ public: const std::string &id() const; + Signal metadataAvailable; Signal bufferCompleted; Signal requestCompleted; Signal<> disconnected; diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp index 88990c3c0..6fb916400 100644 --- a/src/libcamera/camera.cpp +++ b/src/libcamera/camera.cpp @@ -911,6 +911,60 @@ const std::string &Camera::id() const return _d()->id_; } +/** + * \var Camera::metadataAvailable + * \brief Signal emitted when metadata for a request is available + * + * The metadataAvailable signal notifies applications about the availability + * of metadata for a request before the request completes. + * + * As metadata results could be large in size, the signal transports only a view + * object via which the newly completed metadata items can be accessed. Similarly + * to the metadata list itself, this object is thread-safe, and can be sent to other + * threads for deferred processing. The view object is valid until the request is + * destroyed or reused, whichever happens first. + * + * Applications can access the value of the newly available metadata results as follows: + * + * \code + + void metadataAvailableHandler(Request *request, MetadataList::Diff update) + { + // The object can be iterated... + for (auto &&[id, data] : update) { + // `id` is the numeric identifier + // `data` is a `ControlValueView` object + } + + // ...or individual items can be looked up. + if (auto x = update.get(controls::SensorTimestamp)) { + // `SensorTimestamp` will only be found if it is part of this + // particular update; metadata items completed earlier will + // not be found. + } + } + \endcode + * + * This signal is emitted multiple times for the same request, it is in fact + * emitted by libcamera every time a new set of metadata is made available + * by the Camera to the application. + * + * The sum of all metadata reported through this signal is equal to + * Request::metadata() list when the Request completes. + * + * Applications can opt-in to handle this signal to receive fast notifications + * of metadata availability or can equally access the full metadata list + * at Request completion time through Request::metadata() if they have no interest + * in early metadata notification. + * + * \note The received MetadataList::Diff object is merely a view, it is only valid until + * the associated Request is destroyed or \ref Request::reuse() "reused". However, + * during its valid lifetime, an application is free to create copies and access the + * completed metadata items from separate threads by iteration or MetadataList::Diff::get(). + * + * \sa ControlValueView + */ + /** * \var Camera::bufferCompleted * \brief Signal emitted when a buffer for a request queued to the camera has diff --git a/src/libcamera/metadata_list.cpp b/src/libcamera/metadata_list.cpp index 5b0a47cb4..5a5114fc7 100644 --- a/src/libcamera/metadata_list.cpp +++ b/src/libcamera/metadata_list.cpp @@ -526,6 +526,7 @@ MetadataList::set(const Entry &e, ControlValueView v, State &s) * a series of consecutively added metadata items. Its main purposes is to * enable applications to receive a list of changes made to a MetadataList. * + * \sa Camera::metadataAvailable * \internal * \sa MetadataList::Checkpoint::diffSince() */ From patchwork Tue Jan 6 16:57:47 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25645 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 2BA07C32AF for ; Tue, 6 Jan 2026 16:58:19 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4798461FD3; Tue, 6 Jan 2026 17:58:14 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="c0VD5JTJ"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id B9C1D61FC9 for ; Tue, 6 Jan 2026 17:58:01 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id AB5A91AAE; Tue, 6 Jan 2026 17:57:40 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1767718660; bh=feYXSQdNRDE377ftRoz1dXJzRqiPbSFTQ4/oH38D3f0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=c0VD5JTJokRnF5zVKg++0Kd1hREZPR4bM2EKWWkv98/Di5xJ0OXCRiqI/O1lpQVU+ kRL66SVJy9XCqeeXwY2dsQB1DdMWdwjzshthALdS0LvOGArHdFF0baixgYaggNezuj 1LbEBKvgO+wgM0lanwZe0Vtgi9zugvez0TZksqbs= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Kieran Bingham Subject: [PATCH v4 15/22] guides: application: Document Camera::metadataAvailable Date: Tue, 6 Jan 2026 17:57:47 +0100 Message-ID: <20260106165754.1759831-16-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> References: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" From: Jacopo Mondi Document the Camera::metadataAvailable signal in the application developer guide as an opt-in feature to receive early metadata notifications. Signed-off-by: Jacopo Mondi Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham --- Original: https://patchwork.libcamera.org/patch/22228/ changes in v3: * adjust previous paragraph about signals --- Documentation/guides/application-developer.rst | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Documentation/guides/application-developer.rst b/Documentation/guides/application-developer.rst index 06c07d1e9..344c2a6df 100644 --- a/Documentation/guides/application-developer.rst +++ b/Documentation/guides/application-developer.rst @@ -350,8 +350,17 @@ them. .. _Qt Signals and Slots: https://doc.qt.io/qt-6/signalsandslots.html -The ``Camera`` device emits two signals that applications can connect to in -order to execute callbacks on frame completion events. +During request processing, the ``Camera`` device emits certain signals +to notify the application about events regarding each and every request. +These signals are described below. + +The ``Camera::metadataAvailable`` signal notifies applications of the +availability of metadata results before a request completes. Receiving +notification about metadata availability enables applications to fast-track +handling of metadata results before all the image buffers in a request are +ready. The full list of metadata results associated with a Request is also +available at request complete time, and receiving notifications for early +metadata availability is an optional feature for applications. The ``Camera::bufferCompleted`` signal notifies applications that a buffer with image data is available. Receiving notifications about the single buffer From patchwork Tue Jan 6 16:57:48 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25647 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 EE84EC323E for ; Tue, 6 Jan 2026 16:58:19 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B494461FC4; Tue, 6 Jan 2026 17:58:16 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="OiGdrGPk"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0B2EC61FC2 for ; Tue, 6 Jan 2026 17:58:02 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id ECB1327FB; Tue, 6 Jan 2026 17:57:40 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1767718661; bh=nPLvG5KkAcJaRNTUh6/zF8JDQ5kGoT7sXVLbXGriM70=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=OiGdrGPk2pWedp97NbIq2lIEHPcS03tvTfB0y1A694mFsWwQNn+soD6REBIDW1YyR OAhO9PQuSv7cikIPQhIEVC2VFQ8xqibTCWwYaZfwENAkzNzI81dcd3knl0MjlJiSQR NMgC763xkZcWgkVYfAAipZtQrxDXeUQPt6OOCYfs= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder , Kieran Bingham Subject: [PATCH v4 16/22] libcamera: pipeline_handler: Add metadataAvailable() function Date: Tue, 6 Jan 2026 17:57:48 +0100 Message-ID: <20260106165754.1759831-17-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> References: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" From: Jacopo Mondi Currently the way pipeline handlers report metadata for a Request is by accessing the Request::metadata_ list directly, and either merging a ControlList or seting a single control value there. Direct access to Request::metadata_ is, however, problematic as even if metadata would be available earlier, pipeline handlers can only accumulate the results in Request::metadata_ and they're only available for applications at Request complete time. Instead of letting pipeline handlers access Request::metadata_ directly provide two helper functions, similar in spirit to PipelineHandler::completeBuffer() and PipelineHandler::completeRequest(), to allow pipeline handlers to notify early availability of metadata. Provide three overloads, one that accepts a ControlList and merges it into Request::metadata_, one that allows to set a single metadata result there without going through an intermediate copy, and one that runs a callback for conditional metadata reporting without an intermediate ControlList. The newly provided helpers trigger the Camera::availableMetadata signal from where applications can retrieve the list of controls that have just been made available by the pipeline handler. Signed-off-by: Jacopo Mondi [Fill both lists, use `type_identity`, new overload, adjust commit message.] Signed-off-by: Barnabás Pőcze Reviewed-by: Paul Elder Reviewed-by: Kieran Bingham --- Original: https://patchwork.libcamera.org/patch/22229/ changes in v3: * abort if merging ControlList into metadata fails changes in v2: * add new overload that takes an invocable object for more flexibility --- include/libcamera/internal/pipeline_handler.h | 52 +++++++++++ src/libcamera/pipeline_handler.cpp | 92 +++++++++++++++++++ 2 files changed, 144 insertions(+) diff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h index b4f97477a..10206945d 100644 --- a/include/libcamera/internal/pipeline_handler.h +++ b/include/libcamera/internal/pipeline_handler.h @@ -7,16 +7,21 @@ #pragma once +#include #include #include #include #include +#include #include +#include #include #include +#include "libcamera/internal/request.h" + namespace libcamera { class Camera; @@ -58,6 +63,53 @@ public: void registerRequest(Request *request); void queueRequest(Request *request); + void metadataAvailable(Request *request, const ControlList &metadata); + + template + void metadataAvailable(Request *request, const Control &ctrl, + const internal::cxx20::type_identity_t &value) + { + Request::Private *d = request->_d(); + + auto &m = d->metadata2(); + const auto c = m.checkpoint(); + + std::ignore = m.set(ctrl, value); + d->metadata().set(ctrl, value); + + const auto diff = c.diffSince(); + if (diff) + d->camera()->metadataAvailable.emit(request, diff); + } + +#ifndef __DOXYGEN__ + struct MetadataSetter { + Request::Private *d; + + template + void operator()(const Control &ctrl, + const internal::cxx20::type_identity_t &value) const + { + d->metadata().set(ctrl, value); + std::ignore = d->metadata2().set(ctrl, value); + } + }; + + template> * = nullptr> +#else + template +#endif + void metadataAvailable(Request *request, Func func) + { + Request::Private *d = request->_d(); + const auto c = d->metadata2().checkpoint(); + + std::invoke(func, MetadataSetter{ d }); + + if (const auto diff = c.diffSince()) + d->camera()->metadataAvailable.emit(request, diff); + } + bool completeBuffer(Request *request, FrameBuffer *buffer); void completeRequest(Request *request); void cancelRequest(Request *request); diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp index 5c469e5ba..26675104b 100644 --- a/src/libcamera/pipeline_handler.cpp +++ b/src/libcamera/pipeline_handler.cpp @@ -543,6 +543,98 @@ void PipelineHandler::doQueueRequests(Camera *camera) * \return 0 on success or a negative error code otherwise */ +/** + * \brief Signal the availability of metadata for \a request + * \param[in] request The request the metadata belongs to + * \param[in] metadata The collection of metadata items + * + * This function copies metadata items from \a metadata to the cumulative metadata + * collection of \a request. This function may be called multiple times, but each + * metadata must only be set at most once. Afterwards the function notifies the + * application by triggering the Camera::availableMetadata signal with the just + * added metadata items. + * + * Early metadata completion allows pipeline handlers to fast track delivery of + * metadata results as soon as they are available before the completion of \a + * request. The full list of metadata results of a Request is available at + * Request completion time in Request::metadata(). + * + * \context This function shall be called from the CameraManager thread. + * + * \sa PipelineHandler::metadataAvailable(Request *request, Func func) + * \sa PipelineHandler::metadataAvailable(Request *request, + * const Control &ctrl, + * const internal::cxx20::type_identity_t &value) + */ +void PipelineHandler::metadataAvailable(Request *request, const ControlList &metadata) +{ + Request::Private *d = request->_d(); + + d->metadata().merge(metadata); + + const auto diff = d->metadata2().merge(metadata); + if (!diff) + LOG(Pipeline, Fatal) << "Tried to add incompatible metadata items"; + + if (!diff->empty()) + d->camera()->metadataAvailable.emit(request, *diff); +} + +/** + * \fn void PipelineHandler::metadataAvailable(Request *request, const Control &ctrl, + * const internal::cxx20::type_identity_t &value) + * \copybrief PipelineHandler::metadataAvailable(Request *request, const ControlList &metadata) + * \param[in] request The request the metadata belongs to + * \param[in] ctrl The control id of the metadata item + * \param[in] value The value of the metadata item + * + * This function serves the same purpose as + * PipelineHandler::metadataAvailable(Request *request, const ControlList &metadata) + * but it allows a single metadata item to be reported directly, + * without creating a ControlList. + * + * \context This function shall be called from the CameraManager thread. + * \sa PipelineHandler::metadataAvailable(Request *request, const ControlList &metadata) + */ + +/** + * \fn void PipelineHandler::metadataAvailable(Request *request, Func func) + * \copybrief PipelineHandler::metadataAvailable(Request *request, const ControlList &metadata) + * \param[in] request The request the metadata belongs to + * \param[in] func The callback to invoke + * + * This function serves the same purpose as + * PipelineHandler::metadataAvailable(Request *request, const ControlList &metadata) + * but it provides more flexibility for pipeline handlers. This function invokes + * \a func, which receives as its sole argument an object of unspecified type whose + * operator() can be used to add metadata items. + * + * For example, a PipelineHandler might use this function to conditionally report two + * metadata items without creating an intermediate ControlList: + * + * \code + metadataAvailable(request, [&](auto set) { + if (...) // controls::X is available and ready to be reported + set(controls::X, ...); + if (...) // controls::Y is available and ready to be reported + set(controls::Y, ...); + // ... + }); + * \endcode + * + * The advantage of the above over two calls to + * PipelineHandler::metadataAvailable(Request *request, const Control &ctrl, const internal::cxx20::type_identity_t &value) + * is that the application is only notified once, after \a func has returned. + * + * \note Calling any overload of metadataAvailable() inside \a func is not allowed. + * \note The object passed to \a func is only usable while \a func runs, it must not + * be saved or reused. + * + * \context This function shall be called from the CameraManager thread. + * + * \sa PipelineHandler::metadataAvailable(Request *request, const ControlList &metadata) + */ + /** * \brief Complete a buffer for a request * \param[in] request The request the buffer belongs to From patchwork Tue Jan 6 16:57:49 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25646 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 753ADC32C1 for ; Tue, 6 Jan 2026 16:58:19 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0FECB61FF1; Tue, 6 Jan 2026 17:58:16 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="UiKnDtTf"; 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 81CE361FD4 for ; Tue, 6 Jan 2026 17:58:02 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 38EDF82A; Tue, 6 Jan 2026 17:57:41 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1767718661; bh=++AuruAGHo/40XDNE7QMmVlClER48RxnXnpxx18i1rc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UiKnDtTf2qHk0hagwS+sg+Fk9TpW5iM6hBCFDWKFpo0nDQ1Po5b/yin2o2F8iDG9g /bt6qjsejCm6oCAYELUEVvhvmwQ9m8Uuvh/hP2SJzYdVx0zmUZadZ62B9LTJ561bJU 02pwgECQ82QFU23ijGmK/XndGAbcR5sT1UV+CCYY= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Kieran Bingham Subject: [PATCH v4 17/22] guides: pipeline_handler: Document PipelineHandler::metadataAvailable Date: Tue, 6 Jan 2026 17:57:49 +0100 Message-ID: <20260106165754.1759831-18-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> References: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" From: Jacopo Mondi Document the new available helpers to accumulate metadata results in Request::metadata and notify applications about metadata availability. Signed-off-by: Jacopo Mondi Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham --- Original: https://patchwork.libcamera.org/patch/22230/ changes in v3: * reword some parts --- Documentation/guides/pipeline-handler.rst | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/Documentation/guides/pipeline-handler.rst b/Documentation/guides/pipeline-handler.rst index 85d9cc870..9ec0da01b 100644 --- a/Documentation/guides/pipeline-handler.rst +++ b/Documentation/guides/pipeline-handler.rst @@ -1358,18 +1358,27 @@ and Slot ` classes documentation. .. _Qt Signals and Slots: https://doc.qt.io/qt-6/signalsandslots.html In order to notify applications about the availability of new frames and data, -the ``Camera`` device exposes two ``Signals`` to which applications can connect -to be notified of frame completion events. The ``bufferComplete`` signal serves -to report to applications the completion event of a single ``Stream`` part of a +the ``Camera`` device exposes three ``Signals`` to which applications can +connect to be notified of frame completion and metadata availability events. + +The ``metadataAvailable`` signal serves to notify about the availability of +metadata for a particular ``Request``. The ``bufferComplete`` signal serves to +report to applications the completion event of a single ``Stream`` part of a ``Request``, while the ``requestComplete`` signal notifies the completion of all the ``Streams`` and data submitted as part of a request. This mechanism allows implementation of partial request completion, which allows an application to inspect completed buffers associated with the single streams without waiting for all of them to be ready. -The ``bufferComplete`` and ``requestComplete`` signals are emitted by the -``Camera`` device upon notifications received from the pipeline handler, which -tracks the buffers and request completion status. +The ``metadataAvailable``, ``bufferComplete`` and ``requestComplete`` signals +are emitted by the ``Camera`` device upon notifications received from the +pipeline handler, which tracks the metadata, buffers and request completion +status. + +Metadata availability is signalled by the pipeline handlers by calling the +PipelineHandler base class ``metadataAvailable`` function. This function +notifies applications about metadata availability and accumulates metadata +results in the ``Request::metadata()`` list. The single buffer completion notification is implemented by pipeline handlers by :doxy-int:`connecting ` the ``bufferReady`` signal of the From patchwork Tue Jan 6 16:57:50 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25648 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 50979C32DE for ; Tue, 6 Jan 2026 16:58:20 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 60FAD61FC2; Tue, 6 Jan 2026 17:58:17 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="D/azjUv2"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6FD1261FD3 for ; Tue, 6 Jan 2026 17:58:02 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 723DFF01; Tue, 6 Jan 2026 17:57:41 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1767718661; bh=ZC5EPr5ineHVPVIA2ehyJ5iSyDQ7KboRV6q1+mxMD5U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=D/azjUv2VzXJ7r1I7oWjPMHKXDXTVQFy5/U6v6CEbeJiR5cclVpPVKgvnowgRLcqD WXue4EL2FZQOKxHJVY+qYebjp0COif5eEdJrc64O35KpYKvkMNl457Iz9bzcTB5uAP 82LkNtMuIo9C7A70ogLAMeCB4Fbpkq4opwUlgLZA= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v4 18/22] [DNI] apps: cam: Use Camera::metadataAvailable signal Date: Tue, 6 Jan 2026 17:57:50 +0100 Message-ID: <20260106165754.1759831-19-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> References: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" From: Jacopo Mondi Handle the Camera::metadataAvailable signal and print the metadata list. Use the --metadata option of cam to validate that the metadata list in Request::metadata() matches the accumulated results. --- Original: https://patchwork.libcamera.org/patch/22234/ --- src/apps/cam/camera_session.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/apps/cam/camera_session.cpp b/src/apps/cam/camera_session.cpp index 610279c24..ba43220d1 100644 --- a/src/apps/cam/camera_session.cpp +++ b/src/apps/cam/camera_session.cpp @@ -311,6 +311,17 @@ int CameraSession::start() camera_->requestCompleted.connect(this, &CameraSession::requestComplete); + if (printMetadata_) { + camera_->metadataAvailable.connect(this, [](Request *r, MetadataList::Diff update) { + std::cout << ">> early metadata for " << r->sequence() << " with " << update.size() << " entries {\n"; + for (auto &&[tag, v] : update) { + const auto *id = controls::controls.at(tag); + std::cout << '\t' << id->name() << " = " << v << '\n'; + } + std::cout << "}" << std::endl; + }); + } + #ifdef HAVE_KMS if (options_.isSet(OptDisplay)) sink_ = std::make_unique(options_[OptDisplay].toString()); From patchwork Tue Jan 6 16:57:51 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25649 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 AD89BC32E0 for ; Tue, 6 Jan 2026 16:58:20 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 394B961FFC; Tue, 6 Jan 2026 17:58:18 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="SgWEPOB2"; 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 AD02161FC5 for ; Tue, 6 Jan 2026 17:58:02 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A55B227FD; Tue, 6 Jan 2026 17:57:41 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1767718661; bh=vTqSKINkUPKA8GSZpLwj8tMURCG6R4QGjmovCN8mIEU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=SgWEPOB2mMmqpCVhUF2kfcDDp6+aWS0cdbF3e1EHp5iHa8xrLhLKAdQLVHr4bRd4P YwSgorKbAcLOBF8ULsmSNT+ITHf8wFFf39TzHHAsS9KEDktbqQc06AjLN8focgEsL4 znM57IU97PHyhGOml6WSzRuUtSqw9ETAvvEl1q2I= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder Subject: [PATCH v4 19/22] libcamera: pipeline_handler: Inject "debug" metadata Date: Tue, 6 Jan 2026 17:57:51 +0100 Message-ID: <20260106165754.1759831-20-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> References: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" Inject all metadata controls in the "debug" namespace into the metadata plan of each camera so that they can be used seamlessly. Dynamically sized array-like controls have a hard-coded size of 32 elements. Additionally, a new type is added for inspecting properties of a control type at runtime since that was not available previously. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi Reviewed-by: Paul Elder --- include/libcamera/internal/controls.h | 39 +++++++++++++++++++++++ src/libcamera/controls.cpp | 2 +- src/libcamera/pipeline_handler.cpp | 45 +++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 include/libcamera/internal/controls.h diff --git a/include/libcamera/internal/controls.h b/include/libcamera/internal/controls.h new file mode 100644 index 000000000..be3f93e43 --- /dev/null +++ b/include/libcamera/internal/controls.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas on Board Oy + */ + +#pragma once + +#include + +namespace libcamera::controls::details { + +struct TypeInfo { + std::size_t size = 0; + std::size_t alignment = 0; + + explicit operator bool() const { return alignment != 0; } + + static constexpr TypeInfo get(ControlType t) + { + switch (t) { + case ControlTypeNone: return {}; + case ControlTypeBool: return { sizeof(bool), alignof(bool) }; + case ControlTypeByte: return { sizeof(uint8_t), alignof(uint8_t) }; + case ControlTypeUnsigned16: return { sizeof(uint16_t), alignof(uint16_t) }; + case ControlTypeUnsigned32: return { sizeof(uint32_t), alignof(uint32_t) }; + case ControlTypeInteger32: return { sizeof(int32_t), alignof(int32_t) }; + case ControlTypeInteger64: return { sizeof(int64_t), alignof(int64_t) }; + case ControlTypeFloat: return { sizeof(float), alignof(float) }; + case ControlTypeString: return { sizeof(char), alignof(char) }; + case ControlTypeRectangle: return { sizeof(Rectangle), alignof(Rectangle) }; + case ControlTypeSize: return { sizeof(Size), alignof(Size) }; + case ControlTypePoint: return { sizeof(Point), alignof(Point) }; + } + + return {}; + } +}; + +} /* libcamera::controls::details */ diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp index bddbac10c..72331ddb5 100644 --- a/src/libcamera/controls.cpp +++ b/src/libcamera/controls.cpp @@ -17,7 +17,7 @@ #include "libcamera/internal/control_validator.h" /** - * \file controls.h + * \file libcamera/controls.h * \brief Framework to manage controls related to an object * * A control is a mean to govern or influence the operation of an object, and in diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp index 26675104b..1b9e18a04 100644 --- a/src/libcamera/pipeline_handler.cpp +++ b/src/libcamera/pipeline_handler.cpp @@ -16,11 +16,13 @@ #include #include +#include #include #include #include "libcamera/internal/camera.h" #include "libcamera/internal/camera_manager.h" +#include "libcamera/internal/controls.h" #include "libcamera/internal/device_enumerator.h" #include "libcamera/internal/media_device.h" #include "libcamera/internal/request.h" @@ -768,6 +770,47 @@ std::string PipelineHandler::configurationFile(const std::string &subdir, return std::string(); } +namespace { + +/* + * This is kind of hack. The metadata controls in the "debug" namespace + * are forcefully injected into each Camera's MetadataListPlan so that + * they work seamlessly without any additional setup. + * + * The dynamically-sized array-like controls have a maximum capacity + * determined by the magic number below. + */ +void extendMetadataPlanWithDebugMetadata(MetadataListPlan& mlp) +{ + constexpr std::size_t kDynamicArrayCapacity = 32; + + for (const auto &[id, ctrl] : controls::controls) { + if (!ctrl->isOutput()) + continue; + if (ctrl->vendor() != "debug") + continue; + if (mlp.get(id)) + continue; + + std::size_t count = ctrl->size(); + if (count == 0) /* Non-array controls have a static size of 0. */ + count = 1; + else if (ctrl->size() == libcamera::dynamic_extent) + count = kDynamicArrayCapacity; + + const auto info = controls::details::TypeInfo::get(ctrl->type()); + if (!info) + continue; + + [[maybe_unused]] bool ok = mlp.set(id, + info.size, info.alignment, + count, ctrl->type(), ctrl->isArray()); + ASSERT(ok); + } +} + +} /* namespace */ + /** * \brief Register a camera to the camera manager and pipeline handler * \param[in] camera The camera to be added @@ -813,6 +856,8 @@ void PipelineHandler::registerCamera(std::shared_ptr camera) Camera::Private *data = camera->_d(); data->properties_.set(properties::SystemDevices, devnums); + extendMetadataPlanWithDebugMetadata(data->metadataPlan_); + manager_->_d()->addCamera(std::move(camera)); } From patchwork Tue Jan 6 16:57:52 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25650 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 42ECCBDCBF for ; Tue, 6 Jan 2026 16:58:21 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2357D62000; Tue, 6 Jan 2026 17:58:19 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="SU1uBEoQ"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0F64061FD0 for ; Tue, 6 Jan 2026 17:58:03 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E191A27FE; Tue, 6 Jan 2026 17:57:41 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1767718662; bh=jtwX65RzNmNJ+aJ8e8QrwQc31HZxu339fMbNIR2kbjs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=SU1uBEoQzSBBPDCkMzqFCcUeSz5HMA8VUx1SlR7LNmt+iFNdGa39RiuZhNv1cGbl2 zQfLtVfGxlkMuUtGNFXgL0D3VvwubWB1ZrZy6NysFavqdS1YgryYUXSiYDDWMnue/I 9lBwl1ucK4uX6IPzmzx8IdYHP1UbQYzKr14cnKk0= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , Kieran Bingham Subject: [PATCH v4 20/22] libcamera: pipeline: Fill `MetadataListPlan` of cameras Date: Tue, 6 Jan 2026 17:57:52 +0100 Message-ID: <20260106165754.1759831-21-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> References: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" Fill the newly introduced `MetadataListPlan` member of the camera's private data during initializations, similarly to the camera's `ControlInfoMap`. Signed-off-by: Barnabás Pőcze Reviewed-by: Paul Elder Reviewed-by: Kieran Bingham --- changes in v2: * add missing controls for `rkisp1` * sort alphabetically --- .../internal/software_isp/software_isp.h | 3 +- include/libcamera/ipa/ipu3.mojom | 3 +- include/libcamera/ipa/mali-c55.mojom | 3 +- include/libcamera/ipa/raspberrypi.mojom | 1 + include/libcamera/ipa/rkisp1.mojom | 3 +- include/libcamera/ipa/soft.mojom | 3 +- src/ipa/ipu3/algorithms/agc.cpp | 4 +++ src/ipa/ipu3/algorithms/awb.cpp | 12 +++++++ src/ipa/ipu3/algorithms/awb.h | 1 + src/ipa/ipu3/ipa_context.cpp | 3 ++ src/ipa/ipu3/ipa_context.h | 3 ++ src/ipa/ipu3/ipu3.cpp | 8 +++-- src/ipa/mali-c55/algorithms/agc.cpp | 5 +++ src/ipa/mali-c55/algorithms/awb.cpp | 7 ++++ src/ipa/mali-c55/algorithms/awb.h | 1 + src/ipa/mali-c55/algorithms/blc.cpp | 2 ++ src/ipa/mali-c55/ipa_context.h | 3 ++ src/ipa/mali-c55/mali-c55.cpp | 6 ++-- src/ipa/rkisp1/algorithms/agc.cpp | 10 ++++++ src/ipa/rkisp1/algorithms/awb.cpp | 4 +++ src/ipa/rkisp1/algorithms/blc.cpp | 2 ++ src/ipa/rkisp1/algorithms/ccm.cpp | 2 ++ src/ipa/rkisp1/algorithms/goc.cpp | 2 ++ src/ipa/rkisp1/algorithms/lux.cpp | 8 ++++- src/ipa/rkisp1/ipa_context.h | 1 + src/ipa/rkisp1/rkisp1.cpp | 8 +++-- src/ipa/rpi/common/ipa_base.cpp | 34 +++++++++++++++++++ src/ipa/rpi/pisp/pisp.cpp | 5 +-- src/ipa/rpi/vc4/vc4.cpp | 4 ++- src/ipa/simple/algorithms/agc.cpp | 8 +++++ src/ipa/simple/algorithms/agc.h | 1 + src/ipa/simple/algorithms/awb.cpp | 8 +++++ src/ipa/simple/algorithms/awb.h | 1 + src/ipa/simple/algorithms/blc.cpp | 3 ++ src/ipa/simple/algorithms/ccm.cpp | 3 ++ src/ipa/simple/algorithms/lut.cpp | 3 ++ src/ipa/simple/ipa_context.h | 2 ++ src/ipa/simple/soft_simple.cpp | 8 +++-- src/libcamera/pipeline/imx8-isi/imx8-isi.cpp | 2 ++ src/libcamera/pipeline/ipu3/ipu3.cpp | 7 +++- src/libcamera/pipeline/mali-c55/mali-c55.cpp | 2 +- src/libcamera/pipeline/rkisp1/rkisp1.cpp | 4 ++- .../pipeline/rpi/common/pipeline_base.cpp | 8 +++++ src/libcamera/pipeline/simple/simple.cpp | 2 +- src/libcamera/pipeline/uvcvideo/uvcvideo.cpp | 2 ++ src/libcamera/pipeline/vimc/vimc.cpp | 2 ++ .../pipeline/virtual/config_parser.cpp | 3 ++ src/libcamera/software_isp/software_isp.cpp | 6 ++-- 48 files changed, 203 insertions(+), 23 deletions(-) diff --git a/include/libcamera/internal/software_isp/software_isp.h b/include/libcamera/internal/software_isp/software_isp.h index ad89c9b3c..48a838a69 100644 --- a/include/libcamera/internal/software_isp/software_isp.h +++ b/include/libcamera/internal/software_isp/software_isp.h @@ -41,6 +41,7 @@ class Debayer; class FrameBuffer; class PixelFormat; class Stream; +class MetadataListPlan; struct StreamConfiguration; LOG_DECLARE_CATEGORY(SoftwareIsp) @@ -49,7 +50,7 @@ class SoftwareIsp : public Object { public: SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor, - ControlInfoMap *ipaControls); + ControlInfoMap *ipaControls, MetadataListPlan *metadataPlan); ~SoftwareIsp(); int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; } diff --git a/include/libcamera/ipa/ipu3.mojom b/include/libcamera/ipa/ipu3.mojom index d9a50b01d..f49b77797 100644 --- a/include/libcamera/ipa/ipu3.mojom +++ b/include/libcamera/ipa/ipu3.mojom @@ -20,7 +20,8 @@ interface IPAIPU3Interface { init(libcamera.IPASettings settings, libcamera.IPACameraSensorInfo sensorInfo, libcamera.ControlInfoMap sensorControls) - => (int32 ret, libcamera.ControlInfoMap ipaControls); + => (int32 ret, libcamera.ControlInfoMap ipaControls, + libcamera.MetadataListPlan metadata); start() => (int32 ret); stop(); diff --git a/include/libcamera/ipa/mali-c55.mojom b/include/libcamera/ipa/mali-c55.mojom index 39b7f1f10..af1d08372 100644 --- a/include/libcamera/ipa/mali-c55.mojom +++ b/include/libcamera/ipa/mali-c55.mojom @@ -11,7 +11,8 @@ struct IPAConfigInfo { interface IPAMaliC55Interface { init(libcamera.IPASettings settings, IPAConfigInfo configInfo) - => (int32 ret, libcamera.ControlInfoMap ipaControls); + => (int32 ret, libcamera.ControlInfoMap ipaControls, + libcamera.MetadataListPlan metadataPlan); start() => (int32 ret); stop(); diff --git a/include/libcamera/ipa/raspberrypi.mojom b/include/libcamera/ipa/raspberrypi.mojom index 12b083e9d..c32391911 100644 --- a/include/libcamera/ipa/raspberrypi.mojom +++ b/include/libcamera/ipa/raspberrypi.mojom @@ -26,6 +26,7 @@ struct InitParams { struct InitResult { SensorConfig sensorConfig; libcamera.ControlInfoMap controlInfo; + libcamera.MetadataListPlan metadataPlan; }; struct BufferIds { diff --git a/include/libcamera/ipa/rkisp1.mojom b/include/libcamera/ipa/rkisp1.mojom index 068e89884..015ba4603 100644 --- a/include/libcamera/ipa/rkisp1.mojom +++ b/include/libcamera/ipa/rkisp1.mojom @@ -19,7 +19,8 @@ interface IPARkISP1Interface { uint32 hwRevision, uint32 supportedBlocks, libcamera.IPACameraSensorInfo sensorInfo, libcamera.ControlInfoMap sensorControls) - => (int32 ret, libcamera.ControlInfoMap ipaControls); + => (int32 ret, libcamera.ControlInfoMap ipaControls, + libcamera.MetadataListPlan metadataPlan); start() => (int32 ret); stop(); diff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom index 77328c5fd..360ed668b 100644 --- a/include/libcamera/ipa/soft.mojom +++ b/include/libcamera/ipa/soft.mojom @@ -18,7 +18,8 @@ interface IPASoftInterface { libcamera.SharedFD fdParams, libcamera.IPACameraSensorInfo sensorInfo, libcamera.ControlInfoMap sensorControls) - => (int32 ret, libcamera.ControlInfoMap ipaControls, bool ccmEnabled); + => (int32 ret, libcamera.ControlInfoMap ipaControls, bool ccmEnabled, + libcamera.MetadataListPlan metadataPlan); start() => (int32 ret); stop(); configure(IPAConfigInfo configInfo) diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp index b0d89541d..2d7b0482f 100644 --- a/src/ipa/ipu3/algorithms/agc.cpp +++ b/src/ipa/ipu3/algorithms/agc.cpp @@ -82,6 +82,10 @@ int Agc::init(IPAContext &context, const YamlObject &tuningData) context.ctrlMap.merge(controls()); + context.metadataPlan.set(controls::AnalogueGain); + context.metadataPlan.set(controls::ExposureTime); + context.metadataPlan.set(controls::FrameDuration); + return 0; } diff --git a/src/ipa/ipu3/algorithms/awb.cpp b/src/ipa/ipu3/algorithms/awb.cpp index 55de05d9e..499c7790f 100644 --- a/src/ipa/ipu3/algorithms/awb.cpp +++ b/src/ipa/ipu3/algorithms/awb.cpp @@ -197,6 +197,18 @@ Awb::Awb() Awb::~Awb() = default; +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Awb::init(IPAContext &context, [[maybe_unused]] const YamlObject &tuningData) +{ + context.metadataPlan.set(controls::AwbEnable); + context.metadataPlan.set(controls::ColourGains); + context.metadataPlan.set(controls::ColourTemperature); + + return 0; +} + /** * \copydoc libcamera::ipa::Algorithm::configure */ diff --git a/src/ipa/ipu3/algorithms/awb.h b/src/ipa/ipu3/algorithms/awb.h index dbf69c907..1d47bffa3 100644 --- a/src/ipa/ipu3/algorithms/awb.h +++ b/src/ipa/ipu3/algorithms/awb.h @@ -40,6 +40,7 @@ public: Awb(); ~Awb(); + int init(IPAContext &context, const YamlObject &tuningData) override; int configure(IPAContext &context, const IPAConfigInfo &configInfo) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, diff --git a/src/ipa/ipu3/ipa_context.cpp b/src/ipa/ipu3/ipa_context.cpp index 3b22f7917..f0b8b5dbe 100644 --- a/src/ipa/ipu3/ipa_context.cpp +++ b/src/ipa/ipu3/ipa_context.cpp @@ -54,6 +54,9 @@ namespace libcamera::ipa::ipu3 { * * \var IPAContext::ctrlMap * \brief A ControlInfoMap::Map of controls populated by the algorithms + * + * \var IPAContext::metadataPlan + * \brief A MetadataListPlan populated by the algorithms */ /** diff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h index 97fcf06cd..f4f45ef4d 100644 --- a/src/ipa/ipu3/ipa_context.h +++ b/src/ipa/ipu3/ipa_context.h @@ -14,6 +14,7 @@ #include #include +#include #include @@ -95,6 +96,8 @@ struct IPAContext { FCQueue frameContexts; ControlInfoMap::Map ctrlMap; + + MetadataListPlan metadataPlan; // TODO: only needed during init(), how could be removed? }; } /* namespace ipa::ipu3 */ diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp index b926f579a..a673d97bc 100644 --- a/src/ipa/ipu3/ipu3.cpp +++ b/src/ipa/ipu3/ipu3.cpp @@ -144,7 +144,8 @@ public: int init(const IPASettings &settings, const IPACameraSensorInfo &sensorInfo, const ControlInfoMap &sensorControls, - ControlInfoMap *ipaControls) override; + ControlInfoMap *ipaControls, + MetadataListPlan *metadataPlan) override; int start() override; void stop() override; @@ -299,7 +300,8 @@ void IPAIPU3::updateControls(const IPACameraSensorInfo &sensorInfo, int IPAIPU3::init(const IPASettings &settings, const IPACameraSensorInfo &sensorInfo, const ControlInfoMap &sensorControls, - ControlInfoMap *ipaControls) + ControlInfoMap *ipaControls, + MetadataListPlan *metadataPlan) { camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); if (camHelper_ == nullptr) { @@ -348,6 +350,8 @@ int IPAIPU3::init(const IPASettings &settings, /* Initialize controls. */ updateControls(sensorInfo, sensorControls, ipaControls); + *metadataPlan = std::move(context_.metadataPlan); + return 0; } diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp index 014fd1245..45738c0b1 100644 --- a/src/ipa/mali-c55/algorithms/agc.cpp +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -145,6 +145,11 @@ int Agc::init(IPAContext &context, const YamlObject &tuningData) ); context.ctrlMap.merge(controls()); + context.metadataPlan.set(controls::AnalogueGain); + context.metadataPlan.set(controls::ColourTemperature); + context.metadataPlan.set(controls::DigitalGain); + context.metadataPlan.set(controls::ExposureTime); + return 0; } diff --git a/src/ipa/mali-c55/algorithms/awb.cpp b/src/ipa/mali-c55/algorithms/awb.cpp index 964e81088..b6bbeca0f 100644 --- a/src/ipa/mali-c55/algorithms/awb.cpp +++ b/src/ipa/mali-c55/algorithms/awb.cpp @@ -29,6 +29,13 @@ Awb::Awb() { } +int Awb::init(IPAContext &context, [[maybe_unused]] const YamlObject &tuningData) +{ + context.metadataPlan.set(controls::ColourGains); + + return 0; +} + int Awb::configure([[maybe_unused]] IPAContext &context, [[maybe_unused]] const IPACameraSensorInfo &configInfo) { diff --git a/src/ipa/mali-c55/algorithms/awb.h b/src/ipa/mali-c55/algorithms/awb.h index 683a62af2..1b6cf9b5f 100644 --- a/src/ipa/mali-c55/algorithms/awb.h +++ b/src/ipa/mali-c55/algorithms/awb.h @@ -18,6 +18,7 @@ public: Awb(); ~Awb() = default; + int init(IPAContext &context, const YamlObject &tuningData) override; int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; void prepare(IPAContext &context, const uint32_t frame, diff --git a/src/ipa/mali-c55/algorithms/blc.cpp b/src/ipa/mali-c55/algorithms/blc.cpp index d099219c3..9a1ad8260 100644 --- a/src/ipa/mali-c55/algorithms/blc.cpp +++ b/src/ipa/mali-c55/algorithms/blc.cpp @@ -51,6 +51,8 @@ int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context, tuningParameters_ = true; + context.metadataPlan.set(controls::SensorBlackLevels); + LOG(MaliC55Blc, Debug) << "Black levels: 00 " << offset00 << ", 01 " << offset01 << ", 10 " << offset10 << ", 11 " << offset11; diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h index 13885eb83..55aef1d10 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -9,6 +9,7 @@ #include #include +#include #include "libcamera/internal/bayer_format.h" @@ -83,6 +84,8 @@ struct IPAContext { FCQueue frameContexts; ControlInfoMap::Map ctrlMap; + + MetadataListPlan metadataPlan; // TODO: only needed during init(), how could be removed? }; } /* namespace ipa::mali_c55 */ diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp index 1d2a4f75c..7d1b1a7be 100644 --- a/src/ipa/mali-c55/mali-c55.cpp +++ b/src/ipa/mali-c55/mali-c55.cpp @@ -49,7 +49,7 @@ public: IPAMaliC55(); int init(const IPASettings &settings, const IPAConfigInfo &ipaConfig, - ControlInfoMap *ipaControls) override; + ControlInfoMap *ipaControls, MetadataListPlan *metadataPlan) override; int start() override; void stop() override; int configure(const IPAConfigInfo &ipaConfig, uint8_t bayerOrder, @@ -99,7 +99,7 @@ std::string IPAMaliC55::logPrefix() const } int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig, - ControlInfoMap *ipaControls) + ControlInfoMap *ipaControls, MetadataListPlan *metadataPlan) { camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); if (!camHelper_) { @@ -134,6 +134,8 @@ int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig updateControls(ipaConfig.sensorInfo, ipaConfig.sensorControls, ipaControls); + *metadataPlan = std::move(context_.metadataPlan); + return 0; } diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index 1ecaff680..8b6bca6be 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -160,6 +160,16 @@ int Agc::init(IPAContext &context, const YamlObject &tuningData) context.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); context.ctrlMap.merge(controls()); + context.metadataPlan.set(controls::AeConstraintMode); + context.metadataPlan.set(controls::AeExposureMode); + context.metadataPlan.set(controls::AeMeteringMode); + context.metadataPlan.set(controls::AnalogueGain); + context.metadataPlan.set(controls::AnalogueGainMode); + context.metadataPlan.set(controls::ExposureTime); + context.metadataPlan.set(controls::ExposureTimeMode); + context.metadataPlan.set(controls::ExposureValue); + context.metadataPlan.set(controls::FrameDuration); + return 0; } diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp index e8da7974a..b83f022cd 100644 --- a/src/ipa/rkisp1/algorithms/awb.cpp +++ b/src/ipa/rkisp1/algorithms/awb.cpp @@ -119,6 +119,10 @@ int Awb::init(IPAContext &context, const YamlObject &tuningData) const auto &src = awbAlgo_->controls(); cmap.insert(src.begin(), src.end()); + context.metadataPlan.set(controls::AwbEnable); + context.metadataPlan.set(controls::ColourGains); + context.metadataPlan.set(controls::ColourTemperature); + return 0; } diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp index 32fc44fff..03071360b 100644 --- a/src/ipa/rkisp1/algorithms/blc.cpp +++ b/src/ipa/rkisp1/algorithms/blc.cpp @@ -103,6 +103,8 @@ int BlackLevelCorrection::init(IPAContext &context, const YamlObject &tuningData << ", green (blue) " << blackLevelGreenB_ << ", blue " << blackLevelBlue_; + context.metadataPlan.set(controls::SensorBlackLevels); + return 0; } diff --git a/src/ipa/rkisp1/algorithms/ccm.cpp b/src/ipa/rkisp1/algorithms/ccm.cpp index de2b6fe77..3115d3eff 100644 --- a/src/ipa/rkisp1/algorithms/ccm.cpp +++ b/src/ipa/rkisp1/algorithms/ccm.cpp @@ -66,6 +66,8 @@ int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData offsets_.setData({ { 0, Matrix({ 0, 0, 0 }) } }); } + context.metadataPlan.set(controls::ColourCorrectionMatrix); + return 0; } diff --git a/src/ipa/rkisp1/algorithms/goc.cpp b/src/ipa/rkisp1/algorithms/goc.cpp index a0e7030fe..46a4c2c36 100644 --- a/src/ipa/rkisp1/algorithms/goc.cpp +++ b/src/ipa/rkisp1/algorithms/goc.cpp @@ -60,6 +60,8 @@ int GammaOutCorrection::init(IPAContext &context, const YamlObject &tuningData) defaultGamma_ = tuningData["gamma"].get(kDefaultGamma); context.ctrlMap[&controls::Gamma] = ControlInfo(0.1f, 10.0f, defaultGamma_); + context.metadataPlan.set(controls::Gamma); + return 0; } diff --git a/src/ipa/rkisp1/algorithms/lux.cpp b/src/ipa/rkisp1/algorithms/lux.cpp index e9717bb3b..b005545ee 100644 --- a/src/ipa/rkisp1/algorithms/lux.cpp +++ b/src/ipa/rkisp1/algorithms/lux.cpp @@ -43,7 +43,13 @@ Lux::Lux() */ int Lux::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData) { - return lux_.parseTuningData(tuningData); + int ret = lux_.parseTuningData(tuningData); + if (ret) + return ret; + + context.metadataPlan.set(controls::Lux); + + return 0; } /** diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index b257cee55..86d2166e3 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -234,6 +234,7 @@ struct IPAContext { FCQueue frameContexts; ControlInfoMap::Map ctrlMap; + MetadataListPlan metadataPlan; // TODO: only needed during init(), how could be removed? DebugMetadata debugMetadata; diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index fbcc39103..466d4cc7e 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -55,7 +55,8 @@ public: uint32_t supportedBlocks, const IPACameraSensorInfo &sensorInfo, const ControlInfoMap &sensorControls, - ControlInfoMap *ipaControls) override; + ControlInfoMap *ipaControls, + MetadataListPlan *metadataPlan) override; int start() override; void stop() override; @@ -139,7 +140,8 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision, uint32_t supportedBlocks, const IPACameraSensorInfo &sensorInfo, const ControlInfoMap &sensorControls, - ControlInfoMap *ipaControls) + ControlInfoMap *ipaControls, + MetadataListPlan *metadataPlan) { /* \todo Add support for other revisions */ switch (hwRevision) { @@ -209,6 +211,8 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision, /* Initialize controls. */ updateControls(sensorInfo, sensorControls, ipaControls); + *metadataPlan = std::move(context_.metadataPlan); + return 0; } diff --git a/src/ipa/rpi/common/ipa_base.cpp b/src/ipa/rpi/common/ipa_base.cpp index 14aba4500..d5190d961 100644 --- a/src/ipa/rpi/common/ipa_base.cpp +++ b/src/ipa/rpi/common/ipa_base.cpp @@ -184,6 +184,40 @@ int32_t IpaBase::init(const IPASettings &settings, const InitParams ¶ms, Ini result->controlInfo = ControlInfoMap(std::move(ctrlMap), controls::controls); + // TODO: only set those that can be reported by configured algorithms? + // TODO: move this somewhere else? + result->metadataPlan.set(controls::AeConstraintMode); + result->metadataPlan.set(controls::AeExposureMode); + result->metadataPlan.set(controls::AeMeteringMode); + result->metadataPlan.set(controls::AeState); + result->metadataPlan.set(controls::AfPauseState); + result->metadataPlan.set(controls::AfState); + result->metadataPlan.set(controls::AnalogueGain); + result->metadataPlan.set(controls::AnalogueGainMode); + result->metadataPlan.set(controls::AwbEnable); + result->metadataPlan.set(controls::AwbMode); + result->metadataPlan.set(controls::Brightness); + result->metadataPlan.set(controls::ColourCorrectionMatrix); + result->metadataPlan.set(controls::ColourGains); + result->metadataPlan.set(controls::ColourTemperature); + result->metadataPlan.set(controls::Contrast); + result->metadataPlan.set(controls::DigitalGain); + result->metadataPlan.set(controls::ExposureTime); + result->metadataPlan.set(controls::ExposureTimeMode); + result->metadataPlan.set(controls::ExposureValue); + result->metadataPlan.set(controls::FocusFoM); + result->metadataPlan.set(controls::FrameDuration); + result->metadataPlan.set(controls::FrameDurationLimits); + result->metadataPlan.set(controls::HdrChannel); + result->metadataPlan.set(controls::HdrMode); + result->metadataPlan.set(controls::LensPosition); + result->metadataPlan.set(controls::Lux); + result->metadataPlan.set(controls::Saturation); + result->metadataPlan.set(controls::SensorBlackLevels); + result->metadataPlan.set(controls::SensorTemperature); + result->metadataPlan.set(controls::Sharpness); + result->metadataPlan.set(controls::draft::NoiseReductionMode); + return platformInit(params, result); } diff --git a/src/ipa/rpi/pisp/pisp.cpp b/src/ipa/rpi/pisp/pisp.cpp index ec7593ffc..04e7731d2 100644 --- a/src/ipa/rpi/pisp/pisp.cpp +++ b/src/ipa/rpi/pisp/pisp.cpp @@ -291,8 +291,7 @@ private: HdrStatus lastStitchHdrStatus_; }; -int32_t IpaPiSP::platformInit(const InitParams ¶ms, - [[maybe_unused]] InitResult *result) +int32_t IpaPiSP::platformInit(const InitParams ¶ms, InitResult *result) { const std::string &target = controller_.getTarget(); if (target != "pisp") { @@ -325,6 +324,8 @@ int32_t IpaPiSP::platformInit(const InitParams ¶ms, setDefaultConfig(); + result->metadataPlan.set(controls::rpi::PispStatsOutput, sizeof(pisp_statistics)); + return 0; } diff --git a/src/ipa/rpi/vc4/vc4.cpp b/src/ipa/rpi/vc4/vc4.cpp index 2b205b286..3014925ea 100644 --- a/src/ipa/rpi/vc4/vc4.cpp +++ b/src/ipa/rpi/vc4/vc4.cpp @@ -91,7 +91,7 @@ private: AwbStatus lastAwbStatus_; }; -int32_t IpaVc4::platformInit([[maybe_unused]] const InitParams ¶ms, [[maybe_unused]] InitResult *result) +int32_t IpaVc4::platformInit([[maybe_unused]] const InitParams ¶ms, InitResult *result) { const std::string &target = controller_.getTarget(); @@ -102,6 +102,8 @@ int32_t IpaVc4::platformInit([[maybe_unused]] const InitParams ¶ms, [[maybe_ return -EINVAL; } + result->metadataPlan.set(controls::rpi::Bcm2835StatsOutput, sizeof(bcm2835_isp_stats)); + return 0; } diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp index 2f7e040c2..73ae63fdf 100644 --- a/src/ipa/simple/algorithms/agc.cpp +++ b/src/ipa/simple/algorithms/agc.cpp @@ -41,6 +41,14 @@ Agc::Agc() { } +int Agc::init(IPAContext &context, [[maybe_unused]] const YamlObject &tuningData) +{ + context.metadataPlan.set(controls::AnalogueGain); + context.metadataPlan.set(controls::ExposureTime); + + return 0; +} + void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV) { /* diff --git a/src/ipa/simple/algorithms/agc.h b/src/ipa/simple/algorithms/agc.h index 112d9f5a1..00e70ea70 100644 --- a/src/ipa/simple/algorithms/agc.h +++ b/src/ipa/simple/algorithms/agc.h @@ -19,6 +19,7 @@ public: Agc(); ~Agc() = default; + int init(IPAContext &context, const YamlObject &tuningData) override; void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, const SwIspStats *stats, diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp index 0080865aa..67886a24c 100644 --- a/src/ipa/simple/algorithms/awb.cpp +++ b/src/ipa/simple/algorithms/awb.cpp @@ -26,6 +26,14 @@ LOG_DEFINE_CATEGORY(IPASoftAwb) namespace ipa::soft::algorithms { +int Awb::init(IPAContext &context, [[maybe_unused]] const YamlObject &tuningData) +{ + context.metadataPlan.set(controls::ColourGains); + context.metadataPlan.set(controls::ColourTemperature); + + return 0; +} + int Awb::configure(IPAContext &context, [[maybe_unused]] const IPAConfigInfo &configInfo) { diff --git a/src/ipa/simple/algorithms/awb.h b/src/ipa/simple/algorithms/awb.h index ad993f39c..b8ae63dcb 100644 --- a/src/ipa/simple/algorithms/awb.h +++ b/src/ipa/simple/algorithms/awb.h @@ -19,6 +19,7 @@ public: Awb() = default; ~Awb() = default; + int init(IPAContext &context, const YamlObject &tuningData) override; int configure(IPAContext &context, const IPAConfigInfo &configInfo) override; void prepare(IPAContext &context, const uint32_t frame, diff --git a/src/ipa/simple/algorithms/blc.cpp b/src/ipa/simple/algorithms/blc.cpp index 464e43c27..534011908 100644 --- a/src/ipa/simple/algorithms/blc.cpp +++ b/src/ipa/simple/algorithms/blc.cpp @@ -34,6 +34,9 @@ int BlackLevel::init([[maybe_unused]] IPAContext &context, */ definedLevel_ = blackLevel.value() >> 8; } + + context.metadataPlan.set(controls::SensorBlackLevels); + return 0; } diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp index 0a98406c1..b1758ff39 100644 --- a/src/ipa/simple/algorithms/ccm.cpp +++ b/src/ipa/simple/algorithms/ccm.cpp @@ -39,6 +39,9 @@ int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData context.ccmEnabled = true; context.ctrlMap[&controls::Saturation] = ControlInfo(0.0f, 2.0f, 1.0f); + context.metadataPlan.set(controls::ColourCorrectionMatrix); + context.metadataPlan.set(controls::Saturation); + return 0; } diff --git a/src/ipa/simple/algorithms/lut.cpp b/src/ipa/simple/algorithms/lut.cpp index 9aaab54f1..beed0aca8 100644 --- a/src/ipa/simple/algorithms/lut.cpp +++ b/src/ipa/simple/algorithms/lut.cpp @@ -28,6 +28,9 @@ int Lut::init(IPAContext &context, [[maybe_unused]] const YamlObject &tuningData) { context.ctrlMap[&controls::Contrast] = ControlInfo(0.0f, 2.0f, 1.0f); + + context.metadataPlan.set(controls::Contrast); + return 0; } diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index 26b60fb68..0dc8b38b2 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -12,6 +12,7 @@ #include #include +#include #include "libcamera/internal/matrix.h" #include "libcamera/internal/vector.h" @@ -103,6 +104,7 @@ struct IPAContext { IPAActiveState activeState; FCQueue frameContexts; ControlInfoMap::Map ctrlMap; + MetadataListPlan metadataPlan; // TODO: only needed during init(), how could be removed? bool ccmEnabled = false; }; diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp index dde116661..97b8332be 100644 --- a/src/ipa/simple/soft_simple.cpp +++ b/src/ipa/simple/soft_simple.cpp @@ -56,7 +56,8 @@ public: const IPACameraSensorInfo &sensorInfo, const ControlInfoMap &sensorControls, ControlInfoMap *ipaControls, - bool *ccmEnabled) override; + bool *ccmEnabled, + MetadataListPlan *metadataPlan) override; int configure(const IPAConfigInfo &configInfo) override; int start() override; @@ -96,7 +97,8 @@ int IPASoftSimple::init(const IPASettings &settings, const IPACameraSensorInfo &sensorInfo, const ControlInfoMap &sensorControls, ControlInfoMap *ipaControls, - bool *ccmEnabled) + bool *ccmEnabled, + MetadataListPlan *metadataPlan) { camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); if (!camHelper_) { @@ -190,6 +192,8 @@ int IPASoftSimple::init(const IPASettings &settings, return -EINVAL; } + *metadataPlan = std::move(context_.metadataPlan); + return 0; } diff --git a/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp b/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp index 706681fce..b1111f19d 100644 --- a/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp +++ b/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp @@ -171,6 +171,8 @@ int ISICameraData::init() properties_ = sensor_->properties(); + metadataPlan_.set(controls::SensorTimestamp); + return 0; } diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp index 0190f677e..04549899c 100644 --- a/src/libcamera/pipeline/ipu3/ipu3.cpp +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp @@ -1082,6 +1082,11 @@ int PipelineHandlerIPU3::registerCameras() if (ret) continue; + data->metadataPlan_.set(controls::draft::PipelineDepth); + data->metadataPlan_.set(controls::draft::TestPatternMode); + data->metadataPlan_.set(controls::ScalerCrop); + data->metadataPlan_.set(controls::SensorTimestamp); + const CameraSensorProperties::SensorDelays &delays = cio2->sensor()->sensorDelays(); std::unordered_map params = { { V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } }, @@ -1191,7 +1196,7 @@ int IPU3CameraData::loadIPA() ipa_->configurationFile(sensor->model() + ".yaml", "uncalibrated.yaml"); ret = ipa_->init(IPASettings{ ipaTuningFile, sensor->model() }, - sensorInfo, sensor->controls(), &ipaControls_); + sensorInfo, sensor->controls(), &ipaControls_, &metadataPlan_); if (ret) { LOG(IPU3, Error) << "Failed to initialise the IPU3 IPA"; return ret; diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp index 77f251416..f66615265 100644 --- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp @@ -402,7 +402,7 @@ int MaliC55CameraData::loadIPA() ControlInfoMap ipaControls; ret = ipa_->init({ ipaTuningFile, sensor_->model() }, ipaConfig, - &ipaControls); + &ipaControls, &metadataPlan_); if (ret) { LOG(MaliC55, Error) << "Failed to initialise the Mali-C55 IPA"; return ret; diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp index 320a4dc5a..d2cd1d4b8 100644 --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp @@ -410,7 +410,7 @@ int RkISP1CameraData::loadIPA(unsigned int hwRevision, uint32_t supportedBlocks) ret = ipa_->init({ ipaTuningFile, sensor_->model() }, hwRevision, supportedBlocks, sensorInfo, sensor_->controls(), - &ipaControls_); + &ipaControls_, &metadataPlan_); if (ret < 0) { LOG(RkISP1, Error) << "IPA initialization failure"; return ret; @@ -1493,6 +1493,8 @@ int PipelineHandlerRkISP1::createCamera(MediaEntity *sensor) updateControls(data.get()); + data->metadataPlan_.set(controls::SensorTimestamp); + std::set streams{ &data->mainPathStream_, &data->selfPathStream_, diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp index fb8e466f6..c50697877 100644 --- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp @@ -589,6 +589,9 @@ int PipelineHandlerBase::configure(Camera *camera, CameraConfiguration *config) data->controlInfo_ = ControlInfoMap(std::move(ctrlMap), result.controlInfo.idmap()); + /* Update `rpi::ScalerCrops` size for the corrent configuration. */ + data->metadataPlan_.set(controls::rpi::ScalerCrops, config->size()); + /* Setup the Video Mux/Bridge entities. */ for (auto &[device, link] : data->bridgeDevices_) { /* @@ -839,6 +842,11 @@ int PipelineHandlerBase::registerCamera(std::unique_ptr &camera /* Initialize the camera properties. */ data->properties_ = data->sensor_->properties(); + data->metadataPlan_ = std::move(result.metadataPlan); + data->metadataPlan_.set(controls::SensorTimestamp); + data->metadataPlan_.set(controls::FrameWallClock); + data->metadataPlan_.set(controls::ScalerCrop); + /* * The V4L2_CID_NOTIFY_GAINS control, if present, is used to inform the * sensor of the colour gains. It is defined to be a linear gain where diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp index b30b0a122..f1925cdf7 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -615,7 +615,7 @@ int SimpleCameraData::init() * Instantiate Soft ISP if this is enabled for the given driver and no converter is used. */ if (!converter_ && pipe->swIspEnabled()) { - swIsp_ = std::make_unique(pipe, sensor_.get(), &controlInfo_); + swIsp_ = std::make_unique(pipe, sensor_.get(), &controlInfo_, &metadataPlan_); if (!swIsp_->isValid()) { LOG(SimplePipeline, Warning) << "Failed to create software ISP, disabling software debayering"; diff --git a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp index 3bd51733d..fcd25cf4e 100644 --- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp +++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp @@ -617,6 +617,8 @@ int UVCCameraData::init(std::shared_ptr media) controlInfo_ = ControlInfoMap(std::move(ctrls), controls::controls); + metadataPlan_.set(controls::SensorTimestamp); + /* * Close to allow camera to go into runtime-suspend, video_ will be * re-opened from acquireDevice() and validate(). diff --git a/src/libcamera/pipeline/vimc/vimc.cpp b/src/libcamera/pipeline/vimc/vimc.cpp index 4a03c149a..182aa56e8 100644 --- a/src/libcamera/pipeline/vimc/vimc.cpp +++ b/src/libcamera/pipeline/vimc/vimc.cpp @@ -593,6 +593,8 @@ int VimcCameraData::init() controlInfo_ = ControlInfoMap(std::move(ctrls), controls::controls); + metadataPlan_.set(controls::SensorTimestamp); + /* Initialize the camera properties. */ properties_ = sensor_->properties(); diff --git a/src/libcamera/pipeline/virtual/config_parser.cpp b/src/libcamera/pipeline/virtual/config_parser.cpp index 1d3d9ba87..608bbd469 100644 --- a/src/libcamera/pipeline/virtual/config_parser.cpp +++ b/src/libcamera/pipeline/virtual/config_parser.cpp @@ -65,6 +65,9 @@ ConfigParser::parseConfigFile(File &file, PipelineHandler *pipe) controls[&controls::draft::FaceDetectMode] = ControlInfo(supportedFaceDetectModes); data->controlInfo_ = ControlInfoMap(std::move(controls), controls::controls); + + data->metadataPlan_.set(controls::SensorTimestamp); + configurations.push_back(std::move(data)); } diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp index b31a374d8..407f1bca5 100644 --- a/src/libcamera/software_isp/software_isp.cpp +++ b/src/libcamera/software_isp/software_isp.cpp @@ -71,10 +71,11 @@ LOG_DEFINE_CATEGORY(SoftwareIsp) * \param[in] pipe The pipeline handler in use * \param[in] sensor Pointer to the CameraSensor instance owned by the pipeline * \param[out] ipaControls The IPA controls to update + * \param[out] metadataPlan The metadata plan to update * handler */ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor, - ControlInfoMap *ipaControls) + ControlInfoMap *ipaControls, MetadataListPlan *metadataPlan) : ispWorkerThread_("SWIspWorker"), dmaHeap_(DmaBufAllocator::DmaBufAllocatorFlag::CmaHeap | DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap | @@ -149,7 +150,8 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor, sensorInfo, sensor->controls(), ipaControls, - &ccmEnabled_); + &ccmEnabled_, + metadataPlan); if (ret) { LOG(SoftwareIsp, Error) << "IPA init failed"; debayer_.reset(); From patchwork Tue Jan 6 16:57:53 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25651 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 90762C32E7 for ; Tue, 6 Jan 2026 16:58:21 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D02F562002; Tue, 6 Jan 2026 17:58:19 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Jo1TzhNV"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 551FC61FE7 for ; Tue, 6 Jan 2026 17:58:03 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 451B32800; Tue, 6 Jan 2026 17:57:42 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1767718662; bh=zvJ2msDbWkuKhWI5EL3308A+BY/rq7hbpMaqWJZHpr8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Jo1TzhNVfw39d91OGCQ9cJBjIfTb1tU2lQ1HDVfhArkrXAflnk1+x+r9bZXCfp+jA ECTe2rslw7MusIjFxcK2NtWtCbBxLzzC7Gm+RDcj7+LJb6T8G8Qj1X0ohvUSxkjoXh 4ZGd52QJjTBy2hcAenyLJ65tXpb5nCwIsb+NdxFM= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder , Kieran Bingham Subject: [PATCH v4 21/22] libcamera: pipeline: Use `metadataAvailable()` Date: Tue, 6 Jan 2026 17:57:53 +0100 Message-ID: <20260106165754.1759831-22-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> References: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" From: Jacopo Mondi Use the newly introduced `metadataAvailable()` function to send metadata items to the application. Signed-off-by: Jacopo Mondi [Adjust commit message, adjust rpi changes.] Signed-off-by: Barnabás Pőcze Reviewed-by: Paul Elder Reviewed-by: Kieran Bingham --- changes in v2: * include rpi changes as well --- src/libcamera/pipeline/imx8-isi/imx8-isi.cpp | 5 +-- src/libcamera/pipeline/ipu3/ipu3.cpp | 14 ++++---- src/libcamera/pipeline/mali-c55/mali-c55.cpp | 2 +- src/libcamera/pipeline/rkisp1/rkisp1.cpp | 10 +++--- .../pipeline/rpi/common/pipeline_base.cpp | 36 +++++++++---------- src/libcamera/pipeline/simple/simple.cpp | 6 ++-- src/libcamera/pipeline/uvcvideo/uvcvideo.cpp | 3 +- src/libcamera/pipeline/vimc/vimc.cpp | 3 +- src/libcamera/pipeline/virtual/virtual.cpp | 2 +- 9 files changed, 39 insertions(+), 42 deletions(-) diff --git a/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp b/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp index b1111f19d..ff2c39c70 100644 --- a/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp +++ b/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp @@ -1128,10 +1128,7 @@ void PipelineHandlerISI::bufferReady(FrameBuffer *buffer) Request *request = buffer->request(); /* Record the sensor's timestamp in the request metadata. */ - ControlList &metadata = request->_d()->metadata(); - if (!metadata.contains(controls::SensorTimestamp.id())) - metadata.set(controls::SensorTimestamp, - buffer->metadata().timestamp); + metadataAvailable(request, controls::SensorTimestamp, buffer->metadata().timestamp); completeBuffer(request, buffer); if (request->hasPendingBuffers()) diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp index 04549899c..a44d3e7e4 100644 --- a/src/libcamera/pipeline/ipu3/ipu3.cpp +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp @@ -1254,7 +1254,7 @@ void IPU3CameraData::metadataReady(unsigned int id, const ControlList &metadata) return; Request *request = info->request; - request->_d()->metadata().merge(metadata); + pipe()->metadataAvailable(request, metadata); info->metadataProcessed = true; if (frameInfos_.tryComplete(info)) @@ -1281,12 +1281,14 @@ void IPU3CameraData::imguOutputBufferReady(FrameBuffer *buffer) pipe()->completeBuffer(request, buffer); - request->_d()->metadata().set(controls::draft::PipelineDepth, 3); + pipe()->metadataAvailable(request, controls::draft::PipelineDepth, 3); + /* \todo Actually apply the scaler crop region to the ImgU. */ const auto &scalerCrop = request->controls().get(controls::ScalerCrop); if (scalerCrop) cropRegion_ = *scalerCrop; - request->_d()->metadata().set(controls::ScalerCrop, cropRegion_); + + pipe()->metadataAvailable(request, controls::ScalerCrop, cropRegion_); if (frameInfos_.tryComplete(info)) pipe()->completeRequest(request); @@ -1326,8 +1328,7 @@ void IPU3CameraData::cio2BufferReady(FrameBuffer *buffer) * \todo The sensor timestamp should be better estimated by connecting * to the V4L2Device::frameStart signal. */ - request->_d()->metadata().set(controls::SensorTimestamp, - buffer->metadata().timestamp); + pipe()->metadataAvailable(request, controls::SensorTimestamp, buffer->metadata().timestamp); info->effectiveSensorControls = delayedCtrls_->get(buffer->metadata().sequence); @@ -1421,8 +1422,7 @@ void IPU3CameraData::frameStart(uint32_t sequence) return; } - request->_d()->metadata().set(controls::draft::TestPatternMode, - *testPatternMode); + pipe()->metadataAvailable(request, controls::draft::TestPatternMode, *testPatternMode); } REGISTER_PIPELINE_HANDLER(PipelineHandlerIPU3, "ipu3") diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp index f66615265..3e40be5f8 100644 --- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp @@ -1528,7 +1528,7 @@ void PipelineHandlerMaliC55::statsProcessed(unsigned int requestId, MaliC55FrameInfo &frameInfo = frameInfoMap_[requestId]; frameInfo.statsDone = true; - frameInfo.request->_d()->metadata().merge(metadata); + metadataAvailable(frameInfo.request, metadata); tryComplete(&frameInfo); } diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp index d2cd1d4b8..42a8da65b 100644 --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp @@ -507,7 +507,7 @@ void RkISP1CameraData::metadataReady(unsigned int frame, const ControlList &meta if (!info) return; - info->request->_d()->metadata().merge(metadata); + pipe()->metadataAvailable(info->request, metadata); info->metadataProcessed = true; pipe()->tryCompleteRequest(info); @@ -1645,8 +1645,7 @@ void PipelineHandlerRkISP1::imageBufferReady(FrameBuffer *buffer) * \todo The sensor timestamp should be better estimated by connecting * to the V4L2Device::frameStart signal. */ - request->_d()->metadata().set(controls::SensorTimestamp, - metadata.timestamp); + metadataAvailable(request, controls::SensorTimestamp, metadata.timestamp); if (isRaw_) { const ControlList &ctrls = @@ -1688,7 +1687,10 @@ void PipelineHandlerRkISP1::imageBufferReady(FrameBuffer *buffer) return; } - dewarper_->populateMetadata(&data->mainPathStream_, request->_d()->metadata()); + // FIXME: !!!! + ControlList meta; + dewarper_->populateMetadata(&data->mainPathStream_, meta); + metadataAvailable(request, meta); } void PipelineHandlerRkISP1::dewarpBufferReady(FrameBuffer *buffer) diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp index c50697877..f99d827b9 100644 --- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp @@ -1235,7 +1235,7 @@ void CameraData::metadataReady(const ControlList &metadata) /* Add to the Request metadata buffer what the IPA has provided. */ /* Last thing to do is to fill up the request metadata. */ Request *request = requestQueue_.front(); - request->_d()->metadata().merge(metadata); + pipe()->metadataAvailable(request, metadata); /* * Inform the sensor of the latest colour gains if it has the @@ -1505,24 +1505,24 @@ void CameraData::checkRequestCompleted() void CameraData::fillRequestMetadata(const ControlList &bufferControls, Request *request) { - if (auto x = bufferControls.get(controls::SensorTimestamp)) - request->_d()->metadata().set(controls::SensorTimestamp, *x); - if (auto x = bufferControls.get(controls::FrameWallClock)) - request->_d()->metadata().set(controls::FrameWallClock, *x); - - if (cropParams_.size()) { - std::vector crops; - - for (auto const &[k, v] : cropParams_) - crops.push_back(scaleIspCrop(v.ispCrop)); - - request->_d()->metadata().set(controls::ScalerCrop, crops[0]); - if (crops.size() > 1) { - request->_d()->metadata().set(controls::rpi::ScalerCrops, - Span(crops.data(), - crops.size())); + pipe()->metadataAvailable(request, [&](auto set) { + if (auto x = bufferControls.get(controls::SensorTimestamp)) + set(controls::SensorTimestamp, *x); + if (auto x = bufferControls.get(controls::FrameWallClock)) + set(controls::FrameWallClock, *x); + + if (cropParams_.size()) { + std::vector crops; + + for (auto const &[k, v] : cropParams_) + crops.push_back(scaleIspCrop(v.ispCrop)); + + set(controls::ScalerCrop, crops[0]); + + if (crops.size() > 1) + set(controls::rpi::ScalerCrops, { crops.data(), crops.size() }); } - } + }); } } /* namespace libcamera */ diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp index f1925cdf7..81e15f09c 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -940,8 +940,8 @@ void SimpleCameraData::imageBufferReady(FrameBuffer *buffer) } if (request) - request->_d()->metadata().set(controls::SensorTimestamp, - buffer->metadata().timestamp); + pipe->metadataAvailable(request, controls::SensorTimestamp, + buffer->metadata().timestamp); /* * Queue the captured and the request buffer to the converter or Software @@ -1036,7 +1036,7 @@ void SimpleCameraData::metadataReady(uint32_t frame, const ControlList &metadata if (!info) return; - info->request->_d()->metadata().merge(metadata); + pipe()->metadataAvailable(info->request, metadata); info->metadataProcessed = true; tryCompleteRequest(info->request); } diff --git a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp index fcd25cf4e..0b67e39bd 100644 --- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp +++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp @@ -897,8 +897,7 @@ void UVCCameraData::imageBufferReady(FrameBuffer *buffer) Request *request = buffer->request(); /* \todo Use the UVC metadata to calculate a more precise timestamp */ - request->_d()->metadata().set(controls::SensorTimestamp, - buffer->metadata().timestamp); + pipe()->metadataAvailable(request, controls::SensorTimestamp, buffer->metadata().timestamp); pipe()->completeBuffer(request, buffer); pipe()->completeRequest(request); diff --git a/src/libcamera/pipeline/vimc/vimc.cpp b/src/libcamera/pipeline/vimc/vimc.cpp index 182aa56e8..7adaa0cc8 100644 --- a/src/libcamera/pipeline/vimc/vimc.cpp +++ b/src/libcamera/pipeline/vimc/vimc.cpp @@ -620,8 +620,7 @@ void VimcCameraData::imageBufferReady(FrameBuffer *buffer) } /* Record the sensor's timestamp in the request metadata. */ - request->_d()->metadata().set(controls::SensorTimestamp, - buffer->metadata().timestamp); + pipe->metadataAvailable(request, controls::SensorTimestamp, buffer->metadata().timestamp); pipe->completeBuffer(request, buffer); pipe->completeRequest(request); diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp index 40c35264c..bb982a6c0 100644 --- a/src/libcamera/pipeline/virtual/virtual.cpp +++ b/src/libcamera/pipeline/virtual/virtual.cpp @@ -366,7 +366,7 @@ int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera, VirtualCameraData *data = cameraData(camera); const auto timestamp = currentTimestamp(); - request->_d()->metadata().set(controls::SensorTimestamp, timestamp); + metadataAvailable(request, controls::SensorTimestamp, timestamp); data->invokeMethod(&VirtualCameraData::processRequest, ConnectionTypeQueued, request); From patchwork Tue Jan 6 16:57:54 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25652 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 E5F94C32EA for ; Tue, 6 Jan 2026 16:58:21 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8997361FD8; Tue, 6 Jan 2026 17:58:20 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="WYIZcnJJ"; 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 E617861FA0 for ; Tue, 6 Jan 2026 17:58:03 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B5224F01; Tue, 6 Jan 2026 17:57:42 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1767718663; bh=MKRS+mbV0xAQWRTd1xmlBg2eQGM/fgI+I7K9liDd4hM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=WYIZcnJJOTd+FTCMeoV0gdzzely+XSJlqN7lwjUwU21y2cKcTCFkOFrk3AxzjrhQE v0pxCOL78K0slOtHmVgNBONvzd/wMDa8pA73MKnzjHVfhAnCR3YgmTH8E+SkhWoROp mLfQhELZ7IVSIZg8OLuo5WWYPWTa4bzEfKPJrMi0= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder Subject: [PATCH v4 22/22] libcamera: request: Swap the two metadata lists Date: Tue, 6 Jan 2026 17:57:54 +0100 Message-ID: <20260106165754.1759831-23-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> References: <20260106165754.1759831-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" Swap `metadata_` and `metadata2_`, so `MetadataList` is used as the primary metadata list of a request. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi Reviewed-by: Paul Elder --- include/libcamera/internal/pipeline_handler.h | 10 +++++----- include/libcamera/internal/request.h | 8 ++++---- include/libcamera/request.h | 4 ++-- src/android/camera_device.cpp | 2 +- src/apps/cam/camera_session.cpp | 7 +++---- src/apps/cam/file_sink.cpp | 2 +- src/apps/cam/file_sink.h | 4 ++-- src/apps/common/dng_writer.cpp | 2 +- src/apps/common/dng_writer.h | 4 ++-- src/apps/qcam/main_window.cpp | 2 +- src/apps/qcam/main_window.h | 3 ++- src/gstreamer/gstlibcamera-controls.cpp.in | 4 ++-- src/libcamera/pipeline_handler.cpp | 4 ++-- src/libcamera/request.cpp | 8 ++++---- src/py/libcamera/py_helpers.cpp | 4 ++-- src/py/libcamera/py_helpers.h | 2 +- src/py/libcamera/py_main.cpp | 2 +- 17 files changed, 36 insertions(+), 36 deletions(-) diff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h index 10206945d..60811ce33 100644 --- a/include/libcamera/internal/pipeline_handler.h +++ b/include/libcamera/internal/pipeline_handler.h @@ -71,11 +71,11 @@ public: { Request::Private *d = request->_d(); - auto &m = d->metadata2(); + auto &m = d->metadata(); const auto c = m.checkpoint(); std::ignore = m.set(ctrl, value); - d->metadata().set(ctrl, value); + d->metadata2().set(ctrl, value); const auto diff = c.diffSince(); if (diff) @@ -90,8 +90,8 @@ public: void operator()(const Control &ctrl, const internal::cxx20::type_identity_t &value) const { - d->metadata().set(ctrl, value); - std::ignore = d->metadata2().set(ctrl, value); + std::ignore = d->metadata().set(ctrl, value); + d->metadata2().set(ctrl, value); } }; @@ -102,7 +102,7 @@ public: void metadataAvailable(Request *request, Func func) { Request::Private *d = request->_d(); - const auto c = d->metadata2().checkpoint(); + const auto c = d->metadata().checkpoint(); std::invoke(func, MetadataSetter{ d }); diff --git a/include/libcamera/internal/request.h b/include/libcamera/internal/request.h index 3e10ae051..bd03f4e40 100644 --- a/include/libcamera/internal/request.h +++ b/include/libcamera/internal/request.h @@ -37,9 +37,9 @@ public: Camera *camera() const { return camera_; } bool hasPendingBuffers() const; - ControlList &metadata() { return metadata_; } + [[nodiscard]] MetadataList &metadata() { return metadata_; } #ifndef __DOXYGEN__ - [[nodiscard]] MetadataList &metadata2() { return metadata2_; } + ControlList &metadata2() { return metadata2_; } #endif bool completeBuffer(FrameBuffer *buffer); @@ -67,8 +67,8 @@ private: std::unordered_set pending_; std::map notifiers_; std::unique_ptr timer_; - ControlList metadata_; - MetadataList metadata2_; + MetadataList metadata_; + ControlList metadata2_; }; } /* namespace libcamera */ diff --git a/include/libcamera/request.h b/include/libcamera/request.h index 96755a655..fb7d1e55c 100644 --- a/include/libcamera/request.h +++ b/include/libcamera/request.h @@ -51,9 +51,9 @@ public: void reuse(ReuseFlag flags = Default); ControlList &controls() { return controls_; } - const ControlList &metadata() const; + [[nodiscard]] const MetadataList &metadata() const; #ifndef __DOXYGEN__ - [[nodiscard]] const MetadataList &metadata2() const; + const ControlList &metadata2() const; #endif const BufferMap &buffers() const { return bufferMap_; } diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp index 80ff248c2..fa0cf1d23 100644 --- a/src/android/camera_device.cpp +++ b/src/android/camera_device.cpp @@ -1423,7 +1423,7 @@ void CameraDevice::notifyError(uint32_t frameNumber, camera3_stream_t *stream, std::unique_ptr CameraDevice::getResultMetadata(const Camera3RequestDescriptor &descriptor) const { - const ControlList &metadata = descriptor.request_->metadata(); + const MetadataList &metadata = descriptor.request_->metadata(); const CameraMetadata &settings = descriptor.settings_; camera_metadata_ro_entry_t entry; bool found; diff --git a/src/apps/cam/camera_session.cpp b/src/apps/cam/camera_session.cpp index ba43220d1..e930c75ee 100644 --- a/src/apps/cam/camera_session.cpp +++ b/src/apps/cam/camera_session.cpp @@ -547,19 +547,18 @@ void CameraSession::processRequest(Request *request) std::cout << info.str() << std::endl; if (printMetadata_) { - const ControlList &requestMetadata = request->metadata(); + const MetadataList &requestMetadata = request->metadata(); std::cout << "Metadata (" << requestMetadata.size() << " entries):\n"; for (const auto &[key, value] : requestMetadata) { const ControlId *id = controls::controls.at(key); - std::cout << "\t" << id->name() << " = " - << value.toString() << std::endl; + std::cout << '\t' << id->name() << " = " << value << std::endl; } const auto &requestMetadata2 = request->metadata2(); std::cout << "Metadata2 (" << requestMetadata2.size() << " entries):\n"; for (const auto &[key, value] : requestMetadata2) { const ControlId *id = controls::controls.at(key); - std::cout << '\t' << id->name() << " = " << value << std::endl; + std::cout << '\t' << id->name() << " = " << value.toString() << std::endl; } } diff --git a/src/apps/cam/file_sink.cpp b/src/apps/cam/file_sink.cpp index 65794a2f9..7149d782c 100644 --- a/src/apps/cam/file_sink.cpp +++ b/src/apps/cam/file_sink.cpp @@ -102,7 +102,7 @@ bool FileSink::processRequest(Request *request) } void FileSink::writeBuffer(const Stream *stream, FrameBuffer *buffer, - [[maybe_unused]] const ControlList &metadata) + [[maybe_unused]] const MetadataList &metadata) { std::string filename = pattern_; size_t pos; diff --git a/src/apps/cam/file_sink.h b/src/apps/cam/file_sink.h index 26cd61b36..1f973f2e6 100644 --- a/src/apps/cam/file_sink.h +++ b/src/apps/cam/file_sink.h @@ -11,7 +11,7 @@ #include #include -#include +#include #include #include "frame_sink.h" @@ -44,7 +44,7 @@ private: void writeBuffer(const libcamera::Stream *stream, libcamera::FrameBuffer *buffer, - const libcamera::ControlList &metadata); + const libcamera::MetadataList &metadata); #ifdef HAVE_TIFF const libcamera::Camera *camera_; diff --git a/src/apps/common/dng_writer.cpp b/src/apps/common/dng_writer.cpp index 8d57023e1..431979564 100644 --- a/src/apps/common/dng_writer.cpp +++ b/src/apps/common/dng_writer.cpp @@ -521,7 +521,7 @@ const std::map formatInfo = { int DNGWriter::write(const char *filename, const Camera *camera, const StreamConfiguration &config, - const ControlList &metadata, + const MetadataList &metadata, [[maybe_unused]] const FrameBuffer *buffer, const void *data) { diff --git a/src/apps/common/dng_writer.h b/src/apps/common/dng_writer.h index aaa8a852b..741f78a75 100644 --- a/src/apps/common/dng_writer.h +++ b/src/apps/common/dng_writer.h @@ -10,8 +10,8 @@ #ifdef HAVE_TIFF #include -#include #include +#include #include class DNGWriter @@ -19,7 +19,7 @@ class DNGWriter public: static int write(const char *filename, const libcamera::Camera *camera, const libcamera::StreamConfiguration &config, - const libcamera::ControlList &metadata, + const libcamera::MetadataList &metadata, const libcamera::FrameBuffer *buffer, const void *data); }; diff --git a/src/apps/qcam/main_window.cpp b/src/apps/qcam/main_window.cpp index 96a2d5090..2a678c19a 100644 --- a/src/apps/qcam/main_window.cpp +++ b/src/apps/qcam/main_window.cpp @@ -646,7 +646,7 @@ void MainWindow::captureRaw() } void MainWindow::processRaw(FrameBuffer *buffer, - [[maybe_unused]] const ControlList &metadata) + [[maybe_unused]] const MetadataList &metadata) { #ifdef HAVE_TIFF QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); diff --git a/src/apps/qcam/main_window.h b/src/apps/qcam/main_window.h index 81fcf915a..278de1d44 100644 --- a/src/apps/qcam/main_window.h +++ b/src/apps/qcam/main_window.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -66,7 +67,7 @@ private Q_SLOTS: void saveImageAs(); void captureRaw(); void processRaw(libcamera::FrameBuffer *buffer, - const libcamera::ControlList &metadata); + const libcamera::MetadataList &metadata); void renderComplete(libcamera::FrameBuffer *buffer); diff --git a/src/gstreamer/gstlibcamera-controls.cpp.in b/src/gstreamer/gstlibcamera-controls.cpp.in index 6faf3ee7a..b42eade9b 100644 --- a/src/gstreamer/gstlibcamera-controls.cpp.in +++ b/src/gstreamer/gstlibcamera-controls.cpp.in @@ -282,6 +282,6 @@ void GstCameraControls::applyControls(std::unique_ptr &reque void GstCameraControls::readMetadata(libcamera::Request *request) { - controls_acc_.merge(request->metadata(), - ControlList::MergePolicy::OverwriteExisting); + for (const auto &[k, v] : request->metadata()) + controls_acc_.set(k, ControlValue(v)); } diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp index 1b9e18a04..afc9d09bd 100644 --- a/src/libcamera/pipeline_handler.cpp +++ b/src/libcamera/pipeline_handler.cpp @@ -572,9 +572,9 @@ void PipelineHandler::metadataAvailable(Request *request, const ControlList &met { Request::Private *d = request->_d(); - d->metadata().merge(metadata); + d->metadata2().merge(metadata); - const auto diff = d->metadata2().merge(metadata); + const auto diff = d->metadata().merge(metadata); if (!diff) LOG(Pipeline, Fatal) << "Tried to add incompatible metadata items"; diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp index 6d44c6749..57b294ec6 100644 --- a/src/libcamera/request.cpp +++ b/src/libcamera/request.cpp @@ -57,8 +57,8 @@ LOG_DEFINE_CATEGORY(Request) * \todo Add a validator for metadata controls. */ Request::Private::Private(Camera *camera) - : camera_(camera), cancelled_(false), metadata_(controls::controls), - metadata2_(camera->metadata()) + : camera_(camera), cancelled_(false), metadata_(camera->metadata()), + metadata2_(controls::controls) { } @@ -429,13 +429,13 @@ void Request::reuse(ReuseFlag flags) * \brief Retrieve the request's metadata * \return The a const reference to the metadata associated with the request */ -const ControlList &Request::metadata() const +const MetadataList &Request::metadata() const { return _d()->metadata_; } #ifndef __DOXYGEN__ -const MetadataList &Request::metadata2() const +const ControlList &Request::metadata2() const { return _d()->metadata2_; } diff --git a/src/py/libcamera/py_helpers.cpp b/src/py/libcamera/py_helpers.cpp index 8c55ef845..a994c90b7 100644 --- a/src/py/libcamera/py_helpers.cpp +++ b/src/py/libcamera/py_helpers.cpp @@ -16,7 +16,7 @@ namespace py = pybind11; using namespace libcamera; template -static py::object valueOrTuple(const ControlValue &cv) +static py::object valueOrTuple(const ControlValueView &cv) { if (cv.isArray()) { const T *v = reinterpret_cast(cv.data().data()); @@ -31,7 +31,7 @@ static py::object valueOrTuple(const ControlValue &cv) return py::cast(cv.get()); } -py::object controlValueToPy(const ControlValue &cv) +py::object controlValueToPy(const ControlValueView &cv) { switch (cv.type()) { case ControlTypeNone: diff --git a/src/py/libcamera/py_helpers.h b/src/py/libcamera/py_helpers.h index 983969dff..895006d0c 100644 --- a/src/py/libcamera/py_helpers.h +++ b/src/py/libcamera/py_helpers.h @@ -9,5 +9,5 @@ #include -pybind11::object controlValueToPy(const libcamera::ControlValue &cv); +pybind11::object controlValueToPy(const libcamera::ControlValueView &cv); libcamera::ControlValue pyToControlValue(const pybind11::object &ob, libcamera::ControlType type); diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp index a983ea75c..77e0ead68 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -466,7 +466,7 @@ PYBIND11_MODULE(_libcamera, m) self.controls().set(id.id(), pyToControlValue(value, id.type())); }) .def_property_readonly("metadata", [](Request &self) { - /* Convert ControlList to std container */ + /* Convert MetadataList to std container */ std::unordered_map ret;