new file mode 100644
@@ -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
new file mode 100644
@@ -0,0 +1,447 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * capture_script.cpp - Capture session configuration script
+ */
+
+#include "capture_script.h"
+
+#include <iostream>
+#include <stdio.h>
+#include <stdlib.h>
+
+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("<b>Start Block (Sequence)</b>");
+ break;
+ case YAML_BLOCK_ENTRY_TOKEN:
+ puts("<b>Start Block (Entry)</b>");
+ break;
+ case YAML_BLOCK_END_TOKEN:
+ puts("<b>End block</b>");
+ 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> 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<char *>(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<char *>(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<bool>(val);
+ break;
+ }
+ case ControlTypeByte: {
+ uint8_t val = strtol(repr.c_str(), NULL, 10);
+ value.set<uint8_t>(val);
+ break;
+ }
+ case ControlTypeInteger32: {
+ int32_t val = strtol(repr.c_str(), NULL, 10);
+ value.set<int32_t>(val);
+ break;
+ }
+ case ControlTypeInteger64: {
+ int64_t val = strtoll(repr.c_str(), NULL, 10);
+ value.set<int64_t>(val);
+ break;
+ }
+ case ControlTypeFloat: {
+ float val = strtof(repr.c_str(), NULL);
+ value.set<float>(val);
+ break;
+ }
+ case ControlTypeString: {
+ value.set<std::string>(repr);
+ break;
+ }
+ case ControlTypeRectangle:
+ /* \todo Parse rectangles. */
+ break;
+ case ControlTypeSize:
+ /* \todo Parse Sizes. */
+ break;
+ }
+
+ return value;
+}
new file mode 100644
@@ -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 <experimental/filesystem>
+namespace std {
+namespace filesystem = std::experimental::filesystem;
+}
+#else
+#include <filesystem>
+#endif
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include <libcamera/camera.h>
+#include <libcamera/controls.h>
+
+#include <yaml.h>
+
+class CaptureScript
+{
+public:
+ CaptureScript(std::shared_ptr<libcamera::Camera> camera,
+ std::string fileName);
+
+ bool valid() const { return valid_; }
+
+ const libcamera::ControlList &frameControls(unsigned int frame);
+
+private:
+ std::map<std::string, const libcamera::ControlId *> controls_;
+ std::map<unsigned int, libcamera::ControlList> frameControls_;
+ std::shared_ptr<libcamera::Camera> 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);
+};
@@ -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)
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 <jacopo@jmondi.org> --- 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