From patchwork Thu Oct 30 16:57:55 2025 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: 24904 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 80F36C3259 for ; Thu, 30 Oct 2025 16:58:27 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E0E3D60921; Thu, 30 Oct 2025 17:58:22 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="omO5JSUl"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id BFFFD608C8 for ; Thu, 30 Oct 2025 17:58:20 +0100 (CET) Received: from pb-laptop.local (185.221.140.239.nat.pool.zt.hu [185.221.140.239]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 9508C15D2; Thu, 30 Oct 2025 17:56:30 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761843390; bh=BbJSD/7oCP/mj9yqYWmwjFpiEwHBJuUFMJdxNysWtb8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=omO5JSUlxdicUvln5sQGkjJKUVa9LV+mYibRnL5CkdFavHShRHvM8lhhRQ3+sFzbz lfbKq1Ai1/IehBi2V6vlQMscznKPaJDI9iCZf8l+zuBL0AuOhM9CgGLPUzCWwoZSg2 mFkHhkuc0URVfMUsk+hp85YnCDSuMEZ5N9q0oiMo= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Isaac Scott , Paul Elder Subject: [RFC PATCH v3 01/22] libcamera: controls: Strings are arrays Date: Thu, 30 Oct 2025 17:57:55 +0100 Message-ID: <20251030165816.1095180-2-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251030165816.1095180-1-barnabas.pocze@ideasonboard.com> References: <20251030165816.1095180-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 c970e4b7b8..1ee1971c4d 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 Thu Oct 30 16:57:56 2025 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: 24905 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 C99D1C3259 for ; Thu, 30 Oct 2025 16:58:29 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5E22260949; Thu, 30 Oct 2025 17:58:25 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="jxmHhF9K"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 19068603ED for ; Thu, 30 Oct 2025 17:58:21 +0100 (CET) Received: from pb-laptop.local (185.221.140.239.nat.pool.zt.hu [185.221.140.239]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id F13E96F3; Thu, 30 Oct 2025 17:56:30 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761843391; bh=hfdUOWJkDvb6OiI1O3UnghTjch/xz3Ns7oYFJJNscZ8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=jxmHhF9Kc9JyXipqZ5A1epj0KJumkEPzv1mCndCh2iG6RZwo3qBtS6IEK+ZwjxhSd nGnRrVbuTEqj0zsipr+gLkMTVBywAEeUON2ypS28KRBTjjkl+TVtpUEwOF5X6XrrDQ GCnBVUe2qRT39WmCjrGPaQi2L6bCwAPNS++XF8oU= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Paul Elder Subject: [RFC PATCH v3 02/22] libcamera: controls: Add `ControlValueView` Date: Thu, 30 Oct 2025 17:57:56 +0100 Message-ID: <20251030165816.1095180-3-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251030165816.1095180-1-barnabas.pocze@ideasonboard.com> References: <20251030165816.1095180-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 | 268 +++++++++++++++++++++++++---------- 2 files changed, 258 insertions(+), 78 deletions(-) diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h index 1ee1971c4d..4af52b4f7d 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 1e1b49e6bd..222030c434 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,185 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen storage_ = reinterpret_cast(new uint8_t[newSize]); } +/** + * \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 Thu Oct 30 16:57:57 2025 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: 24906 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 08962C32CE for ; Thu, 30 Oct 2025 16:58:31 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id BFA7A60978; Thu, 30 Oct 2025 17:58:26 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="LdkV+bJ/"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 561AA608CF for ; Thu, 30 Oct 2025 17:58:21 +0100 (CET) Received: from pb-laptop.local (185.221.140.239.nat.pool.zt.hu [185.221.140.239]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 4D95D18E5 for ; Thu, 30 Oct 2025 17:56:31 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761843391; bh=7vV2KaBt0kQaOTQ1wtzhRF7gjU+bgTGiSQA7JdtrZAg=; h=From:To:Subject:Date:In-Reply-To:References:From; b=LdkV+bJ/5XgKWXeZJfjCArdiNjvSBAfBS++6mwI8wQsCDAcNQos73Q4gnVEcbT4/7 WHo98tuWTGFMeawR4ILu8aMtuGkF4OJA+2J59vmE5Zeujs22dI/+xeURpseMefxq4+ e4m9fF+kLTVm4PnZDY4NuuLLq9/fnqyroKCoVzUQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 03/22] libcamera: base: Add file for C++20 polyfills Date: Thu, 30 Oct 2025 17:57:57 +0100 Message-ID: <20251030165816.1095180-4-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251030165816.1095180-1-barnabas.pocze@ideasonboard.com> References: <20251030165816.1095180-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 0000000000..70a43f83c9 --- /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 f28ae4d42a..ee42c9910f 100644 --- a/include/libcamera/base/meson.build +++ b/include/libcamera/base/meson.build @@ -31,10 +31,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 Thu Oct 30 16:57:58 2025 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: 24907 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 8A3F5C32D4 for ; Thu, 30 Oct 2025 16:58:33 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6CB0B60990; Thu, 30 Oct 2025 17:58:28 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="b+QMKywT"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id A97DB608DC for ; Thu, 30 Oct 2025 17:58:21 +0100 (CET) Received: from pb-laptop.local (185.221.140.239.nat.pool.zt.hu [185.221.140.239]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8A62D1E33; Thu, 30 Oct 2025 17:56:31 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761843391; bh=WtclvwcEGVeNjqk9NUASUu+f9QRky4OsOLH0IVX3Lns=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=b+QMKywT2PTtroazp8hhsNWYJxSd57XuxXiFuTNtaWWWt6nnHXzHoe0ZpSNUWLA4Z zRqu+uvzJItc0mj8F/odD8DCVHNFnKkvzx0Er2xmOf1mFIrs3MMrrloc6IL0Ds9BW4 WOrPA5ns5Hf6DOgUQGW6KjJ+aQYYMtGcIC6bmUPM= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder Subject: [RFC PATCH v3 04/22] libcamera: base: cxx20: Add `type_identity{, _t}` Date: Thu, 30 Oct 2025 17:57:58 +0100 Message-ID: <20251030165816.1095180-5-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251030165816.1095180-1-barnabas.pocze@ideasonboard.com> References: <20251030165816.1095180-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 70a43f83c9..1f4caf56f8 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 Thu Oct 30 16:57:59 2025 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: 24908 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 15D33C3259 for ; Thu, 30 Oct 2025 16:58:35 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 828DE608F4; Thu, 30 Oct 2025 17:58:29 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="I6ClU1co"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0CC26608DD for ; Thu, 30 Oct 2025 17:58:22 +0100 (CET) Received: from pb-laptop.local (185.221.140.239.nat.pool.zt.hu [185.221.140.239]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DDE2218E5; Thu, 30 Oct 2025 17:56:31 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761843392; bh=HciZSJ8c9rrJbGOx9JzK+X8ACBgX7TlUyIG8STiQ8As=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=I6ClU1cor3yL5GN0TOgC+N4PzT3L0hWZyvBN4O9rvJ3hA6X1z6x2fxvdlhEaCRYFG 2US9fOcspPUHmR2GrW4pndlkFvb0z+kw173chh/tw5iuCQJev/l86d8mJPAVhc1Q8H TN/G2ntXlXE6EAaHtLY0VlHoAzNBKjlIOS6ROxnQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Paul Elder Subject: [RFC PATCH v3 05/22] libcamera: base: cxx20: Add `has_single_bit()` Date: Thu, 30 Oct 2025 17:57:59 +0100 Message-ID: <20251030165816.1095180-6-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251030165816.1095180-1-barnabas.pocze@ideasonboard.com> References: <20251030165816.1095180-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 1f4caf56f8..d47168a70a 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 Thu Oct 30 16:58:00 2025 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: 24909 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 5D928C32CE for ; Thu, 30 Oct 2025 16:58:36 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 913F0609B8; Thu, 30 Oct 2025 17:58:30 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="WhF7h9H1"; 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 7699F608FE for ; Thu, 30 Oct 2025 17:58:22 +0100 (CET) Received: from pb-laptop.local (185.221.140.239.nat.pool.zt.hu [185.221.140.239]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 35B4915D2; Thu, 30 Oct 2025 17:56:32 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761843392; bh=uzkL5Fxz7WOvjGEDWesm1VWIovsBhcpyyMDr+NwGhJQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=WhF7h9H1P1x3bc2OLsqixb+ko5ZAZZP4ntVvkacQS+Kn+DjSCz/ZUmmtITL9VbFY9 gMyrMn6ZJnEpv3Uya3MUVT4kud9KdFUmaZsOKAInAQh0ZG5t3RZtetvf8fUZgLOvpF QVv4eCQ6jabbbEZgCgt+ngPPK8nMh1rhiqy5AM00= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Paul Elder Subject: [RFC PATCH v3 06/22] libcamera: base: Add alignment utility functions Date: Thu, 30 Oct 2025 17:58:00 +0100 Message-ID: <20251030165816.1095180-7-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251030165816.1095180-1-barnabas.pocze@ideasonboard.com> References: <20251030165816.1095180-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 0000000000..d8ee4e3695 --- /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 ee42c9910f..723f42b362 100644 --- a/include/libcamera/base/meson.build +++ b/include/libcamera/base/meson.build @@ -33,6 +33,7 @@ libcamera_base_private_headers = files([ libcamera_base_internal_headers = files([ 'internal/cxx20.h', + 'internal/align.h', ]) libcamera_base_headers = [ From patchwork Thu Oct 30 16:58:01 2025 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: 24910 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 B6B4FC32D4 for ; Thu, 30 Oct 2025 16:58:37 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A1AA4609C0; Thu, 30 Oct 2025 17:58:34 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="WaAfcpD3"; 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 C00E6608D4 for ; Thu, 30 Oct 2025 17:58:22 +0100 (CET) Received: from pb-laptop.local (185.221.140.239.nat.pool.zt.hu [185.221.140.239]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A1C736F3 for ; Thu, 30 Oct 2025 17:56:32 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761843392; bh=s2gmV+ER3TiRpX/qFuzyC8aC84ErMIBQKWuLLxX4Pws=; h=From:To:Subject:Date:In-Reply-To:References:From; b=WaAfcpD3GDb6jzztNrpS8ZXUWa/HPUfaujDH1JyvxQHmAAHYfs6wGAeKNrGh9v9Lv Hwou5DKiMwXSaZT26DYyEcpOAQ5UkKlU2KnYbt5jjg3qQMNl5GQlPzj6AjxlamQMIE UbqJHwohm8nbQ8cxGmT6mbK9ipEpUP0C19OIgQ90= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 07/22] libcamera: Add `MetadataList` Date: Thu, 30 Oct 2025 17:58:01 +0100 Message-ID: <20251030165816.1095180-8-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251030165816.1095180-1-barnabas.pocze@ideasonboard.com> References: <20251030165816.1095180-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 30ea76f947..410b548dde 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 0000000000..9366dcbc3a --- /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 0000000000..8f058e5c0d --- /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 5b9b86f211..7fd806ae78 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 0000000000..5a5114fc73 --- /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 763f8905e7..ff635454b3 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 0000000000..b0eddde436 --- /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 0000000000..66b31136e4 --- /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 Thu Oct 30 16:58:02 2025 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: 24911 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 2959FC32DB for ; Thu, 30 Oct 2025 16:58:39 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A5B7760990; Thu, 30 Oct 2025 17:58:36 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="FMPuXtqg"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1BA696092C for ; Thu, 30 Oct 2025 17:58:23 +0100 (CET) Received: from pb-laptop.local (185.221.140.239.nat.pool.zt.hu [185.221.140.239]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id F35CD1E33; Thu, 30 Oct 2025 17:56:32 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761843393; bh=5JE7w9KDQzJDEvzFyD4a9cHhBREUvFFBvGaGuJiEsaA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=FMPuXtqgEq6oBgmGTFL+KX8RxCMToQqaajzocNYj8yJTZjrrE5V3FgbVG7ykKlavH 4btkncn48KJVk6UmMUQ1p6+FmBz6/VMwh7WzQV5QDUy1pS/nw6KrV0EoxHkFrOBIMs gnnm/S/ftG1wg2P2RkdF5mEG+28O7kSLeS3vkhjs= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Paul Elder Subject: [RFC PATCH v3 08/22] Documentation: design: Document `MetadataList` Date: Thu, 30 Oct 2025 17:58:02 +0100 Message-ID: <20251030165816.1095180-9-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251030165816.1095180-1-barnabas.pocze@ideasonboard.com> References: <20251030165816.1095180-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 | 266 +++++++++++++++++++++++++ Documentation/index.rst | 3 +- Documentation/meson.build | 1 + 3 files changed, 268 insertions(+), 2 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 0000000000..f668c7c96f --- /dev/null +++ b/Documentation/design/metadata-list.rst @@ -0,0 +1,266 @@ +.. 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 :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` +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 item. + +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. + +TODO: is a forward iterator enough? is a bidirectional iterator needed? + +.. _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 febd4e13e6..cd76546dc0 100644 --- a/Documentation/index.rst +++ b/Documentation/index.rst @@ -26,6 +26,7 @@ SoftwareISP Benchmarking Tracing guide Design document: AE + Design document: Metadata list Internal API .. toctree:: @@ -34,5 +35,3 @@ Lens driver requirements Sensor driver requirements - - diff --git a/Documentation/meson.build b/Documentation/meson.build index fe53ebcf7b..4104f96645 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', From patchwork Thu Oct 30 16:58:03 2025 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: 24912 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 441ABC3259 for ; Thu, 30 Oct 2025 16:58:40 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C477D60947; Thu, 30 Oct 2025 17:58:38 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="TgkOXX2t"; 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 6F867608F7 for ; Thu, 30 Oct 2025 17:58:23 +0100 (CET) Received: from pb-laptop.local (185.221.140.239.nat.pool.zt.hu [185.221.140.239]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 453586F3; Thu, 30 Oct 2025 17:56:33 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761843393; bh=ocNC7npJuj3ARUqDCiwjK0p9zYAy2CtsBWIWpgl9Tds=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=TgkOXX2tMd48lbyI+H4nUvbOYeK7d7gq4YbWNRCBiWAYx5Ovws8brIatxL7OBxQ6o W1pWLMhFM3QEtp6oep/MjQxXfpZOoEhx8P7E3x7gERTOem/elx3It/fI1I1lXLJBcp RvTz2MlyjrYjB5AhSyS8HIFSgQURJzX9a2waIfUU= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder Subject: [RFC PATCH v3 09/22] libcamera: ipa_data_serializer: Support `MetadataListPlan` Date: Thu, 30 Oct 2025 17:58:03 +0100 Message-ID: <20251030165816.1095180-10-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251030165816.1095180-1-barnabas.pocze@ideasonboard.com> References: <20251030165816.1095180-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 bce7972458..754e4065cb 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 0537f785b1..5de99b58f2 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 93f988cd9f..d4468e1762 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 Thu Oct 30 16:58:04 2025 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: 24913 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 180E7C3331 for ; Thu, 30 Oct 2025 16:58:41 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id AE13D60967; Thu, 30 Oct 2025 17:58:39 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="SeuW4F1c"; 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 B73E060934 for ; Thu, 30 Oct 2025 17:58:23 +0100 (CET) Received: from pb-laptop.local (185.221.140.239.nat.pool.zt.hu [185.221.140.239]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 9915B15D2; Thu, 30 Oct 2025 17:56:33 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761843393; bh=IkO03SFMU6hCRMG1ItK9eiBRksIT39onlC8odA8a5dw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=SeuW4F1cBT5jVv5LcYAlhJg0Soc7/bv93wk+df02deCyKn/+zNh5ZjRfkhlds70Fu i4IupyJTvZOYxJl3wlGf0nNCZ7grlwSpBMO7um3nxsLked5XNhrdHLkXrygWYX2V7S UKdX824ElC5yC3vcEjLl+DdYxUFy6OSfcSnRNDX4= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder Subject: [RFC PATCH v3 10/22] libcamera: camera: Store `MetadataListPlan` in `Camera::Private` Date: Thu, 30 Oct 2025 17:58:04 +0100 Message-ID: <20251030165816.1095180-11-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251030165816.1095180-1-barnabas.pocze@ideasonboard.com> References: <20251030165816.1095180-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 b24a297400..53089282a4 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 8a2e9ed589..016c1ae512 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 2e1e146a25..7bfa50b52c 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 Thu Oct 30 16:58:05 2025 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: 24914 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 01D93C32CE for ; Thu, 30 Oct 2025 16:58:41 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 906EE6098E; Thu, 30 Oct 2025 17:58:40 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ZE2GtRmi"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1A7EA60947 for ; Thu, 30 Oct 2025 17:58:24 +0100 (CET) Received: from pb-laptop.local (185.221.140.239.nat.pool.zt.hu [185.221.140.239]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E993918E5; Thu, 30 Oct 2025 17:56:33 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761843394; bh=SuOPzvKn2H326C+sircC2A8eH3QDwVNrhhGo1tYDfeQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ZE2GtRmi+job4rNhcPSbjfGjiUBvRdKWHm7EbM5Lx6xaBywkb6do5325slZBKQXai myytAWtN+Zhnt24KTnHwE/P5FVLAizn98ffeyo29rbautMSX6uhcIuUk+io7NPA0NU M/K7nwTIvVTXhSERH+Li69/S0+7lGap5d6Xy/jqo= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder Subject: [RFC PATCH v3 11/22] libcamera: request: Store `MetadataList` Date: Thu, 30 Oct 2025 17:58:05 +0100 Message-ID: <20251030165816.1095180-12-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251030165816.1095180-1-barnabas.pocze@ideasonboard.com> References: <20251030165816.1095180-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/request.h | 5 +++++ src/libcamera/request.cpp | 2 ++ 2 files changed, 7 insertions(+) diff --git a/include/libcamera/request.h b/include/libcamera/request.h index 0c5939f7b3..23e7bde72c 100644 --- a/include/libcamera/request.h +++ b/include/libcamera/request.h @@ -18,6 +18,7 @@ #include #include +#include namespace libcamera { @@ -51,6 +52,9 @@ public: ControlList &controls() { return *controls_; } ControlList &metadata() { return *metadata_; } +#ifndef __DOXYGEN__ + [[nodiscard]] MetadataList &metadata2() { return metadata2_; } +#endif const BufferMap &buffers() const { return bufferMap_; } int addBuffer(const Stream *stream, FrameBuffer *buffer, std::unique_ptr &&fence = {}); @@ -69,6 +73,7 @@ private: ControlList *controls_; ControlList *metadata_; + MetadataList metadata2_; BufferMap bufferMap_; const uint64_t cookie_; diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp index 992d476e09..0ff465e6f7 100644 --- a/src/libcamera/request.cpp +++ b/src/libcamera/request.cpp @@ -357,6 +357,7 @@ void Request::Private::timeout() */ Request::Request(Camera *camera, uint64_t cookie) : Extensible(std::make_unique(camera)), + metadata2_(camera->_d()->metadataPlan_), cookie_(cookie), status_(RequestPending) { controls_ = new ControlList(controls::controls, @@ -410,6 +411,7 @@ void Request::reuse(ReuseFlag flags) controls_->clear(); metadata_->clear(); + metadata2_.clear(); } /** From patchwork Thu Oct 30 16:58:06 2025 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: 24915 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 134F7C3332 for ; Thu, 30 Oct 2025 16:58:43 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6559660A7A; Thu, 30 Oct 2025 17:58:41 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="S0U7pgV2"; 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 5824E608D9 for ; Thu, 30 Oct 2025 17:58:24 +0100 (CET) Received: from pb-laptop.local (185.221.140.239.nat.pool.zt.hu [185.221.140.239]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 452C71E33 for ; Thu, 30 Oct 2025 17:56:34 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761843394; bh=fVyTzTg2dafxpL1k2PuEDr32Z8EkOdBxf1+x/Tt/jZs=; h=From:To:Subject:Date:In-Reply-To:References:From; b=S0U7pgV2bztig15Aj9l9cOEvAjQVXqoHx975MOFQk5xKykDBFlANwZZBeV5G8JllQ FPKZhtGFmJ1WZ2/o75++pPswnC97RFbLZ0UAxWUWq/WsfOFJXVCD6obGbB0djUte3C lyDSDWBlD/QBCgoS0SfEOLn/KN5dX9SZDHkHtl98= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 12/22] [DNI] apps: cam: Print `MetadataListPlan` of camera Date: Thu, 30 Oct 2025 17:58:06 +0100 Message-ID: <20251030165816.1095180-13-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251030165816.1095180-1-barnabas.pocze@ideasonboard.com> References: <20251030165816.1095180-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. Reviewed-by: Kieran Bingham --- 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 1596a25a3a..0aef3128f2 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 Thu Oct 30 16:58:07 2025 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: 24916 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 9A4ADC3333 for ; Thu, 30 Oct 2025 16:58:43 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 920B160990; Thu, 30 Oct 2025 17:58:42 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="O3dMLkSY"; 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 90D8F608CF for ; Thu, 30 Oct 2025 17:58:24 +0100 (CET) Received: from pb-laptop.local (185.221.140.239.nat.pool.zt.hu [185.221.140.239]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 82FBD6F3 for ; Thu, 30 Oct 2025 17:56:34 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761843394; bh=Xmti/TvZIYgfsUaceRsdKI359qygUCDpPRng/9aNt1M=; h=From:To:Subject:Date:In-Reply-To:References:From; b=O3dMLkSYHhnc6/c1HDDth0lqB5r1WCqegkOZfsFXleITNLifrBgjxUxr9s+W0eZz8 8l9zCGPKtpXdxWJu7uikU+H8LivhKQ1S7Ffhiq6x+9bEELn8OeZKturYwScCIb4bbt uQgFEjL8xVbrT1UVnzyvQ4j1zCbqHj0OmBepGgHk= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 13/22] [DNI] apps: cam: Print `MetadataList` of `Request` as well Date: Thu, 30 Oct 2025 17:58:07 +0100 Message-ID: <20251030165816.1095180-14-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251030165816.1095180-1-barnabas.pocze@ideasonboard.com> References: <20251030165816.1095180-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 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/apps/cam/camera_session.cpp b/src/apps/cam/camera_session.cpp index 0aef3128f2..ecb1b50b9c 100644 --- a/src/apps/cam/camera_session.cpp +++ b/src/apps/cam/camera_session.cpp @@ -535,11 +535,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 Thu Oct 30 16:58:08 2025 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: 24917 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 AD147C32D4 for ; Thu, 30 Oct 2025 16:58:44 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5E54160A8B; Thu, 30 Oct 2025 17:58:44 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="JDSNMdEJ"; 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 E0FEF608DE for ; Thu, 30 Oct 2025 17:58:24 +0100 (CET) Received: from pb-laptop.local (185.221.140.239.nat.pool.zt.hu [185.221.140.239]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C46761E33; Thu, 30 Oct 2025 17:56:34 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761843394; bh=k6ZRoM30ybTKu+WVfG0DyOfg43MBzqbd912GFVWQjkY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=JDSNMdEJgntC51lXf2pA+mi/qIPBvfIgKRrmuM40pRSbkbFb9V29BQaIhNR6qJhBU QM10iBx2dXKzldXd1k62o3XojEdxgL87H7IFi+2lV/CLYhTb1LJI6ylNqqy3Vj3JwW cKt72jF7AROseW7bKdb8sZmBkOZ/WBIOzqqikzMw= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder Subject: [RFC PATCH v3 14/22] libcamera: camera: Introduce metadataAvailable signal Date: Thu, 30 Oct 2025 17:58:08 +0100 Message-ID: <20251030165816.1095180-15-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251030165816.1095180-1-barnabas.pocze@ideasonboard.com> References: <20251030165816.1095180-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/ --- include/libcamera/camera.h | 1 + src/libcamera/camera.cpp | 54 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h index 53089282a4..53f0485bc3 100644 --- a/include/libcamera/camera.h +++ b/include/libcamera/camera.h @@ -123,6 +123,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 7bfa50b52c..a6d331a03c 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. + * + * Application 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 From patchwork Thu Oct 30 16:58:09 2025 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: 24919 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 12F02C3334 for ; Thu, 30 Oct 2025 16:58:47 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id AD38F609DC; Thu, 30 Oct 2025 17:58:46 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="XBSLleRO"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3A0E5608F5 for ; Thu, 30 Oct 2025 17:58:25 +0100 (CET) Received: from pb-laptop.local (185.221.140.239.nat.pool.zt.hu [185.221.140.239]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 2174D6F3; Thu, 30 Oct 2025 17:56:35 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761843395; bh=uyiGdGvWow9KOc5Sy+G/2EdD5abElQRSJ2vENI1m0c0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=XBSLleROLStdi2cxbNMrZl/KSAekbEb672nWKAc4Dvpf2I4NipIi7Mx1VCb6couXT SV0XADL2tZj//BA4zpeg9/dRuS32Y++MA0JJA5JvFxm8SVPdFXnPPkdG2m+8p9yKyJ k9a1+14hiFwk4x5hx0AwdhjXgeSO9snzHsjmOCUo= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v3 15/22] guides: application: Document Camera::metadataAvailable Date: Thu, 30 Oct 2025 17:58:09 +0100 Message-ID: <20251030165816.1095180-16-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251030165816.1095180-1-barnabas.pocze@ideasonboard.com> References: <20251030165816.1095180-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 06c07d1e94..166919cf62 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 allows application 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 Thu Oct 30 16:58:10 2025 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: 24918 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 D538BC32DB for ; Thu, 30 Oct 2025 16:58:45 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 64F0D609C0; Thu, 30 Oct 2025 17:58:45 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="XJLaR8LW"; 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 881B460970 for ; Thu, 30 Oct 2025 17:58:25 +0100 (CET) Received: from pb-laptop.local (185.221.140.239.nat.pool.zt.hu [185.221.140.239]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6911015D2; Thu, 30 Oct 2025 17:56:35 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761843395; bh=nU41733PuEdREmE7GxQzkvJHHKYKMBoH+TFSi17v33c=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=XJLaR8LWsn5JkWdIBRWjvtzi3LXu89ZU6yMh2LBRRfYSkIfmnwDmOcLZ12RyrMpR6 NvhfSAyltMTy6T0r6a0jbOZSM/NDPiPL+aaKLvAwbK8ux6GRhfr1zXwJXz/ayIQfP+ n7T6rv8MK2x8VzWD9dx+4QwGqkayqrP8ZWK2rnN0= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder Subject: [RFC PATCH v3 16/22] libcamera: pipeline_handler: Add metadataAvailable() function Date: Thu, 30 Oct 2025 17:58:10 +0100 Message-ID: <20251030165816.1095180-17-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251030165816.1095180-1-barnabas.pocze@ideasonboard.com> References: <20251030165816.1095180-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 | 49 ++++++++++ src/libcamera/pipeline_handler.cpp | 90 +++++++++++++++++++ 2 files changed, 139 insertions(+) diff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h index e89d6a33e3..6c0e546e67 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,50 @@ 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) + { + auto &m = request->metadata2(); + const auto c = m.checkpoint(); + + std::ignore = m.set(ctrl, value); + request->metadata().set(ctrl, value); + + const auto d = c.diffSince(); + if (d) + request->_d()->camera()->metadataAvailable.emit(request, d); + } + +#ifndef __DOXYGEN__ + struct MetadataSetter { + Request *request; + + template + void operator()(const Control &ctrl, + const internal::cxx20::type_identity_t &value) const + { + request->metadata().set(ctrl, value); + std::ignore = request->metadata2().set(ctrl, value); + } + }; + + template> * = nullptr> +#else + template +#endif + void metadataAvailable(Request *request, Func func) + { + const auto c = request->metadata2().checkpoint(); + + std::invoke(func, MetadataSetter{ request }); + + if (const auto d = c.diffSince()) + request->_d()->camera()->metadataAvailable.emit(request, d); + } + 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 e5f9e55c97..de98a93306 100644 --- a/src/libcamera/pipeline_handler.cpp +++ b/src/libcamera/pipeline_handler.cpp @@ -532,6 +532,96 @@ 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 metadata + * items already present in Request::metadata() are ignored. 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->metadata().merge(metadata); + + const auto d = request->metadata2().merge(metadata); + if (!d) + LOG(Pipeline, Fatal) << "Tried to add incompatible metadata items"; + + if (!d->empty()) + request->_d()->camera()->metadataAvailable.emit(request, *d); +} + +/** + * \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 servers 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 Thu Oct 30 16:58:11 2025 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: 24921 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 77DEBC3335 for ; Thu, 30 Oct 2025 16:58:49 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id EEC496098A; Thu, 30 Oct 2025 17:58:48 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Fsw6TiOz"; 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 D629460971 for ; Thu, 30 Oct 2025 17:58:25 +0100 (CET) Received: from pb-laptop.local (185.221.140.239.nat.pool.zt.hu [185.221.140.239]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id BB2C51FDD; Thu, 30 Oct 2025 17:56:35 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761843395; bh=fNow8kYY66Y88AJgtTvr3Ua//1UnW2NR2OS57QMmAWc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Fsw6TiOzk8G8VzmEaqKQxXz02EHypUHlYveNBsXoqPzowW6AZJtrjSC9WFlUoTJr6 f6UBkJSEPYBE7x7rdXgEIuvAaFxB0tp6p0sdlNV0LrdFVkaq2TT7j+zscSHj39dh4t /yopVpCe96B9swoiciiZV1pzWkdZukiGQTX45fJc= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v3 17/22] guides: pipeline_handler: Document PipelineHandler::metadataAvailable Date: Thu, 30 Oct 2025 17:58:11 +0100 Message-ID: <20251030165816.1095180-18-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251030165816.1095180-1-barnabas.pocze@ideasonboard.com> References: <20251030165816.1095180-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 85d9cc8700..9ec0da01b9 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 Thu Oct 30 16:58:12 2025 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: 24920 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 38954C3259 for ; Thu, 30 Oct 2025 16:58:48 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D9200609DE; Thu, 30 Oct 2025 17:58:47 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="NCkoWdGO"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2540460904 for ; Thu, 30 Oct 2025 17:58:26 +0100 (CET) Received: from pb-laptop.local (185.221.140.239.nat.pool.zt.hu [185.221.140.239]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 0D4634E59; Thu, 30 Oct 2025 17:56:36 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761843396; bh=H4Ns2syOMynZatVCN+UZQ5Eqz3d2/Rqd7i1oEWci6nU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=NCkoWdGOL5f0TAOeN29BfDDrm4XxN5J1W7BHpb8uKFOi/jQRiMYB01UNsKPX/9bKr Logvzl1rIS8+ZvuS9tZk7ZvKT8EiK3CV6u9u6iQNxBMFxRqoLLWdriMJyvYwH/CXXM n8hvZveWIiSeO2/Ug2nLgQmxke5wQhoDRk35r7+s= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v3 18/22] [DNI] apps: cam: Use Camera::metadataAvailable signal Date: Thu, 30 Oct 2025 17:58:12 +0100 Message-ID: <20251030165816.1095180-19-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251030165816.1095180-1-barnabas.pocze@ideasonboard.com> References: <20251030165816.1095180-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 ecb1b50b9c..7cd8772e0a 100644 --- a/src/apps/cam/camera_session.cpp +++ b/src/apps/cam/camera_session.cpp @@ -310,6 +310,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 Thu Oct 30 16:58:13 2025 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: 24924 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 2E8D0C3336 for ; Thu, 30 Oct 2025 16:58:53 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A44ED609B3; Thu, 30 Oct 2025 17:58:52 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="P+MLy6S9"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0A2EF6097B for ; Thu, 30 Oct 2025 17:58:27 +0100 (CET) Received: from pb-laptop.local (185.221.140.239.nat.pool.zt.hu [185.221.140.239]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6957F4E5B; Thu, 30 Oct 2025 17:56:36 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761843397; bh=9QB9kHA1zUZ+FIzDCzw0hB32seKsXL9zKorcZNi8fBA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=P+MLy6S9al4FoM80e5QVxRlUYvFbjqP8I7jWKfpttBnyr8wHPvGZ7wc/EUgxP/VXY L5ucpf6oUlyI1/8IuD6pXDO+lFicYldtIvSlGTp53XcM7JzJJy/k4XzjYSKhHmfsm0 DLghoA8C7CSjwrPaFU/7AA2ztpnGMpNO/LfXEv/I= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder Subject: [RFC PATCH v3 19/22] libcamera: pipeline_handler: Inject "debug" metadata Date: Thu, 30 Oct 2025 17:58:13 +0100 Message-ID: <20251030165816.1095180-20-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251030165816.1095180-1-barnabas.pocze@ideasonboard.com> References: <20251030165816.1095180-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 0000000000..be3f93e43c --- /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 222030c434..d51e6c882f 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 de98a93306..a3a37de873 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" @@ -755,6 +757,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 @@ -800,6 +843,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 Thu Oct 30 16:58:14 2025 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: 24923 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 D98D6C32CE for ; Thu, 30 Oct 2025 16:58:51 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 72B9460967; Thu, 30 Oct 2025 17:58:51 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="O9F4w+nX"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7E2E7608DD for ; Thu, 30 Oct 2025 17:58:27 +0100 (CET) Received: from pb-laptop.local (185.221.140.239.nat.pool.zt.hu [185.221.140.239]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3A1DF6F3; Thu, 30 Oct 2025 17:56:37 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761843397; bh=sEbIS5dRHAfgUY53Ii32hXBCl6YO56ivpMm7rM1I9zQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=O9F4w+nXebw0HExtWjAs0gHKLWCq+biwyR3iaiq0o5XWxxZUeDtOKQpncaoCR4s/K t2Yr5w+cDnJphq6kkKJjXBUPFbDm+wbMjY7PB9EpwSFTYALW4OygUX/d3DrwUESD/5 MgcLm9HG9ttu4ooPfyV7fBRls8uypUtaeDWBg3Nw= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Paul Elder Subject: [RFC PATCH v3 20/22] libcamera: pipeline: Fill `MetadataListPlan` of cameras Date: Thu, 30 Oct 2025 17:58:14 +0100 Message-ID: <20251030165816.1095180-21-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251030165816.1095180-1-barnabas.pocze@ideasonboard.com> References: <20251030165816.1095180-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 7862465924..bd85f6b4bd 100644 --- a/include/libcamera/internal/software_isp/software_isp.h +++ b/include/libcamera/internal/software_isp/software_isp.h @@ -41,6 +41,7 @@ class DebayerCpu; 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 d9a50b01db..f49b777973 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 39b7f1f109..af1d083728 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 12b083e9d0..c32391911e 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 068e898848..015ba46036 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 77328c5fd5..360ed668b7 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 b0d89541da..2d7b0482fe 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 55de05d9e3..499c7790f9 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 dbf69c9073..1d47bffa34 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 3b22f79176..f0b8b5dbea 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 97fcf06cd4..f4f45ef4d4 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 1cae08bf25..8d8811f717 100644 --- a/src/ipa/ipu3/ipu3.cpp +++ b/src/ipa/ipu3/ipu3.cpp @@ -143,7 +143,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 f60fddac3f..97316ca35c 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 3d546e5a85..1fd67a4f9f 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 2351d40555..6bdc88a193 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 2a54c86a91..d36c7ac66e 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 13885eb83b..55aef1d100 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 7d45e7310a..10b61b4fb3 100644 --- a/src/ipa/mali-c55/mali-c55.cpp +++ b/src/ipa/mali-c55/mali-c55.cpp @@ -46,7 +46,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, @@ -96,7 +96,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_) { @@ -131,6 +131,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 f5a3c917cb..cbc7311cd5 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 399fb51be4..8de7ea3951 100644 --- a/src/ipa/rkisp1/algorithms/awb.cpp +++ b/src/ipa/rkisp1/algorithms/awb.cpp @@ -117,6 +117,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 32fc44ffff..03071360b3 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 de2b6fe775..3115d3effa 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 a0e7030fe5..46a4c2c364 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 e8da698100..966c711375 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 f85a130d9c..caf7d9d448 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -230,6 +230,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 fa22bfc349..9815b83a03 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 8dfe35cc32..15e65f9315 100644 --- a/src/ipa/rpi/common/ipa_base.cpp +++ b/src/ipa/rpi/common/ipa_base.cpp @@ -182,6 +182,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 ec7593ffc9..04e7731d22 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 2b205b2861..3014925eaf 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 189de770c6..c4e52328f0 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 112d9f5a19..00e70ea705 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 cf78e98009..fe605117ab 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 ad993f39c1..b8ae63dcb1 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 370385afc6..bcb67b832a 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 0a98406c1a..b1758ff39b 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 d1d5f72712..5a7ffbd8fa 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 c3081e3069..d76c0f79ee 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" @@ -102,6 +103,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 b147aca2e3..9d698a27b3 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 de09431cb9..fbad880b23 100644 --- a/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp +++ b/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp @@ -166,6 +166,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 d6b7edcb5a..13dbdb6268 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 38bdc6138e..938c5b2890 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 ecd1383153..002a44f304 100644 --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp @@ -406,7 +406,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; @@ -1377,6 +1377,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 c209aa5963..87ce290225 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_) { /* @@ -835,6 +838,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 91715b7f8a..312f35ad15 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -602,7 +602,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 4b5816dfdd..59fb4bd5c9 100644 --- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp +++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp @@ -614,6 +614,8 @@ int UVCCameraData::init(MediaDevice *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 5022101505..1b6880a5dc 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 1d3d9ba87e..608bbd469f 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 fdadf79e19..30a4b89666 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) : dmaHeap_(DmaBufAllocator::DmaBufAllocatorFlag::CmaHeap | DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap | DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf) @@ -147,7 +148,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 Thu Oct 30 16:58:15 2025 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: 24922 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 DA4E6C3331 for ; Thu, 30 Oct 2025 16:58:50 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 382CB609D8; Thu, 30 Oct 2025 17:58:50 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="wBDSJyEn"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id EB0DB60988 for ; Thu, 30 Oct 2025 17:58:27 +0100 (CET) Received: from pb-laptop.local (185.221.140.239.nat.pool.zt.hu [185.221.140.239]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C282518E5; Thu, 30 Oct 2025 17:56:37 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761843398; bh=9Su/pJBGf/ztWuvWGp91DlkhlqfcK9TbK1KR2wMK85M=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=wBDSJyEn5EocBpiDyAv3qr7xY1DN8tXwjrsHJAXemmRJbJTrQaGBsLLxnLOn+119e Z+Cyk3D2pm31mMPoJUIZv3sDACcjCQT/xtL22LNfMJYR/gaGfJK1BjFoSdGVZETMoP zK41ZTRsHarLrGKH18AWej2jFcHRjQYG3lda9fzg= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder Subject: [RFC PATCH v3 21/22] libcamera: pipeline: Use `metadataAvailable()` Date: Thu, 30 Oct 2025 17:58:15 +0100 Message-ID: <20251030165816.1095180-22-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251030165816.1095180-1-barnabas.pocze@ideasonboard.com> References: <20251030165816.1095180-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 | 7 ++--- .../pipeline/rpi/common/pipeline_base.cpp | 29 ++++++++++--------- src/libcamera/pipeline/simple/simple.cpp | 4 +-- src/libcamera/pipeline/uvcvideo/uvcvideo.cpp | 3 +- src/libcamera/pipeline/vimc/vimc.cpp | 3 +- src/libcamera/pipeline/virtual/virtual.cpp | 2 +- 9 files changed, 32 insertions(+), 37 deletions(-) diff --git a/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp b/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp index fbad880b23..4353a2e7cf 100644 --- a/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp +++ b/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp @@ -1141,10 +1141,7 @@ void PipelineHandlerISI::bufferReady(FrameBuffer *buffer) Request *request = buffer->request(); /* Record the sensor's timestamp in the request metadata. */ - ControlList &metadata = request->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 13dbdb6268..9238f4400e 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->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->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->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->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->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 938c5b2890..96eb9ab07f 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->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 002a44f304..a7d56f1a3e 100644 --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp @@ -452,7 +452,7 @@ void RkISP1CameraData::metadataReady(unsigned int frame, const ControlList &meta if (!info) return; - info->request->metadata().merge(metadata); + pipe()->metadataAvailable(info->request, metadata); info->metadataProcessed = true; pipe()->tryCompleteRequest(info); @@ -1520,8 +1520,7 @@ void PipelineHandlerRkISP1::imageBufferReady(FrameBuffer *buffer) * \todo The sensor timestamp should be better estimated by connecting * to the V4L2Device::frameStart signal. */ - request->metadata().set(controls::SensorTimestamp, - metadata.timestamp); + metadataAvailable(request, controls::SensorTimestamp, metadata.timestamp); if (isRaw_) { const ControlList &ctrls = @@ -1604,7 +1603,7 @@ void PipelineHandlerRkISP1::imageBufferReady(FrameBuffer *buffer) LOG(RkISP1, Error) << "Cannot queue buffers to dewarper: " << strerror(-ret); - request->metadata().set(controls::ScalerCrop, activeCrop_.value()); + metadataAvailable(request, controls::ScalerCrop, activeCrop_.value()); } 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 87ce290225..bf9404db92 100644 --- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp @@ -1227,7 +1227,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->metadata().merge(metadata); + pipe()->metadataAvailable(request, metadata); /* * Inform the sensor of the latest colour gains if it has the @@ -1497,23 +1497,24 @@ void CameraData::checkRequestCompleted() void CameraData::fillRequestMetadata(const ControlList &bufferControls, Request *request) { - if (auto x = bufferControls.get(controls::SensorTimestamp)) - request->metadata().set(controls::SensorTimestamp, *x); - if (auto x = bufferControls.get(controls::FrameWallClock)) - request->metadata().set(controls::FrameWallClock, *x); + 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; + if (cropParams_.size()) { + std::vector crops; - for (auto const &[k, v] : cropParams_) - crops.push_back(scaleIspCrop(v.ispCrop)); + for (auto const &[k, v] : cropParams_) + crops.push_back(scaleIspCrop(v.ispCrop)); - request->metadata().set(controls::ScalerCrop, crops[0]); - if (crops.size() > 1) { - request->metadata().set(controls::rpi::ScalerCrops, - Span(crops.data(), crops.size())); + 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 312f35ad15..5959d94d43 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -919,7 +919,7 @@ void SimpleCameraData::imageBufferReady(FrameBuffer *buffer) } if (request) - request->metadata().set(controls::SensorTimestamp, + pipe->metadataAvailable(request, controls::SensorTimestamp, buffer->metadata().timestamp); /* @@ -1007,7 +1007,7 @@ void SimpleCameraData::metadataReady(uint32_t frame, const ControlList &metadata if (!info) return; - info->request->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 59fb4bd5c9..8cea94721d 100644 --- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp +++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp @@ -894,8 +894,7 @@ void UVCCameraData::imageBufferReady(FrameBuffer *buffer) Request *request = buffer->request(); /* \todo Use the UVC metadata to calculate a more precise timestamp */ - request->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 1b6880a5dc..8a3a63e656 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->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 23eae852f7..c578560854 100644 --- a/src/libcamera/pipeline/virtual/virtual.cpp +++ b/src/libcamera/pipeline/virtual/virtual.cpp @@ -354,7 +354,7 @@ int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera, VirtualCameraData *data = cameraData(camera); const auto timestamp = currentTimestamp(); - request->metadata().set(controls::SensorTimestamp, timestamp); + metadataAvailable(request, controls::SensorTimestamp, timestamp); data->invokeMethod(&VirtualCameraData::processRequest, ConnectionTypeQueued, request); From patchwork Thu Oct 30 16:58:16 2025 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: 24925 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 5E5DEC3332 for ; Thu, 30 Oct 2025 16:58:54 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E8AFD60A8B; Thu, 30 Oct 2025 17:58:53 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="fOjgf7eQ"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 455D76098A for ; Thu, 30 Oct 2025 17:58:28 +0100 (CET) Received: from pb-laptop.local (185.221.140.239.nat.pool.zt.hu [185.221.140.239]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 215A61FDD; Thu, 30 Oct 2025 17:56:38 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761843398; bh=y4mYrZZXQMsWFG8Vn/9gkT5MaDcE8+MJM+S/ZFOFPFI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=fOjgf7eQr+mfGftjJXy3rGvqdFvxNVAUt5rvqXwDd4DlAy9/oiNa+LDG/nBRDclWf 2oiz0XhIYdlbH/HA6gqNr2iMXFSyyFhe/lN5TyPVBQ8dzpzi6dFoI2tz7dk10wot52 WazFqs7DUe0ypbbAgmIImsT3kHcJFZ+yaSh4Q9AU= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder Subject: [RFC PATCH v3 22/22] libcamera: request: Swap the two metadata lists Date: Thu, 30 Oct 2025 17:58:16 +0100 Message-ID: <20251030165816.1095180-23-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251030165816.1095180-1-barnabas.pocze@ideasonboard.com> References: <20251030165816.1095180-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/request.h | 8 ++++---- 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 | 10 +++++----- src/py/libcamera/py_helpers.cpp | 4 ++-- src/py/libcamera/py_helpers.h | 2 +- src/py/libcamera/py_main.cpp | 2 +- 16 files changed, 35 insertions(+), 35 deletions(-) diff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h index 6c0e546e67..1803b9bbcf 100644 --- a/include/libcamera/internal/pipeline_handler.h +++ b/include/libcamera/internal/pipeline_handler.h @@ -69,11 +69,11 @@ public: void metadataAvailable(Request *request, const Control &ctrl, const internal::cxx20::type_identity_t &value) { - auto &m = request->metadata2(); + auto &m = request->metadata(); const auto c = m.checkpoint(); std::ignore = m.set(ctrl, value); - request->metadata().set(ctrl, value); + request->metadata2().set(ctrl, value); const auto d = c.diffSince(); if (d) @@ -88,8 +88,8 @@ public: void operator()(const Control &ctrl, const internal::cxx20::type_identity_t &value) const { - request->metadata().set(ctrl, value); - std::ignore = request->metadata2().set(ctrl, value); + std::ignore = request->metadata().set(ctrl, value); + request->metadata2().set(ctrl, value); } }; @@ -99,7 +99,7 @@ public: #endif void metadataAvailable(Request *request, Func func) { - const auto c = request->metadata2().checkpoint(); + const auto c = request->metadata().checkpoint(); std::invoke(func, MetadataSetter{ request }); diff --git a/include/libcamera/request.h b/include/libcamera/request.h index 23e7bde72c..6e7057c1d5 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_; } - ControlList &metadata() { return *metadata_; } + [[nodiscard]] MetadataList &metadata() { return metadata_; } #ifndef __DOXYGEN__ - [[nodiscard]] MetadataList &metadata2() { return metadata2_; } + ControlList &metadata2() { return *metadata2_; } #endif const BufferMap &buffers() const { return bufferMap_; } int addBuffer(const Stream *stream, FrameBuffer *buffer, @@ -72,8 +72,8 @@ private: LIBCAMERA_DISABLE_COPY(Request) ControlList *controls_; - ControlList *metadata_; - MetadataList metadata2_; + MetadataList metadata_; + ControlList *metadata2_; BufferMap bufferMap_; const uint64_t cookie_; diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp index 80ff248c2a..fa0cf1d236 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 7cd8772e0a..9c5ce97f43 100644 --- a/src/apps/cam/camera_session.cpp +++ b/src/apps/cam/camera_session.cpp @@ -545,19 +545,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 65794a2f90..7149d782cc 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 26cd61b36d..1f973f2e61 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 8d57023e1e..4319795643 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 aaa8a852b3..741f78a75b 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 96a2d50901..2a678c19aa 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 81fcf915ad..278de1d449 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 6faf3ee7a6..b42eade9b8 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 a3a37de873..ae81cfa4d5 100644 --- a/src/libcamera/pipeline_handler.cpp +++ b/src/libcamera/pipeline_handler.cpp @@ -559,9 +559,9 @@ void PipelineHandler::doQueueRequests(Camera *camera) */ void PipelineHandler::metadataAvailable(Request *request, const ControlList &metadata) { - request->metadata().merge(metadata); + request->metadata2().merge(metadata); - const auto d = request->metadata2().merge(metadata); + const auto d = request->metadata().merge(metadata); if (!d) LOG(Pipeline, Fatal) << "Tried to add incompatible metadata items"; diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp index 0ff465e6f7..8e272eeece 100644 --- a/src/libcamera/request.cpp +++ b/src/libcamera/request.cpp @@ -357,7 +357,7 @@ void Request::Private::timeout() */ Request::Request(Camera *camera, uint64_t cookie) : Extensible(std::make_unique(camera)), - metadata2_(camera->_d()->metadataPlan_), + metadata_(camera->_d()->metadataPlan_), cookie_(cookie), status_(RequestPending) { controls_ = new ControlList(controls::controls, @@ -366,7 +366,7 @@ Request::Request(Camera *camera, uint64_t cookie) /** * \todo Add a validator for metadata controls. */ - metadata_ = new ControlList(controls::controls); + metadata2_ = new ControlList(controls::controls); LIBCAMERA_TRACEPOINT(request_construct, this); @@ -377,7 +377,7 @@ Request::~Request() { LIBCAMERA_TRACEPOINT(request_destroy, this); - delete metadata_; + delete metadata2_; delete controls_; } @@ -410,8 +410,8 @@ void Request::reuse(ReuseFlag flags) status_ = RequestPending; controls_->clear(); - metadata_->clear(); - metadata2_.clear(); + metadata_.clear(); + metadata2_->clear(); } /** diff --git a/src/py/libcamera/py_helpers.cpp b/src/py/libcamera/py_helpers.cpp index 8c55ef8458..a994c90b70 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 983969dfff..895006d0c0 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 a983ea75c3..77e0ead68a 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;