From patchwork Mon Dec 2 13:34:00 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 22147 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 1C7BFBEFBE for ; Mon, 2 Dec 2024 13:34:17 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8C53766069; Mon, 2 Dec 2024 14:34:13 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="NWJR6iRC"; 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 929AD66040 for ; Mon, 2 Dec 2024 14:34:10 +0100 (CET) Received: from ideasonboard.com (mob-5-90-236-68.net.vodafone.it [5.90.236.68]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A64D275A; Mon, 2 Dec 2024 14:33:43 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1733146424; bh=4clINLyOxNeTfw/ssU7qARDlzT04DLr1FwFQfBDRrVY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=NWJR6iRCP+hOVExUglJ+foKJlMY0rkXJDqEzqsUm+dyC4b8oLb3GU6UQBlcld+LM0 mSyh8CR/kK72Ru+2mM7DLVNyjd5XAXl/vhV366aR0+YicrFCeUvV9GqqWpwfBqbFQb RhFNgGsAHLngvluCx6hdTphByp/3MU/wFNnDlv54= From: Jacopo Mondi To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v2 1/4] libcamera: Implement YamlEmitter Date: Mon, 2 Dec 2024 14:34:00 +0100 Message-ID: <20241202133404.41431-2-jacopo.mondi@ideasonboard.com> X-Mailer: git-send-email 2.47.0 In-Reply-To: <20241202133404.41431-1-jacopo.mondi@ideasonboard.com> References: <20241202133404.41431-1-jacopo.mondi@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" Implement a helpers to allow outputting text in YAML format. The class of helpers allows to create list and dictionaries and emit scalar values starting from a YamlRoot object initialized with a file path. Signed-off-by: Jacopo Mondi --- include/libcamera/internal/meson.build | 1 + include/libcamera/internal/yaml_emitter.h | 149 +++++ src/libcamera/meson.build | 1 + src/libcamera/yaml_emitter.cpp | 719 ++++++++++++++++++++++ 4 files changed, 870 insertions(+) create mode 100644 include/libcamera/internal/yaml_emitter.h create mode 100644 src/libcamera/yaml_emitter.cpp diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build index 1dddcd50c90b..357d96566cfb 100644 --- a/include/libcamera/internal/meson.build +++ b/include/libcamera/internal/meson.build @@ -42,6 +42,7 @@ libcamera_internal_headers = files([ 'v4l2_pixelformat.h', 'v4l2_subdevice.h', 'v4l2_videodevice.h', + 'yaml_emitter.h', 'yaml_parser.h', ]) diff --git a/include/libcamera/internal/yaml_emitter.h b/include/libcamera/internal/yaml_emitter.h new file mode 100644 index 000000000000..22b3c43b919d --- /dev/null +++ b/include/libcamera/internal/yaml_emitter.h @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board Oy + * + * libcamera YAML emitter helper + */ + +#pragma once + +#include +#include +#include + +#include +#include + +#include + +namespace libcamera { + +class YamlDict; +class YamlEvent; +class YamlList; +class YamlRoot; +class YamlScalar; + +class YamlEmitter final +{ +public: + ~YamlEmitter(); + + static YamlRoot root(const std::string &path); + +private: + friend class YamlOutput; + friend class YamlRoot; + + LIBCAMERA_DISABLE_COPY(YamlEmitter) + + YamlEmitter(const std::string &path); + + void logError(); + void init(); + int emit(); + + File file_; + yaml_event_t event_; + yaml_emitter_t emitter_; +}; + +class YamlOutput +{ +public: + bool valid() const { return !!emitter_; } + +protected: + YamlOutput() = default; + YamlOutput(YamlOutput &&other); + YamlOutput(YamlEmitter *emitter, YamlOutput *parent); + + virtual ~YamlOutput(); + + YamlOutput &operator=(YamlOutput &&other); + + int emitScalar(std::string_view scalar); + int emitMappingStart(); + int emitMappingEnd(); + int emitSequenceStart(); + int emitSequenceEnd(); + + YamlDict dict(YamlOutput *parent); + YamlList list(YamlOutput *parent); + + YamlEmitter *emitter_ = nullptr; + yaml_event_t event_; + + YamlOutput *parent_ = nullptr; + YamlOutput *child_ = nullptr; + +private: + LIBCAMERA_DISABLE_COPY(YamlOutput) + + void unlinkChildren(YamlOutput *node); +}; + +class YamlRoot : public YamlOutput +{ +public: + YamlRoot() = default; + YamlRoot(YamlRoot &&other) = default; + ~YamlRoot(); + + YamlRoot &operator=(YamlRoot &&other) = default; + + YamlList list(); + YamlDict dict(); + +private: + LIBCAMERA_DISABLE_COPY(YamlRoot) + + friend class YamlEmitter; + + YamlRoot(std::unique_ptr emitter) + : YamlOutput(emitter.get(), nullptr), emitterRoot_(std::move(emitter)) + { + } + + std::unique_ptr emitterRoot_; +}; + +class YamlList : public YamlOutput +{ +public: + YamlList() = default; + YamlList(YamlList &&other) = default; + ~YamlList(); + + YamlList &operator=(YamlList &&other) = default; + + YamlList list(); + YamlDict dict(); + void scalar(std::string_view scalar); + +private: + friend class YamlOutput; + + YamlList(YamlEmitter *emitter, YamlOutput *parent); +}; + +class YamlDict : public YamlOutput +{ +public: + YamlDict() = default; + YamlDict(YamlDict &&other) = default; + ~YamlDict(); + + YamlDict &operator=(YamlDict &&other) = default; + + YamlList list(std::string_view key); + YamlDict dict(std::string_view key); + void scalar(std::string_view key, std::string_view scalar); + +private: + friend class YamlOutput; + + YamlDict(YamlEmitter *emitter, YamlOutput *parent); +}; + +} /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index 21cae11756d6..933b98bf05e9 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -52,6 +52,7 @@ libcamera_internal_sources = files([ 'v4l2_pixelformat.cpp', 'v4l2_subdevice.cpp', 'v4l2_videodevice.cpp', + 'yaml_emitter.cpp', 'yaml_parser.cpp', ]) diff --git a/src/libcamera/yaml_emitter.cpp b/src/libcamera/yaml_emitter.cpp new file mode 100644 index 000000000000..c0629e249c57 --- /dev/null +++ b/src/libcamera/yaml_emitter.cpp @@ -0,0 +1,719 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board Oy + * + * libcamera YAML emitter helper + */ + +#include "libcamera/internal/yaml_emitter.h" + +#include + +/** + * \file yaml_emitter.h + * \brief A YAML emitter helper + * + * The YAML emitter helpers allow users to emit output in YAML format. + + * Unlike YAML writers that operate on a fully populated representation of the + * data, the YAML emitter outputs YAML data on the fly. It is suitable for + * outputting large amount of data with low overhead and no runtime heap + * allocation. + * + * To emit YAML users of the these helper classes create a root node with + * + * \code + std::string filePath("..."); + auto root = YamlEmitter::root(filePath); + \endcode + * + * and start emitting dictionaries and lists with the YamlRoot::dict() and + * YamlRoot::list() functions. + * + * The classes part of this file implement RAII-style handling of YAML + * events. By creating a YamlList and YamlDict instance the associated YAML + * sequence start and mapping start events are emitted and once the instances + * gets destroyed the corresponding sequence end and mapping end events are + * emitted. + * + * From an initialized YamlRoot instance is possible to create YAML list and + * dictionaries. + * + * \code + YamlDict dict = root.dict(); + YamlList list = root.list(); + \endcode + * + * YamlDict instances can be populated with scalars associated with a key + * + * \code + dict["key"] = "value"; + \endcode + * + * and it is possible to create lists and dictionaries, associated with a key + * + * \code + YamlDict subDict = dict.dict("newDict"); + YamlList subList = dict.list("newList"); + \endcode + * + * YamlList instances can be populated with scalar elements + * + * \code + list.scalar("x"); + list.scalar("y"); + \endcode + * + * and with dictionaries and lists too + * + * \code + YamlDict subDict = list.dict(); + YamlList subList = list.list(); + \endcode + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(YamlEmitter) + +namespace { + +int yamlWrite(void *data, unsigned char *buffer, size_t size) +{ + File *file = static_cast(data); + + Span buf{ buffer, size }; + ssize_t ret = file->write(buf); + if (ret < 0) { + LOG(YamlEmitter, Error) << "Write error: " << strerror(ret); + return 0; + } + + return 1; +} + +} /* namespace */ + +/** + * \class YamlEmitter + * + * YAML helper classes entry point. This class allows to create a YamlRoot + * instance, using the YamlEmitter::root() function, that users can populate + * with lists, dictionaries and scalars. + */ + +YamlEmitter::YamlEmitter(const std::string &path) +{ + file_.setFileName(path); + file_.open(File::OpenModeFlag::WriteOnly); +} + +YamlEmitter::~YamlEmitter() +{ + yaml_event_delete(&event_); + yaml_emitter_delete(&emitter_); +} + +/** + * \brief Create an initialized instance of YamlRoot + * \param[in] path The YAML output file path + * + * Create an initialized instance of the YamlRoot class that users can start + * using and populating with scalars, lists and dictionaries. + * + * \return An initialized YamlRoot instance + */ +YamlRoot YamlEmitter::root(const std::string &path) +{ + std::unique_ptr emitter{ new YamlEmitter(path) }; + + emitter->init(); + + return YamlRoot(std::move(emitter)); +} + +void YamlEmitter::logError() +{ + switch (emitter_.error) { + case YAML_MEMORY_ERROR: + LOG(YamlEmitter, Error) + << "Memory error: Not enough memory for emitting"; + break; + + case YAML_WRITER_ERROR: + LOG(YamlEmitter, Error) + << "Writer error: " << emitter_.problem; + break; + + case YAML_EMITTER_ERROR: + LOG(YamlEmitter, Error) + << "Emitter error: " << emitter_.problem; + break; + + default: + LOG(YamlEmitter, Error) << "Internal error"; + break; + } +} + +void YamlEmitter::init() +{ + yaml_emitter_initialize(&emitter_); + yaml_emitter_set_output(&emitter_, yamlWrite, &file_); + + yaml_stream_start_event_initialize(&event_, YAML_UTF8_ENCODING); + emit(); + + yaml_document_start_event_initialize(&event_, nullptr, nullptr, + nullptr, 0); + emit(); +} + +int YamlEmitter::emit() +{ + int ret = yaml_emitter_emit(&emitter_, &event_); + if (!ret) { + logError(); + return -EINVAL; + } + + return 0; +} + +/** + * \class YamlOutput + * + * The YamlOutput base class. From this class are derived the YamlList and + * YamlDict classes which are meant to be used by users of the YAML emitter + * helpers. + * + * The YamlOutput base class provides functions to create YAML lists and + * dictionaries and to populate them. + * + * This class cannot be instantiated directly. + */ + +/** + * \fn YamlOutput::YamlOutput() + * \brief The default constructor + * + * Create an empty, non-initialized instance. The newly created instance cannot + * be used without being first move-assigned to a valid and initialized + * instance. Valid instances of this class can only be created using the + * YamlRoot::list(), YamlRoot::dict(), YamlList::list(), YamlList::dict(), + * YamlDict::list() and YamlDict::dict() functions. + */ + +/** + * \brief Create a YamlOutput instance with an associated \a emitter and \a parent + * \param[in] emitter The YAML emitter + * \param[in] parent Pointer to the parent node + * + * Create a YamlOutput with a valid YamlEmitter and with a reference to the + * parent node. The parent's child_ pointer is assigned to the newly created + * instance. If the parent already has a valid child_ reference, unlink and + * invalidate the chain of previously assigned children nodes by resetting their + * parent_ pointer. None of the children nodes can be used anymore from this + * point on. + */ +YamlOutput::YamlOutput(YamlEmitter *emitter, YamlOutput *parent) + : emitter_(emitter), parent_(parent) +{ + if (!parent_) + return; + + unlinkChildren(parent_->child_); + parent_->child_ = this; +} + +/** + * \brief Move constructor + * \param[in] other The instance to be moved + * + * Move an instance and invalidate \a other. The move constructor reassigns + * the parent's child_ to point to the newly initialized instance. + */ +YamlOutput::YamlOutput(YamlOutput &&other) +{ + emitter_ = other.emitter_; + event_ = other.event_; + parent_ = other.parent_; + child_ = other.child_; + + other.emitter_ = nullptr; + other.event_ = {}; + other.parent_ = nullptr; + other.child_ = nullptr; + + if (parent_) + parent_->child_ = this; +} + +YamlOutput::~YamlOutput() +{ + if (parent_) + parent_->child_ = nullptr; +} + +void YamlOutput::unlinkChildren(YamlOutput *node) +{ + YamlOutput *nextNode = node; + while (nextNode) { + YamlOutput *nextChild = nextNode->child_; + + nextNode->parent_ = nullptr; + nextNode->child_ = nullptr; + nextNode = nextChild; + } +} + +/** + * \brief Move assignment operator + * \param[in] other The instance to be moved + * + * Move-assign an instance and invalidate \a other. The move assignment operator + * reassigns the parent's child_ to point to the newly initialized instance. + */ +YamlOutput &YamlOutput::operator=(YamlOutput &&other) +{ + emitter_ = other.emitter_; + event_ = other.event_; + parent_ = other.parent_; + child_ = other.child_; + + other.emitter_ = nullptr; + other.event_ = {}; + other.parent_ = nullptr; + other.child_ = nullptr; + + if (parent_) + parent_->child_ = this; + + return *this; +} + +/** + * \fn YamlOutput::valid() + * \brief Check if a YamlOutput instance has been correctly initialized + * \return True if the instance has been initialized, false otherwise + */ + +/** + * \brief Emit \a scalar as a YAML scalar + * \param[in] scalar The element to emit + * \return 0 in case of success, a negative error value otherwise + */ +int YamlOutput::emitScalar(std::string_view scalar) +{ + if (!valid()) + return -EINVAL; + + const yaml_char_t *value = reinterpret_cast + (scalar.data()); + + /* + * \todo Remove the const_cast of 'value' when + * dropping support for libyaml versions < 0.2.3 (shipped in Debian11). + * + * https://github.com/yaml/libyaml/pull/140 + */ + yaml_scalar_event_initialize(&emitter_->event_, nullptr, nullptr, + const_cast(value), + scalar.length(), true, false, + YAML_PLAIN_SCALAR_STYLE); + return emitter_->emit(); +} + +/** + * \brief Emit the mapping start YAML event + * \return 0 in case of success, a negative error value otherwise + */ +int YamlOutput::emitMappingStart() +{ + if (!valid()) + return -EINVAL; + + yaml_mapping_start_event_initialize(&emitter_->event_, nullptr, nullptr, + true, YAML_BLOCK_MAPPING_STYLE); + return emitter_->emit(); +} + +/** + * \brief Emit the mapping end YAML event + * \return 0 in case of success, a negative error value otherwise + */ +int YamlOutput::emitMappingEnd() +{ + if (!valid()) + return -EINVAL; + + yaml_mapping_end_event_initialize(&emitter_->event_); + return emitter_->emit(); +} + +/** + * \brief Emit the sequence start YAML event + * \return 0 in case of success, a negative error value otherwise + */ +int YamlOutput::emitSequenceStart() +{ + if (!valid()) + return -EINVAL; + + yaml_sequence_start_event_initialize(&emitter_->event_, nullptr, nullptr, + true, YAML_BLOCK_SEQUENCE_STYLE); + return emitter_->emit(); +} + +/** + * \brief Emit the sequence end YAML event + * \return 0 in case of success, a negative error value otherwise + */ +int YamlOutput::emitSequenceEnd() +{ + if (!valid()) + return -EINVAL; + + yaml_sequence_end_event_initialize(&emitter_->event_); + return emitter_->emit(); +} + +/** + * \brief Create a dictionary instance + * \return An instance of YamlDict + */ +YamlDict YamlOutput::dict(YamlOutput *parent) +{ + return YamlDict(emitter_, parent); +} + +/** + * \brief Create a list instance + * \return An instance of YamlList + */ +YamlList YamlOutput::list(YamlOutput *parent) +{ + return YamlList(emitter_, parent); +} + +/** + * \var YamlOutput::emitter_ + * \brief The emitter used by this YamlObject to output YAML events + */ + +/** + * \var YamlOutput::event_ + * \brief The YAML event used by this YamlObject + */ + +/** + * \var YamlOutput::parent_ + * \brief The parent node, set at object creation time + * + * The parent_ class member references the parent node. Only nodes with a valid + * parent can emit valid YAML output. The parent_ pointer is reset to an invalid + * value when a new child is created on the parent node, effectively + * invalidating the current instance. + */ + +/** + * \var YamlOutput::child_ + * \brief The child node + * + * The child_ class member references any eventual child created from the + * current instance using the list() and dict() functions. Only a single + * valid child_ at the time can be assigned to a YamlOutput instance, if + * multiple child are created the last created ones overwrites the child_ + * pointer and invalidates all other children. + */ + +/** + * \class YamlRoot + * + * The YAML root node. A valid YamlRoot instance can only be created using the + * YamlEmitter::root() function. The typical initialization pattern of users of + * this class is similar to the one in the following example: + * + * \code + class YamlUser + { + public: + YamlUser(); + + private: + YamlRool root_; + }; + + YamlUser::YamlUser() + { + root_ = YamlEmitter::root("/path/to/yaml/file.yml"); + } + \endcode + * + * A YamlRoot element can be populated with list and dictionaries. + */ + +/** + * \fn YamlRoot::YamlRoot() + * \brief Construct a YamlRoot instance without initializing it + * + * A YamlRoot instance can be created in non-initialized state typically to be + * stored as a class member to maintain their lifetime active. In order to start + * using and populating a YamlRoot, a valid and initialized instance created + * using the YamlEmitter::root() function has to be move-assigned to a + * non-initialized instance. + * + * \code + YamlRoot root; + + root = YamlEmitter::root("/path/to/yaml/file.yml"); + \endcode + */ + +/** + * \fn YamlRoot::YamlRoot() + * \copydoc YamlOutput::YamlOutput() + */ + +/** + * \fn YamlRoot &YamlRoot::YamlRoot(YamlRoot &&other) + * \copydoc YamlOutput &YamlOutput::YamlOutput(YamlOutput &&other) + */ + +YamlRoot::~YamlRoot() +{ + if (!valid()) + return; + + yaml_document_end_event_initialize(&emitter_->event_, 0); + emitterRoot_->emit(); + + yaml_stream_end_event_initialize(&emitter_->event_); + emitterRoot_->emit(); +} + +/** + * \fn YamlRoot &YamlRoot::operator=(YamlRoot &&other) + * \copydoc YamlOutput &operator=(YamlOutput &&other) + */ + +/** + * \copydoc YamlOutput::dict() + */ +YamlDict YamlRoot::dict() +{ + int ret = emitMappingStart(); + if (ret) + return {}; + + return YamlOutput::dict(this); +} + +/** + * \copydoc YamlOutput::list() + */ +YamlList YamlRoot::list() +{ + int ret = emitSequenceStart(); + if (ret) + return {}; + + return YamlOutput::list(this); +} + +/** + * \class YamlList + * + * A YamlList can be populated with scalars and allows to create nested lists + * and dictionaries. + */ + +/** + * \fn YamlList::YamlList() + * \copydoc YamlOutput::YamlOutput() + */ + +/** + * \copydoc YamlOutput::YamlOutput(YamlEmitter *emitter, YamlOutput *parent) + */ +YamlList::YamlList(YamlEmitter *emitter, YamlOutput *parent) + : YamlOutput(emitter, parent) +{ +} + +/** + * \fn YamlList &YamlList::YamlList(YamlList &&other) + * \copydoc YamlOutput &YamlOutput::YamlOutput(YamlOutput &&other) + */ + +YamlList::~YamlList() +{ + emitSequenceEnd(); +} + +/** + * \fn YamlList &YamlList::operator=(YamlList &&other) + * \copydoc YamlOutput &operator=(YamlOutput &&other) + */ + +/** + * \copydoc YamlOutput::list() + */ +YamlList YamlList::list() +{ + if (!parent_) { + LOG(YamlEmitter, Error) + << "Invalid usage of the YamlEmitter API. " + << " The YAML output might not be correct."; + return {}; + } + + int ret = emitSequenceStart(); + if (ret) + return {}; + + return YamlOutput::list(this); +} + +/** + * \copydoc YamlOutput::dict() + */ +YamlDict YamlList::dict() +{ + if (!parent_) { + LOG(YamlEmitter, Error) + << "Invalid usage of the YamlEmitter API. " + << " The YAML output might not be correct."; + return {}; + } + + int ret = emitMappingStart(); + if (ret) + return {}; + + return YamlOutput::dict(this); +} + +/** + * \brief Append \a scalar to the list + * \param[in] scalar The element to append to the list + */ +void YamlList::scalar(std::string_view scalar) +{ + if (!parent_) { + LOG(YamlEmitter, Error) + << "Invalid usage of the YamlEmitter API. " + << " The YAML output might not be correct."; + return; + } + + emitScalar(scalar); +} + +/** + * \class YamlDict + * + * A YamlDict can create lists, other dictionaries and emit a scalar associated + * with a key. + */ + +/** + * \fn YamlDict::YamlDict() + * \copydoc YamlOutput::YamlOutput() + */ + +/** + * \copydoc YamlOutput::YamlOutput(YamlEmitter *emitter, YamlOutput *parent) + */ +YamlDict::YamlDict(YamlEmitter *emitter, YamlOutput *parent) + : YamlOutput(emitter, parent) +{ +} + +/** + * \fn YamlDict &YamlDict::YamlDict(YamlDict &&other) + * \copydoc YamlOutput &YamlOutput::YamlOutput(YamlOutput &&other) + */ + +YamlDict::~YamlDict() +{ + emitMappingEnd(); +} + +/** + * \fn YamlDict &YamlDict::operator=(YamlDict &&other) + * \copydoc YamlOutput &operator=(YamlOutput &&other) + */ + +/** + * \brief Create a list associated with \a key + * \param[in] key The key to associate the list with + * \return An instance of YamlList + */ +YamlList YamlDict::list(std::string_view key) +{ + if (!parent_) { + LOG(YamlEmitter, Error) + << "Invalid usage of the YamlEmitter API. " + << " The YAML output might not be correct."; + return {}; + } + + int ret = emitScalar(key); + if (ret) + return {}; + + ret = emitSequenceStart(); + if (ret) + return {}; + + return YamlOutput::list(this); +} + +/** + * \brief Create a dictionary associated with \a key + * \param[in] key The key to associate the dictionary with + * \return An instance of YamlDict + */ +YamlDict YamlDict::dict(std::string_view key) +{ + if (!parent_) { + LOG(YamlEmitter, Error) + << "Invalid usage of the YamlEmitter API. " + << " The YAML output might not be correct."; + return {}; + } + + int ret = emitScalar(key); + if (ret) + return {}; + + ret = emitMappingStart(); + if (ret) + return {}; + + return YamlOutput::dict(this); +} + +/** + * \brief Emit \a scalar associated with \a key in the dictionary + * \param[in] key The key associated with the newly created scalar + * \param[in] scalar The scalar to emit + */ +void YamlDict::scalar(std::string_view key, std::string_view scalar) +{ + if (!parent_) { + LOG(YamlEmitter, Error) + << "Invalid usage of the YamlEmitter API. " + << " The YAML output might not be correct."; + return; + } + + int ret = emitScalar(key); + if (ret) + return; + + emitScalar(scalar); +} + +} /* namespace libcamera */ From patchwork Mon Dec 2 13:34:01 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 22148 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 93301BEFBE for ; Mon, 2 Dec 2024 13:34:18 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1072566067; Mon, 2 Dec 2024 14:34:15 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="dUJ3eRGx"; 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 353CA66040 for ; Mon, 2 Dec 2024 14:34:11 +0100 (CET) Received: from ideasonboard.com (mob-5-90-236-68.net.vodafone.it [5.90.236.68]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 54321514; Mon, 2 Dec 2024 14:33:44 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1733146424; bh=jo+JyqtQ+CQ9klqotyraZiGry867K5K1pXQicjk5rbQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=dUJ3eRGxucKjnuG9a/T52xSOulsOXdplYNUHBZb+oFw8xpYOTVS2Mr9PgwefQyMUZ t4kfEqHUV30WdCjHOPGBj8afeIHWcrOwhTWiOHxtzs8JptEz4JE9PIAaMlraV5Nm89 RIvozCBmNXKC9KLGiXnFJKj1ZGtpKEy6K3jPMuxs= From: Jacopo Mondi To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder Subject: [PATCH v2 2/4] pipeline: Add support for dumping capture script and metadata Date: Mon, 2 Dec 2024 14:34:01 +0100 Message-ID: <20241202133404.41431-3-jacopo.mondi@ideasonboard.com> X-Mailer: git-send-email 2.47.0 In-Reply-To: <20241202133404.41431-1-jacopo.mondi@ideasonboard.com> References: <20241202133404.41431-1-jacopo.mondi@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: Paul Elder Add support for dumping capture scripts and metadata using the Yaml Emitter helpers. The capture scripts can then be fed into the cam application and a capture can thus be "replayed". Metadata can also be dumped. Camera configuration is also dumped to the capture script. The cam application currently does not support loading configuration from the capture script, but support for that will be added in a subsequent patch. These can be enabled by a new environment variable. Signed-off-by: Paul Elder [rebased on the YamlEmitter classes] Signed-off-by: Jacopo Mondi --- include/libcamera/internal/camera.h | 3 + include/libcamera/internal/pipeline_handler.h | 23 +++++ src/libcamera/camera.cpp | 13 +++ src/libcamera/pipeline_handler.cpp | 93 +++++++++++++++++++ 4 files changed, 132 insertions(+) diff --git a/include/libcamera/internal/camera.h b/include/libcamera/internal/camera.h index 0add0428bb5d..a42f03d4c755 100644 --- a/include/libcamera/internal/camera.h +++ b/include/libcamera/internal/camera.h @@ -19,6 +19,8 @@ namespace libcamera { +enum class Orientation; + class CameraControlValidator; class PipelineHandler; class Stream; @@ -65,6 +67,7 @@ private: std::string id_; std::set streams_; std::set activeStreams_; + Orientation orientation_; bool disconnected_; std::atomic state_; diff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h index fb28a18d0f46..8e8450ae4e11 100644 --- a/include/libcamera/internal/pipeline_handler.h +++ b/include/libcamera/internal/pipeline_handler.h @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -18,8 +19,12 @@ #include #include +#include "libcamera/internal/yaml_emitter.h" + namespace libcamera { +enum class Orientation; + class Camera; class CameraConfiguration; class CameraManager; @@ -69,6 +74,9 @@ public: CameraManager *cameraManager() const { return manager_; } + void dumpConfiguration(const std::set &streams, + const Orientation &orientation); + protected: void registerCamera(std::shared_ptr camera); void hotplugMediaDevice(MediaDevice *media); @@ -82,6 +90,11 @@ protected: CameraManager *manager_; private: + enum DumpMode { + Controls, + Metadata, + }; + void unlockMediaDevices(); void mediaDeviceDisconnected(MediaDevice *media); @@ -90,6 +103,8 @@ private: void doQueueRequest(Request *request); void doQueueRequests(); + void dumpRequest(Request *request, DumpMode mode); + std::vector> mediaDevices_; std::vector> cameras_; @@ -98,6 +113,14 @@ private: const char *name_; unsigned int useCount_; + YamlRoot controlsEmitter_; + YamlDict controlsDict_; + YamlList controlsList_; + + YamlRoot metadataEmitter_; + YamlDict metadataDict_; + YamlList metadataList_; + friend class PipelineHandlerFactoryBase; }; diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp index 4c865a46af53..421dde0f67f0 100644 --- a/src/libcamera/camera.cpp +++ b/src/libcamera/camera.cpp @@ -1215,6 +1215,9 @@ int Camera::configure(CameraConfiguration *config) d->activeStreams_.insert(stream); } + /* TODO Save sensor configuration for dumping it to capture script */ + d->orientation_ = config->orientation; + d->setState(Private::CameraConfigured); return 0; @@ -1356,6 +1359,16 @@ int Camera::start(const ControlList *controls) ASSERT(d->requestSequence_ == 0); + /* + * Invoke method in blocking mode to avoid the risk of writing after + * streaming has started. + * This needs to be here as PipelineHandler::start is a virtual function + * so it is impractical to add the dumping there. + * TODO Pass the sensor configuration, once it is supported + */ + d->pipe_->invokeMethod(&PipelineHandler::dumpConfiguration, + ConnectionTypeBlocking, d->activeStreams_, d->orientation_); + ret = d->pipe_->invokeMethod(&PipelineHandler::start, ConnectionTypeBlocking, this, controls); if (ret) diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp index caa5c20e7483..e1ff6e6a5ce9 100644 --- a/src/libcamera/pipeline_handler.cpp +++ b/src/libcamera/pipeline_handler.cpp @@ -8,6 +8,7 @@ #include "libcamera/internal/pipeline_handler.h" #include +#include #include #include @@ -462,6 +463,8 @@ void PipelineHandler::doQueueRequest(Request *request) request->_d()->sequence_ = data->requestSequence_++; + dumpRequest(request, DumpMode::Controls); + if (request->_d()->cancelled_) { completeRequest(request); return; @@ -551,6 +554,8 @@ void PipelineHandler::completeRequest(Request *request) request->_d()->complete(); + dumpRequest(request, DumpMode::Metadata); + Camera::Private *data = camera->_d(); while (!data->queuedRequests_.empty()) { @@ -772,6 +777,94 @@ void PipelineHandler::disconnect() * \return The CameraManager for this pipeline handler */ +/** + * \brief Dump the camera configuration to YAML format + * + * Dump to the file path specified in the LIBCAMERA_DUMP_CAPTURE_SCRIPT + * environment variable, if any, the Camera configuration in YAML format. + */ +void PipelineHandler::dumpConfiguration(const std::set &streams, + const Orientation &orientation) +{ + const char *file = utils::secure_getenv("LIBCAMERA_DUMP_CAPTURE_SCRIPT"); + if (!file) + return; + + std::string filePath(file); + LOG(Pipeline, Debug) << "Dumping controls in YAML format to: " + << filePath; + + /* Create the YAML roots for controls and metadata output files. */ + + controlsEmitter_ = YamlEmitter::root(filePath); + controlsDict_ = controlsEmitter_.dict(); + + /* + * Metadata needs to go into a separate file because otherwise it'll + * flood the capture script + */ + filePath += ".metadata"; + LOG(Pipeline, Debug) << "Dumping metadata in YAML format to: " + << filePath; + metadataEmitter_ = YamlEmitter::root(filePath); + metadataDict_ = metadataEmitter_.dict(); + metadataList_ = metadataDict_.list("frames"); + + YamlDict configurationDict = controlsDict_.dict("configuration"); + std::stringstream o; + o << orientation; + configurationDict.scalar("orientation", o.str()); + + /* \todo Dump Sensor configuration */ + + YamlList streamsList = configurationDict.list("streams"); + + for (const auto &stream : streams) { + const StreamConfiguration &streamConfig = stream->configuration(); + YamlDict yamlStream = streamsList.dict(); + + yamlStream.scalar("pixelformat", streamConfig.pixelFormat.toString()); + yamlStream.scalar("size", streamConfig.size.toString()); + yamlStream.scalar("stride", std::to_string(streamConfig.stride)); + yamlStream.scalar("frameSize", std::to_string(streamConfig.frameSize)); + yamlStream.scalar("bufferCount", std::to_string(streamConfig.bufferCount)); + + if (streamConfig.colorSpace) + yamlStream.scalar("colorSpace", + streamConfig.colorSpace->toString()); + } +} + +void PipelineHandler::dumpRequest(Request *request, DumpMode mode) +{ + if (!controlsEmitter_.valid()) + return; + + ControlList &controls = mode == DumpMode::Controls ? request->controls() + : request->metadata(); + if (controls.empty()) + return; + + YamlDict yamlFrame; + if (mode == DumpMode::Controls) { + if (!controlsList_.valid()) + controlsList_ = controlsDict_.list("frames"); + + yamlFrame = controlsList_.dict(); + } else { + yamlFrame = metadataList_.dict(); + } + + YamlDict yamlCtrls = yamlFrame.dict(std::to_string(request->sequence())); + + const ControlIdMap *idMap = controls.idMap(); + for (const auto &pair : controls) { + const ControlId *ctrlId = idMap->at(pair.first); + + yamlCtrls.scalar(ctrlId->name(), pair.second.toString()); + } +} + /** * \class PipelineHandlerFactoryBase * \brief Base class for pipeline handler factories From patchwork Mon Dec 2 13:34:02 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 22149 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 906CCBEFBE for ; Mon, 2 Dec 2024 13:34:19 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id EA05A6606B; Mon, 2 Dec 2024 14:34:15 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="KWLiToFF"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C69B566069 for ; Mon, 2 Dec 2024 14:34:11 +0100 (CET) Received: from ideasonboard.com (mob-5-90-236-68.net.vodafone.it [5.90.236.68]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E84C075A; Mon, 2 Dec 2024 14:33:44 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1733146425; bh=aWjmbJ5uWaEHu7I2VK301HOo61XBr2nudYUCccqvl0Y=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=KWLiToFFGdJFn1+7oF3zvRmFFNXGQdTaSNWpD9HmmJ9SwnXJhh7W0+l6Yaxw9Uyri FZIx/p6ioyqpOcL+PJCFRb9rrtoPPqGrGCvyks2vD744cxRYn9OvvKqFkwGIdkv391 YyvUDBJY6D+n5Wq4EtIbmzwUalJlxQ71j8818K20= From: Jacopo Mondi To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder Subject: [PATCH v2 3/4] apps: cam: Add support for loading configuration from capture script Date: Mon, 2 Dec 2024 14:34:02 +0100 Message-ID: <20241202133404.41431-4-jacopo.mondi@ideasonboard.com> X-Mailer: git-send-email 2.47.0 In-Reply-To: <20241202133404.41431-1-jacopo.mondi@ideasonboard.com> References: <20241202133404.41431-1-jacopo.mondi@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: Paul Elder Add support to the cam application for loading the camera configuration from a capture script. These are not expected to be written by hand, but rather dumped via the LIBCAMERA_DUMP_CAPTURE_SCRIPT environment variable. If any configuration options are specified by command line parameters, those will take precedence. Signed-off-by: Paul Elder --- src/apps/cam/camera_session.cpp | 22 +++-- src/apps/cam/capture_script.cpp | 164 ++++++++++++++++++++++++++++++++ src/apps/cam/capture_script.h | 8 ++ 3 files changed, 184 insertions(+), 10 deletions(-) diff --git a/src/apps/cam/camera_session.cpp b/src/apps/cam/camera_session.cpp index 6e9890ccfda1..f31b5282c706 100644 --- a/src/apps/cam/camera_session.cpp +++ b/src/apps/cam/camera_session.cpp @@ -70,6 +70,18 @@ CameraSession::CameraSession(CameraManager *cm, return; } + if (options_.isSet(OptCaptureScript)) { + std::string scriptName = options_[OptCaptureScript].toString(); + script_ = std::make_unique(camera_, scriptName); + if (!script_->valid()) { + std::cerr << "Invalid capture script '" << scriptName + << "'" << std::endl; + return; + } + + script_->populateConfiguration(config.get()); + } + if (options_.isSet(OptOrientation)) { std::string orientOpt = options_[OptOrientation].toString(); static const std::map orientations{ @@ -119,16 +131,6 @@ CameraSession::CameraSession(CameraManager *cm, } #endif - if (options_.isSet(OptCaptureScript)) { - std::string scriptName = options_[OptCaptureScript].toString(); - script_ = std::make_unique(camera_, scriptName); - if (!script_->valid()) { - std::cerr << "Invalid capture script '" << scriptName - << "'" << std::endl; - return; - } - } - switch (config->validate()) { case CameraConfiguration::Valid: break; diff --git a/src/apps/cam/capture_script.cpp b/src/apps/cam/capture_script.cpp index fc1dfa75f2d4..5455e35f37b1 100644 --- a/src/apps/cam/capture_script.cpp +++ b/src/apps/cam/capture_script.cpp @@ -7,6 +7,7 @@ #include "capture_script.h" +#include #include #include #include @@ -162,6 +163,10 @@ int CaptureScript::parseScript(FILE *script) ret = parseFrames(); if (ret) return ret; + } else if (section == "configuration") { + ret = parseConfiguration(); + if (ret) + return ret; } else { std::cerr << "Unsupported section '" << section << "'" << std::endl; @@ -322,6 +327,165 @@ int CaptureScript::parseControl(EventPtr event, ControlList &controls) return 0; } +int CaptureScript::parseOrientation(EventPtr event) +{ + static const std::map orientations{ + { "Rotate0", libcamera::Orientation::Rotate0 }, + { "Rotate0Mirror", libcamera::Orientation::Rotate0Mirror }, + { "Rotate180", libcamera::Orientation::Rotate180 }, + { "Rotate180Mirror", libcamera::Orientation::Rotate180Mirror }, + { "Rotate90Mirror", libcamera::Orientation::Rotate90Mirror }, + { "Rotate270", libcamera::Orientation::Rotate270 }, + { "Rotate270Mirror", libcamera::Orientation::Rotate270Mirror }, + { "Rotate90", libcamera::Orientation::Rotate90 }, + }; + + std::string orientation = eventScalarValue(event); + + auto it = orientations.find(orientation); + if (it == orientations.end()) { + std::cerr << "Invalid orientation '" << orientation + << "' in capture script" << std::endl; + return -EINVAL; + } + + orientation_ = it->second; + + return 0; +} + +int CaptureScript::parseStream(EventPtr event, unsigned int index) +{ + if (!checkEvent(event, YAML_MAPPING_START_EVENT)) + return -EINVAL; + + StreamConfiguration config; + while (1) { + event = nextEvent(); + if (!event) + return -EINVAL; + if (event->type == YAML_MAPPING_END_EVENT) + break; + + std::string key = eventScalarValue(event); + + event = nextEvent(); + if (!event) + return -EINVAL; + if (event->type == YAML_MAPPING_END_EVENT) + break; + + std::string value = eventScalarValue(event); + + if (key == "pixelFormat") { + config.pixelFormat = libcamera::PixelFormat::fromString(value); + } else if (key == "size") { + auto split = value.find("x"); + if (split == std::string::npos) { + std::cerr << "Invalid size '" << value + << "' in stream configuration " + << index << std::endl; + } + + std::string width = value.substr(0, split); + std::string height = value.substr(split + 1); + config.size = Size(std::stoi(width), std::stoi(height)); + } else if (key == "stride") { + config.stride = std::stoi(value); + } else if (key == "frameSize") { + config.frameSize = std::stoi(value); + } else if (key == "bufferCount") { + config.bufferCount = std::stoi(value); + } else if (key == "colorSpace") { + config.colorSpace = libcamera::ColorSpace::fromString(value); + } else { + std::cerr << "Unknown key-value pair '" + << key << "': '" << value + << "' in stream configuration " + << index << std::endl; + return -EINVAL; + } + } + + streamConfigs_.push_back(config); + + return 0; +} + +int CaptureScript::parseStreams(EventPtr event) +{ + if (!checkEvent(event, YAML_SEQUENCE_START_EVENT)) + return -EINVAL; + + unsigned int index = 0; + while (1) { + event = nextEvent(); + if (!event) + return -EINVAL; + if (event->type == YAML_SEQUENCE_END_EVENT) + return 0; + + if (event->type == YAML_MAPPING_START_EVENT) { + parseStream(std::move(event), index++); + continue; + } else { + std::cerr << "UNKNOWN TYPE" << std::endl; + return -EINVAL; + } + } + + return 0; +} + +int CaptureScript::parseConfiguration() +{ + int ret; + + EventPtr event = nextEvent(YAML_MAPPING_START_EVENT); + if (!event) + return -EINVAL; + + while (1) { + event = nextEvent(); + if (!event) + return -EINVAL; + if (event->type == YAML_MAPPING_END_EVENT) + break; + + std::string key = eventScalarValue(event); + + event = nextEvent(); + if (!event) + return -EINVAL; + if (event->type == YAML_MAPPING_END_EVENT) + break; + + /* TODO Load sensor configuration */ + if (key == "orientation") { + ret = parseOrientation(std::move(event)); + if (ret) + return ret; + } else if (key == "streams") { + ret = parseStreams(std::move(event)); + if (ret) + return ret; + } + } + + return 0; +} + +void CaptureScript::populateConfiguration(CameraConfiguration *configuration) const +{ + if (!configuration) + return; + + configuration->orientation = orientation_; + + for (unsigned int i = 0; i < streamConfigs_.size(); i++) + (*configuration)[i] = streamConfigs_[i]; +} + std::string CaptureScript::parseScalar() { EventPtr event = nextEvent(YAML_SCALAR_EVENT); diff --git a/src/apps/cam/capture_script.h b/src/apps/cam/capture_script.h index 294b920363ba..4ba862d742cf 100644 --- a/src/apps/cam/capture_script.h +++ b/src/apps/cam/capture_script.h @@ -26,6 +26,7 @@ public: const libcamera::ControlList &frameControls(unsigned int frame); + void populateConfiguration(libcamera::CameraConfiguration *configuration) const; private: struct EventDeleter { void operator()(yaml_event_t *event) const @@ -43,6 +44,9 @@ private: unsigned int loop_; bool valid_; + libcamera::Orientation orientation_; + std::vector streamConfigs_; + EventPtr nextEvent(yaml_event_type_t expectedType = YAML_NO_EVENT); bool checkEvent(const EventPtr &event, yaml_event_type_t expectedType) const; static std::string eventScalarValue(const EventPtr &event); @@ -55,6 +59,10 @@ private: int parseFrames(); int parseFrame(EventPtr event); int parseControl(EventPtr event, libcamera::ControlList &controls); + int parseConfiguration(); + int parseOrientation(EventPtr event); + int parseStreams(EventPtr event); + int parseStream(EventPtr event, unsigned int index); libcamera::ControlValue parseScalarControl(const libcamera::ControlId *id, const std::string repr); From patchwork Mon Dec 2 13:34:03 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 22150 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 37962C3295 for ; Mon, 2 Dec 2024 13:34:20 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id DECB066080; Mon, 2 Dec 2024 14:34:16 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="uZH4RpWY"; 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 58B056605F for ; Mon, 2 Dec 2024 14:34:12 +0100 (CET) Received: from ideasonboard.com (mob-5-90-236-68.net.vodafone.it [5.90.236.68]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 88ED5514; Mon, 2 Dec 2024 14:33:45 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1733146425; bh=ps1AvkkE4aqDTfJCOam9fPZFGvmUB027lVxleBkIzNs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=uZH4RpWYaQ4/ryNdpeQcpFM+CHSU9/napxKHn36IGo3+oafyhMmmaKaOcEKmUZH2q B692WrOgGSjxgjm9BOcWICblRlkwI7De/kiYUcyrUPIDx0w9i2kXUxlkuv6UOszzqv Cr47H63jsy8uZknXgQr07xmd9wub/V8CJKOTcT+k= From: Jacopo Mondi To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v2 4/4] [DNI] libcamera: pipeline_handler: Break the Yaml Emitter Date: Mon, 2 Dec 2024 14:34:03 +0100 Message-ID: <20241202133404.41431-5-jacopo.mondi@ideasonboard.com> X-Mailer: git-send-email 2.47.0 In-Reply-To: <20241202133404.41431-1-jacopo.mondi@ideasonboard.com> References: <20241202133404.41431-1-jacopo.mondi@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" Break the pipeline handler with an invalid API usage sequence in pipeline_handler.cpp ASSERT() for the presence of a valid parent_ reference in the YamlOutput class hierarcy to trigger a visible error to users. FATAL default yaml_emitter.cpp:578 assertion "parent_" failed in dict() Backtrace: libcamera::YamlList::dict()+0xb4 (../src/libcamera/yaml_emitter.cpp:580) Signed-off-by: Jacopo Mondi --- src/libcamera/pipeline_handler.cpp | 1 + src/libcamera/yaml_emitter.cpp | 42 +++++------------------------- 2 files changed, 7 insertions(+), 36 deletions(-) diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp index e1ff6e6a5ce9..963a98e15894 100644 --- a/src/libcamera/pipeline_handler.cpp +++ b/src/libcamera/pipeline_handler.cpp @@ -818,6 +818,7 @@ void PipelineHandler::dumpConfiguration(const std::set &streams, /* \todo Dump Sensor configuration */ YamlList streamsList = configurationDict.list("streams"); + YamlList thatsanono = configurationDict.list("shouldntbehere"); for (const auto &stream : streams) { const StreamConfiguration &streamConfig = stream->configuration(); diff --git a/src/libcamera/yaml_emitter.cpp b/src/libcamera/yaml_emitter.cpp index c0629e249c57..ed0367717e11 100644 --- a/src/libcamera/yaml_emitter.cpp +++ b/src/libcamera/yaml_emitter.cpp @@ -561,12 +561,7 @@ YamlList::~YamlList() */ YamlList YamlList::list() { - if (!parent_) { - LOG(YamlEmitter, Error) - << "Invalid usage of the YamlEmitter API. " - << " The YAML output might not be correct."; - return {}; - } + ASSERT(parent_); int ret = emitSequenceStart(); if (ret) @@ -580,12 +575,7 @@ YamlList YamlList::list() */ YamlDict YamlList::dict() { - if (!parent_) { - LOG(YamlEmitter, Error) - << "Invalid usage of the YamlEmitter API. " - << " The YAML output might not be correct."; - return {}; - } + ASSERT(parent_); int ret = emitMappingStart(); if (ret) @@ -600,12 +590,7 @@ YamlDict YamlList::dict() */ void YamlList::scalar(std::string_view scalar) { - if (!parent_) { - LOG(YamlEmitter, Error) - << "Invalid usage of the YamlEmitter API. " - << " The YAML output might not be correct."; - return; - } + ASSERT(parent_); emitScalar(scalar); } @@ -652,12 +637,7 @@ YamlDict::~YamlDict() */ YamlList YamlDict::list(std::string_view key) { - if (!parent_) { - LOG(YamlEmitter, Error) - << "Invalid usage of the YamlEmitter API. " - << " The YAML output might not be correct."; - return {}; - } + ASSERT(parent_); int ret = emitScalar(key); if (ret) @@ -677,12 +657,7 @@ YamlList YamlDict::list(std::string_view key) */ YamlDict YamlDict::dict(std::string_view key) { - if (!parent_) { - LOG(YamlEmitter, Error) - << "Invalid usage of the YamlEmitter API. " - << " The YAML output might not be correct."; - return {}; - } + ASSERT(parent_); int ret = emitScalar(key); if (ret) @@ -702,12 +677,7 @@ YamlDict YamlDict::dict(std::string_view key) */ void YamlDict::scalar(std::string_view key, std::string_view scalar) { - if (!parent_) { - LOG(YamlEmitter, Error) - << "Invalid usage of the YamlEmitter API. " - << " The YAML output might not be correct."; - return; - } + ASSERT(parent_); int ret = emitScalar(key); if (ret)