From patchwork Wed Nov 6 17:58:51 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 21819 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 995F9C324E for ; Wed, 6 Nov 2024 17:59:13 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 26A9B65435; Wed, 6 Nov 2024 18:59:13 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="wPsOxB7Y"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 019F665432 for ; Wed, 6 Nov 2024 18:59:10 +0100 (CET) Received: from ideasonboard.com (93-61-96-190.ip145.fastwebnet.it [93.61.96.190]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7BAE6C80; Wed, 6 Nov 2024 18:59:01 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1730915941; bh=jFTln8oGrWCy1Ik8GlmMhkgfS8bgoZ1LY5c5xZI3Pgs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=wPsOxB7YSXKNoLRvO7tIzOA21QkEg9uTT09a0WAmtKg8Su+g/6zRdYcehJ+QvPShH GZT+ryWZwAwFnC305Z+zjUG80Pl8jiLTs7yNhgOVlwD2KmqAb98N0JiSWVl5WkINXu KZUZR/NH4+6tb2Ft2w/5OuOfK6X7k3qYeq70/ebc= From: Jacopo Mondi To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH 1/3] libcamera: Implement YamlEmitter Date: Wed, 6 Nov 2024 18:58:51 +0100 Message-ID: <20241106175901.83960-2-jacopo.mondi@ideasonboard.com> X-Mailer: git-send-email 2.47.0 In-Reply-To: <20241106175901.83960-1-jacopo.mondi@ideasonboard.com> References: <20241106175901.83960-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 | 164 ++++++ src/libcamera/meson.build | 1 + src/libcamera/yaml_emitter.cpp | 577 ++++++++++++++++++++++ 4 files changed, 743 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 1c5eef9cab80..7533b075fde2 100644 --- a/include/libcamera/internal/meson.build +++ b/include/libcamera/internal/meson.build @@ -41,6 +41,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..78196a7f147f --- /dev/null +++ b/include/libcamera/internal/yaml_emitter.h @@ -0,0 +1,164 @@ +/* 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(YamlEmitter *emitter) + : emitter_(emitter) + { + } + + YamlOutput &operator=(YamlOutput &&other) + { + emitter_ = other.emitter_; + other.emitter_ = nullptr; + + return *this; + } + + int emitScalar(std::string_view scalar); + int emitMappingStart(); + int emitMappingEnd(); + int emitSequenceStart(); + int emitSequenceEnd(); + + YamlScalar scalar(); + YamlDict dict(); + YamlList list(); + + YamlEmitter *emitter_ = nullptr; + yaml_event_t event_; + +private: + LIBCAMERA_DISABLE_COPY(YamlOutput) +}; + +class YamlRoot : public YamlOutput +{ +public: + YamlRoot() = 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()), emitterRoot_(std::move(emitter)) + { + } + + std::unique_ptr emitterRoot_; +}; + +class YamlScalar : public YamlOutput +{ +public: + YamlScalar() = default; + ~YamlScalar() = default; + + void operator=(std::string_view scalar); + +private: + friend class YamlOutput; + + YamlScalar(YamlEmitter *emitter); +}; + +class YamlList : public YamlOutput +{ +public: + YamlList() = default; + ~YamlList(); + + YamlList &operator=(YamlList &&other) = default; + + YamlList list(); + YamlDict dict(); + void scalar(std::string_view scalar); + +private: + friend class YamlOutput; + + YamlList(YamlEmitter *emitter); +}; + +class YamlDict : public YamlOutput +{ +public: + YamlDict() = default; + ~YamlDict(); + + YamlDict &operator=(YamlDict &&other) = default; + + YamlList list(std::string_view key); + YamlDict dict(std::string_view key); + + YamlScalar operator[](std::string_view key); + +private: + friend class YamlOutput; + + YamlDict(YamlEmitter *emitter); +}; + +} /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index aa9ab0291854..5b8af4103085 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -51,6 +51,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..fa3de91ce988 --- /dev/null +++ b/src/libcamera/yaml_emitter.cpp @@ -0,0 +1,577 @@ +/* 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. + * + * 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 populating it with 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 + * instances, using the YamlEmitter::root() function, that users can populate + * with lists, dictionaries and scalars. + */ + +YamlEmitter::YamlEmitter(const std::string &path) +{ + std::string filePath(path); + file_.setFileName(filePath); + file_.open(File::OpenModeFlag::WriteOnly); +} + +/** + * \brief Destroy the YamlEmitter + */ +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 scalers, 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 problem"; + 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_, NULL, NULL, NULL, 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 YamlScalar, + * 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. + * + * Instances of this class cannot be instantiated directly by applications. + */ + +/** + * \fn YamlOutput::valid() + * \brief Check if a YamlOutput instance has been correctly initialized + * \return True if the instance has been initialized, false otherwise + */ + +/** + * \fn YamlOutput::YamlOutput(YamlEmitter *emitter) + * \brief Create a YamlOutput instance with an associated emitter + * \param[in] emitter The YAML emitter + */ + +/** + * \fn YamlOutput &YamlOutput::operator=(YamlOutput &&other) + * \brief The move-assignment operator + * \param[in] other The instance to be moved + */ + +/** + * \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()); + yaml_scalar_event_initialize(&emitter_->event_, NULL, NULL, 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_, NULL, NULL, + 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_, NULL, NULL, + 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 scalar instance + * \return An instance of YamlScalar + */ +YamlScalar YamlOutput::scalar() +{ + return YamlScalar(emitter_); +} + +/** + * \brief Create a dictionary instance + * \return An instance of YamlDict + */ +YamlDict YamlOutput::dict() +{ + return YamlDict(emitter_); +} + +/** + * \brief Create a list instance + * \return An instance of YamlList + */ +YamlList YamlOutput::list() +{ + return YamlList(emitter_); +} + +/** + * \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 + */ + +/** + * \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 by the users of this class. In order to start using + * and populating the YamlRoot instance a valid and initialized instance, + * created using the YamlEmitter::root() function, has to be move-assigned to + * the non-initialized instance. + * + * \code + YamlRoot root; + + root = YamlEmitter::root("/path/to/yaml/file.yml"); + \endcode + */ + +/** + * \brief Delete a YamlRoot + */ +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) + * \brief Move assignment operator + * + * Move-assign a YamlRoot instance. This function is typically used to assign an + * initialized instance returned by YamlEmitter::root() to a non-initialized + * one. + * + * \return A reference to this instance of YamlRoot + */ + +/** + * \copydoc YamlOutput::dict() + */ +YamlDict YamlRoot::dict() +{ + int ret = emitMappingStart(); + if (ret) + return {}; + + return YamlOutput::dict(); +} + +/** + * \copydoc YamlOutput::list() + */ +YamlList YamlRoot::list() +{ + int ret = emitSequenceStart(); + if (ret) + return {}; + + return YamlOutput::list(); +} + +/** + * \class YamlScalar + * + * A YamlScalar can be assigned to an std::string_view to emit them as YAML + * elements. + */ + +/** + * \brief Create a YamlScalar instance + */ +YamlScalar::YamlScalar(YamlEmitter *emitter) + : YamlOutput(emitter) +{ +} + +/** + * \brief Emit \a scalar as a YAML scalar + * \param[in] scalar The element to emit in the YAML output + */ +void YamlScalar::operator=(std::string_view scalar) +{ + emitScalar(scalar); +} + +/** + * \class YamlList + * + * A YamlList can be populated with scalars and allows to create nested lists + * and dictionaries. + */ + +/** + * \brief Create a YamlList + */ +YamlList::YamlList(YamlEmitter *emitter) + : YamlOutput(emitter) +{ +} + +/** + * \brief Destroy a YamlList instance + */ +YamlList::~YamlList() +{ + emitSequenceEnd(); +} + +/** + * \fn YamlList &YamlList::operator=(YamlList &&other) + * \brief Move-assignment operator + * \param[inout] other The instance to move + */ + +/** + * \brief Append \a scalar to the list + * \param[in] scalar The element to append to the list + */ +void YamlList::scalar(std::string_view scalar) +{ + emitScalar(scalar); +} + +/** + * \copydoc YamlOutput::list() + */ +YamlList YamlList::list() +{ + int ret = emitSequenceStart(); + if (ret) + return {}; + + return YamlOutput::list(); +} + +/** + * \copydoc YamlOutput::dict() + */ +YamlDict YamlList::dict() +{ + int ret = emitMappingStart(); + if (ret) + return {}; + + return YamlOutput::dict(); +} + +/** + * \class YamlDict + * + * A YamlDict can be populated with scalars using operator[] and allows to + * create other lists and dictionaries associated with a key. + */ + +/** + * \fn YamlDict::YamlDict() + * \brief Create a non-initialized instance of a YamlDict + */ + +YamlDict::YamlDict(YamlEmitter *emitter) + : YamlOutput(emitter) +{ +} + +/** + * \brief Destroy a YamlDict instance + */ +YamlDict::~YamlDict() +{ + emitMappingEnd(); +} + +/** + * \fn YamlDict &YamlDict::operator=(YamlDict &&other) + * \brief Move-assignment operator + * \param[inout] other The instance to move + */ + +/** + * \copydoc YamlOutput::list() + */ +YamlList YamlDict::list(std::string_view key) +{ + int ret = emitScalar(key); + if (ret) + return {}; + + ret = emitSequenceStart(); + if (ret) + return {}; + + return YamlOutput::list(); +} + +/** + * \copydoc YamlOutput::dict() + */ +YamlDict YamlDict::dict(std::string_view key) +{ + int ret = emitScalar(key); + if (ret) + return {}; + + ret = emitMappingStart(); + if (ret) + return {}; + + return YamlOutput::dict(); +} + +/** + * \brief Create a scalar associated with \a key in the dictionary + * \param[in] key The key associated with the newly created scalar + * \return A YamlScalar that application can use to output text + */ +YamlScalar YamlDict::operator[](std::string_view key) +{ + int ret = emitScalar(key); + if (ret) + return {}; + + return YamlOutput::scalar(); +} + +} /* namespace libcamera */ From patchwork Wed Nov 6 17:58:52 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 21820 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 8CA56BE173 for ; Wed, 6 Nov 2024 17:59:15 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D325D65437; Wed, 6 Nov 2024 18:59:13 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Nk/qfJC8"; 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 42C9165436 for ; Wed, 6 Nov 2024 18:59:10 +0100 (CET) Received: from ideasonboard.com (93-61-96-190.ip145.fastwebnet.it [93.61.96.190]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D13DD475; Wed, 6 Nov 2024 18:59:01 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1730915942; bh=xLJFCIv1YwnDXvk5Oodq7BJFfBd99jga0gCntxrLLdw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Nk/qfJC8nRWYgjkJB/xvaI7XJ0BZe12O0kffwZcOPjNcketQNVoQSzEHBxnzZs1+L UY8wb18T39Qci+N2a7gc8Zve4UIq/xv2PVH8jn8uVKYSepmonNP0Mrz0IhPrWWkpqC jg1i6vMB7vqQXnLjjn6P1H4moXOVSM658UItdscg= From: Jacopo Mondi To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder Subject: [PATCH 2/3] pipeline: Add support for dumping capture script and metadata Date: Wed, 6 Nov 2024 18:58:52 +0100 Message-ID: <20241106175901.83960-3-jacopo.mondi@ideasonboard.com> X-Mailer: git-send-email 2.47.0 In-Reply-To: <20241106175901.83960-1-jacopo.mondi@ideasonboard.com> References: <20241106175901.83960-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 0d38080369c5..89d10b373cfa 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; @@ -68,6 +73,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); @@ -81,6 +89,11 @@ protected: CameraManager *manager_; private: + enum DumpMode { + Controls, + Metadata, + }; + void unlockMediaDevices(); void mediaDeviceDisconnected(MediaDevice *media); @@ -89,6 +102,8 @@ private: void doQueueRequest(Request *request); void doQueueRequests(); + void dumpRequest(Request *request, DumpMode mode); + std::vector> mediaDevices_; std::vector> cameras_; @@ -97,6 +112,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 7507e9ddae77..9d28b1202397 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 e5940469127e..34111589ab22 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 @@ -464,6 +465,8 @@ void PipelineHandler::doQueueRequest(Request *request) request->_d()->sequence_ = data->requestSequence_++; + dumpRequest(request, DumpMode::Controls); + if (request->_d()->cancelled_) { completeRequest(request); return; @@ -555,6 +558,8 @@ void PipelineHandler::completeRequest(Request *request) request->_d()->complete(); + dumpRequest(request, DumpMode::Metadata); + Camera::Private *data = camera->_d(); while (!data->queuedRequests_.empty()) { @@ -758,6 +763,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["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["pixelformat"] = streamConfig.pixelFormat.toString(); + yamlStream["size"] = streamConfig.size.toString(); + yamlStream["stride"] = std::to_string(streamConfig.stride); + yamlStream["frameSize"] = std::to_string(streamConfig.frameSize); + yamlStream["bufferCount"] = std::to_string(streamConfig.bufferCount); + + if (streamConfig.colorSpace) + yamlStream["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[ctrlId->name()] = pair.second.toString(); + } +} + /** * \class PipelineHandlerFactoryBase * \brief Base class for pipeline handler factories From patchwork Wed Nov 6 17:58:53 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 21821 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 BD05DC32CE for ; Wed, 6 Nov 2024 17:59:16 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id EE4746545E; Wed, 6 Nov 2024 18:59:14 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="TEjGt3Ok"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id B644865446 for ; Wed, 6 Nov 2024 18:59:10 +0100 (CET) Received: from ideasonboard.com (93-61-96-190.ip145.fastwebnet.it [93.61.96.190]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 33F46C80; Wed, 6 Nov 2024 18:59:02 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1730915942; bh=LzkEqwnvZ8o5189gKCovTM9dpHA1Kq316oRZwbMntlc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=TEjGt3OkmSGjCcLsfxiJoYRTgyknHp6Qz+bksRxrxcD1uF28uOXXtjTZHTgwUCyl8 hpuFqI5x8GBLdI88PdocyfIm8tP13y5wk0qBG9f/tLmlDUqHr7gUH/jFmHggWHtBY5 ucbAM5GZYL9Rro+HRMQyGr9BNtOv3fX2pjhR2e3o= From: Jacopo Mondi To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder Subject: [PATCH 3/3] apps: cam: Add support for loading configuration from capture script Date: Wed, 6 Nov 2024 18:58:53 +0100 Message-ID: <20241106175901.83960-4-jacopo.mondi@ideasonboard.com> X-Mailer: git-send-email 2.47.0 In-Reply-To: <20241106175901.83960-1-jacopo.mondi@ideasonboard.com> References: <20241106175901.83960-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 Signed-off-by: Jacopo Mondi --- 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..7f166f45657e 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") { + unsigned int 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);