From patchwork Wed May 18 17:19:17 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 15975 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 CC1C9C326C for ; Wed, 18 May 2022 17:19:33 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id EB43D6565C; Wed, 18 May 2022 19:19:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1652894370; bh=FYmJbgOwMo3b8+Y4Ss0p3R4ccq6wZMoksZrM8tx0Tl8=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=3L5s6Ffiav5n3H08HGcy9n4HTyxbvcesioF5LAQ7WwJywP0FUfP4a+qFxux9hckyp 3WZ8ks4FKfaIWhvPJHibmssnm4JepY0iOBL/DRN+AvYBjN6vAvOFIcV72h8jKDYFiX eROiw2kctZpBU45N1TB0vH6rh7m/pBk2N0KmRjx0QzGdc66hIAeHV4Z93akFgFEvc7 OXOUgm555c1Ht/xYifa5GWJSpQtWdPysf+8J+222an4mFx/jTbrj5Lh3y01eO/lHFd YFWRKl6a/WpY1Kv7Rdqhs6Kg3bvZVVxSJ/qlgk7C9XcoZj94jo/GHhJ1t0mDoT5ThU DDyjDYEBWUD2A== Received: from relay1-d.mail.gandi.net (relay1-d.mail.gandi.net [217.70.183.193]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5D00465656 for ; Wed, 18 May 2022 19:19:29 +0200 (CEST) Received: (Authenticated sender: jacopo@jmondi.org) by mail.gandi.net (Postfix) with ESMTPSA id 891FD240012; Wed, 18 May 2022 17:19:28 +0000 (UTC) To: libcamera-devel@lists.libcamera.org Date: Wed, 18 May 2022 19:19:17 +0200 Message-Id: <20220518171921.244168-2-jacopo@jmondi.org> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20220518171921.244168-1-jacopo@jmondi.org> References: <20220518171921.244168-1-jacopo@jmondi.org> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 1/5] cam: Add a parser for capture scripts 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: , X-Patchwork-Original-From: Jacopo Mondi via libcamera-devel From: Jacopo Mondi Reply-To: Jacopo Mondi Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a parser class to the cam test application to control the capture operations through a yaml script. The capture script currently allow to specify a list of controls and their associated values to be applied per-frame. Also add a trivial capture script example to showcase the intended script structure. Signed-off-by: Jacopo Mondi --- src/cam/capture-script.yaml | 44 ++++ src/cam/capture_script.cpp | 447 ++++++++++++++++++++++++++++++++++++ src/cam/capture_script.h | 57 +++++ src/cam/meson.build | 2 + 4 files changed, 550 insertions(+) create mode 100644 src/cam/capture-script.yaml create mode 100644 src/cam/capture_script.cpp create mode 100644 src/cam/capture_script.h diff --git a/src/cam/capture-script.yaml b/src/cam/capture-script.yaml new file mode 100644 index 000000000000..48eac5fedd46 --- /dev/null +++ b/src/cam/capture-script.yaml @@ -0,0 +1,44 @@ +# Capture scrip example +# +# A capture script allows to associate a list of controls and their values +# to frame numbers. + +# \todo Formally define the capture script structure with a schema + +# Notes: +# - Controls have to be specified by name, as defined in the +# libcamera::controls:: enumeration +# - Controls not supported by the camera currently operated are ignored +# - Frame numbers shall be monotonically incrementing, gaps are allowed + +# Example: +frames: + - 1: + Brightness: 0.0 + + - 40: + Brightness: 0.2 + + - 80: + Brightness: 0.4 + + - 120: + Brightness: 0.8 + + - 160: + Brightness: 0.4 + + - 200: + Brightness: 0.2 + + - 240: + Brightness: 0.0 + + - 280: + Brightness: -0.2 + + - 300: + Brightness: -0.4 + + - 340: + Brightness: -0.8 diff --git a/src/cam/capture_script.cpp b/src/cam/capture_script.cpp new file mode 100644 index 000000000000..d153dac7b0de --- /dev/null +++ b/src/cam/capture_script.cpp @@ -0,0 +1,447 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * capture_script.cpp - Capture session configuration script + */ + +#include "capture_script.h" + +#include +#include +#include + +using namespace libcamera; + +namespace { + +/* + * Schema error debug function + * From: https://www.wpsoftware.net/andrew/pages/libyaml.html + */ +void printToken(yaml_token_t &token) +{ + std::string type; + + switch (token.type) { + case YAML_KEY_TOKEN: + printf("(Key token) "); + break; + case YAML_VALUE_TOKEN: + printf("(Value token) "); + break; + case YAML_BLOCK_SEQUENCE_START_TOKEN: + puts("Start Block (Sequence)"); + break; + case YAML_BLOCK_ENTRY_TOKEN: + puts("Start Block (Entry)"); + break; + case YAML_BLOCK_END_TOKEN: + puts("End block"); + break; + case YAML_BLOCK_MAPPING_START_TOKEN: + puts("[Block mapping]"); + break; + case YAML_SCALAR_TOKEN: + printf("scalar %s \n", token.data.scalar.value); + break; + default: + printf("Token type %u \n", token.type); + break; + } + std::cerr << std::endl; +} + +} /* namespace */ + +CaptureScript::CaptureScript(std::shared_ptr camera, std::string fileName) + : camera_(camera), scriptPath_(fileName), valid_(false) +{ + if (!std::filesystem::is_regular_file(scriptPath_)) { + std::cerr << "Capture script file not found: " + << scriptPath_ << std::endl; + return; + } + + FILE *fh = fopen(scriptPath_.c_str(), "r"); + if (!fh) { + int ret = -errno; + std::cerr << "Failed to open capture script " << scriptPath_ + << ": " << strerror(-ret) << std::endl; + return; + } + + /* + * Map the camera's controls to their name so that they can be + * easily identified when parsing the script file. + */ + for (const auto &[control, info] : camera_->controls()) + controls_[control->name()] = control; + + int ret = parseScript(fh); + fclose(fh); + if (ret) + return; + + valid_ = true; +} + +/* Retrieve the control list associated with a frame number. */ +const ControlList &CaptureScript::frameControls(unsigned int frame) +{ + static ControlList controls{}; + + auto it = frameControls_.find(frame); + if (it == frameControls_.end()) + return controls; + + return it->second; +} + +int CaptureScript::parseScript(FILE *script) +{ + int ret = yaml_parser_initialize(&parser_); + if (!ret) { + std::cerr << "Failed to initialize yaml parser" << std::endl; + return ret; + } + + yaml_parser_set_input_file(&parser_, script); + + yaml_token_t token; + yaml_parser_scan(&parser_, &token); + if (token.type != YAML_STREAM_START_TOKEN) { + std::cerr << "Configuration file not valid" << std::endl; + printToken(token); + yaml_token_delete(&token); + yaml_parser_delete(&parser_); + return -EINVAL; + } + yaml_token_delete(&token); + + yaml_parser_scan(&parser_, &token); + if (token.type != YAML_BLOCK_MAPPING_START_TOKEN) { + std::cerr << "Configuration file not valid" << std::endl; + printToken(token); + yaml_token_delete(&token); + yaml_parser_delete(&parser_); + return -EINVAL; + } + yaml_token_delete(&token); + + /* Capture script shall start with the "frames" key. */ + std::string section = parseKey(); + if (section != "frames") { + std::cerr << "Unsupported section: " << section << std::endl; + yaml_token_delete(&token); + yaml_parser_delete(&parser_); + return -EINVAL; + } + + yaml_parser_scan(&parser_, &token); + if (token.type != YAML_VALUE_TOKEN) { + std::cerr << "Configuration file not valid" << std::endl; + printToken(token); + yaml_token_delete(&token); + yaml_parser_delete(&parser_); + return -EINVAL; + } + yaml_token_delete(&token); + + parseFrames(); + + /* Consume the tokens on the stack until we don't hit an STREAM_END */ + do { + ret = 0; + + yaml_parser_scan(&parser_, &token); + switch (token.type) { + case YAML_STREAM_END_TOKEN: + ret = -ENOENT; + [[fallthrough]]; + default: + yaml_token_delete(&token); + break; + } + } while (ret >= 0); + + return 0; +} + +int CaptureScript::parseFrames() +{ + int ret; + + yaml_token_t token; + yaml_parser_scan(&parser_, &token); + if (token.type != YAML_BLOCK_SEQUENCE_START_TOKEN) { + std::cerr << "Configuration file not valid" << std::endl; + printToken(token); + yaml_token_delete(&token); + yaml_parser_delete(&parser_); + return -EINVAL; + } + yaml_token_delete(&token); + + do { + yaml_parser_scan(&parser_, &token); + switch (token.type) { + case YAML_BLOCK_ENTRY_TOKEN: + ret = parseFrame(); + yaml_token_delete(&token); + break; + case YAML_BLOCK_END_TOKEN: + ret = -ENODEV; + yaml_token_delete(&token); + break; + default: + ret = -EINVAL; + std::cerr << "Configuration file not valid" << std::endl; + printToken(token); + yaml_token_delete(&token); + yaml_parser_delete(&parser_); + } + } while (ret == 0); + + return ret != -ENODEV ? ret : 0; +} + +int CaptureScript::parseFrame() +{ + yaml_token_t token; + + yaml_parser_scan(&parser_, &token); + if (token.type != YAML_BLOCK_MAPPING_START_TOKEN) { + std::cerr << "Configuration file not valid" << std::endl; + printToken(token); + yaml_token_delete(&token); + yaml_parser_delete(&parser_); + return -EINVAL; + } + yaml_token_delete(&token); + + std::string key = parseKey(); + unsigned int frameId = atoi(key.c_str()); + + frameControls_[frameId] = parseControls(); + + return 0; +} + +ControlList CaptureScript::parseControls() +{ + ControlList controls{}; + yaml_token_t token; + + yaml_parser_scan(&parser_, &token); + if (token.type != YAML_VALUE_TOKEN) { + std::cerr << "Configuration file not valid" << std::endl; + printToken(token); + yaml_token_delete(&token); + yaml_parser_delete(&parser_); + return controls; + } + yaml_token_delete(&token); + + /* + * Whenever we get an unbalanced block_end token, we're done + * parsing the controls. + */ + int blockCounter = 0; + + do { + yaml_parser_scan(&parser_, &token); + + switch (token.type) { + case YAML_BLOCK_MAPPING_START_TOKEN: + blockCounter++; + yaml_token_delete(&token); + break; + case YAML_BLOCK_END_TOKEN: + blockCounter--; + yaml_token_delete(&token); + break; + case YAML_KEY_TOKEN: { + /* We expect a value after a key. */ + std::string controlName = parseKey(token); + std::string controlRepr = parseValue(); + + /* + * If the camera does not support the control just + * ignore it. + */ + auto controlIt = controls_.find(controlName); + if (controlIt == controls_.end()) { + std::cerr << "Unsupported control: " + << controlName << std::endl; + break; + } + + const ControlId *controlId = controlIt->second; + ControlValue val = unpackControl(controlId, controlRepr); + controls.set(controlId->id(), val); + break; + } + default: + /* Error condition */ + yaml_token_delete(&token); + break; + } + } while (blockCounter >= 0); + + return controls; +} + +std::string CaptureScript::parseKey(yaml_token_t &token) +{ + /* Make sure the token type is a key and get its value. */ + if (token.type != YAML_KEY_TOKEN) { + std::cerr << "Configuration file not valid" << std::endl; + printToken(token); + yaml_token_delete(&token); + yaml_parser_delete(&parser_); + return ""; + } + yaml_token_delete(&token); + + yaml_parser_scan(&parser_, &token); + if (token.type != YAML_SCALAR_TOKEN) { + yaml_token_delete(&token); + return ""; + } + + std::string value(reinterpret_cast(token.data.scalar.value), + token.data.scalar.length); + yaml_token_delete(&token); + + return value; +} + +std::string CaptureScript::parseKey() +{ + yaml_token_t token; + + yaml_parser_scan(&parser_, &token); + return parseKey(token); +} + +std::string CaptureScript::parseValue() +{ + yaml_token_t token; + + /* Make sure the token type is a value and get its content. */ + yaml_parser_scan(&parser_, &token); + if (token.type != YAML_VALUE_TOKEN) { + std::cerr << "Configuration file not valid" << std::endl; + printToken(token); + yaml_token_delete(&token); + yaml_parser_delete(&parser_); + return ""; + } + yaml_token_delete(&token); + + yaml_parser_scan(&parser_, &token); + if (token.type != YAML_SCALAR_TOKEN) { + yaml_token_delete(&token); + return ""; + } + + std::string value(reinterpret_cast(token.data.scalar.value), + token.data.scalar.length); + yaml_token_delete(&token); + + return value; +} + +void CaptureScript::unpackFailure(const ControlId *id, std::string repr) +{ + std::string ctrlType; + + switch (id->type()) { + case ControlTypeNone: + ctrlType = "none"; + break; + case ControlTypeBool: + ctrlType = "bool"; + break; + case ControlTypeByte: + ctrlType = "byte"; + break; + case ControlTypeInteger32: + ctrlType = "int32"; + break; + case ControlTypeInteger64: + ctrlType = "int64"; + break; + case ControlTypeFloat: + ctrlType = "float"; + break; + case ControlTypeString: + ctrlType = "string"; + break; + case ControlTypeRectangle: + ctrlType = "Rectangle"; + break; + case ControlTypeSize: + ctrlType = "Size"; + break; + } + + std::cerr << "Unsupported control \'" << repr <<"\'" << " for " + << ctrlType << " control " << id->name() << std::endl; +} + +ControlValue CaptureScript::unpackControl(const ControlId *id, std::string repr) +{ + ControlValue value{}; + + switch (id->type()) { + case ControlTypeNone: + break; + case ControlTypeBool: { + bool val; + + if (repr == "true") { + val = true; + } else if (repr == "false") { + val = false; + } else { + unpackFailure(id, repr); + return value; + } + + value.set(val); + break; + } + case ControlTypeByte: { + uint8_t val = strtol(repr.c_str(), NULL, 10); + value.set(val); + break; + } + case ControlTypeInteger32: { + int32_t val = strtol(repr.c_str(), NULL, 10); + value.set(val); + break; + } + case ControlTypeInteger64: { + int64_t val = strtoll(repr.c_str(), NULL, 10); + value.set(val); + break; + } + case ControlTypeFloat: { + float val = strtof(repr.c_str(), NULL); + value.set(val); + break; + } + case ControlTypeString: { + value.set(repr); + break; + } + case ControlTypeRectangle: + /* \todo Parse rectangles. */ + break; + case ControlTypeSize: + /* \todo Parse Sizes. */ + break; + } + + return value; +} diff --git a/src/cam/capture_script.h b/src/cam/capture_script.h new file mode 100644 index 000000000000..39fded1250a6 --- /dev/null +++ b/src/cam/capture_script.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * capture_script.h - Capture session configuration script + */ + +#pragma once + +#if defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE < 8 +#include +namespace std { +namespace filesystem = std::experimental::filesystem; +} +#else +#include +#endif + +#include +#include +#include + +#include +#include + +#include + +class CaptureScript +{ +public: + CaptureScript(std::shared_ptr camera, + std::string fileName); + + bool valid() const { return valid_; } + + const libcamera::ControlList &frameControls(unsigned int frame); + +private: + std::map controls_; + std::map frameControls_; + std::shared_ptr camera_; + std::filesystem::path scriptPath_; + yaml_parser_t parser_; + bool valid_; + + int parseScript(FILE *script); + + libcamera::ControlList parseControls(); + int parseFrames(); + int parseFrame(); + + std::string parseKey(yaml_token_t &token); + std::string parseKey(); + std::string parseValue(); + + void unpackFailure(const libcamera::ControlId *id, std::string repr); + libcamera::ControlValue unpackControl(const libcamera::ControlId *id, + std::string repr); +}; diff --git a/src/cam/meson.build b/src/cam/meson.build index afc0ea9f397a..60ecbdca0c38 100644 --- a/src/cam/meson.build +++ b/src/cam/meson.build @@ -11,6 +11,7 @@ cam_enabled = true cam_sources = files([ 'camera_session.cpp', + 'capture_script.cpp', 'event_loop.cpp', 'file_sink.cpp', 'frame_sink.cpp', @@ -59,6 +60,7 @@ cam = executable('cam', cam_sources, libsdl2, libsdl2_image, libevent, + libyaml, ], cpp_args : cam_cpp_args, install : true)