{"id":11951,"url":"https://patchwork.libcamera.org/api/patches/11951/?format=json","web_url":"https://patchwork.libcamera.org/patch/11951/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20210415135213.94511-4-jacopo@jmondi.org>","date":"2021-04-15T13:52:11","name":"[libcamera-devel,v6,3/5] android: Add CameraHalConfig class","commit_ref":null,"pull_url":null,"state":"accepted","archived":false,"hash":"1350d2ce2a8cc311c7f9fab4667d37f5ffd46e22","submitter":{"id":3,"url":"https://patchwork.libcamera.org/api/people/3/?format=json","name":"Jacopo Mondi","email":"jacopo@jmondi.org"},"delegate":{"id":15,"url":"https://patchwork.libcamera.org/api/users/15/?format=json","username":"jmondi","first_name":"Jacopo","last_name":"Mondi","email":"jacopo@jmondi.org"},"mbox":"https://patchwork.libcamera.org/patch/11951/mbox/","series":[{"id":1936,"url":"https://patchwork.libcamera.org/api/series/1936/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=1936","date":"2021-04-15T13:52:08","name":"android: Introduce HAL configuration file","version":6,"mbox":"https://patchwork.libcamera.org/series/1936/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/11951/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/11951/checks/","tags":{},"headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id E5571BD230\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 15 Apr 2021 13:51:42 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 0672F68822;\n\tThu, 15 Apr 2021 15:51:40 +0200 (CEST)","from relay10.mail.gandi.net (relay10.mail.gandi.net\n\t[217.70.178.230])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id E295C605AE\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 15 Apr 2021 15:51:38 +0200 (CEST)","from uno.LocalDomain (93-61-96-190.ip145.fastwebnet.it\n\t[93.61.96.190]) (Authenticated sender: jacopo@jmondi.org)\n\tby relay10.mail.gandi.net (Postfix) with ESMTPSA id A4037240009\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 15 Apr 2021 13:51:38 +0000 (UTC)"],"From":"Jacopo Mondi <jacopo@jmondi.org>","To":"libcamera-devel@lists.libcamera.org","Date":"Thu, 15 Apr 2021 15:52:11 +0200","Message-Id":"<20210415135213.94511-4-jacopo@jmondi.org>","X-Mailer":"git-send-email 2.31.1","In-Reply-To":"<20210415135213.94511-1-jacopo@jmondi.org>","References":"<20210415135213.94511-1-jacopo@jmondi.org>","MIME-Version":"1.0","Subject":"[libcamera-devel] [PATCH v6 3/5] android: Add CameraHalConfig class","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Content-Type":"text/plain; charset=\"us-ascii\"","Content-Transfer-Encoding":"7bit","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"},"content":"Add a CameraHalConfig class to the Android Camera3 HAL layer.\n\nReviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\nReviewed-by: Hirokazu Honda <hiroh@chromium.org>\nSigned-off-by: Jacopo Mondi <jacopo@jmondi.org>\n---\n README.rst                        |   2 +-\n src/android/camera_hal_config.cpp | 400 ++++++++++++++++++++++++++++++\n src/android/camera_hal_config.h   |  40 +++\n src/android/meson.build           |   2 +\n 4 files changed, 443 insertions(+), 1 deletion(-)\n create mode 100644 src/android/camera_hal_config.cpp\n create mode 100644 src/android/camera_hal_config.h","diff":"diff --git a/README.rst b/README.rst\nindex c77e54b48b7a..fcf0f47f14c5 100644\n--- a/README.rst\n+++ b/README.rst\n@@ -88,7 +88,7 @@ for tracing with lttng: [optional]\n         liblttng-ust-dev python3-jinja2 lttng-tools\n \n for android: [optional]\n-        libexif libjpeg\n+        libexif libjpeg libyaml\n \n Using GStreamer plugin\n ~~~~~~~~~~~~~~~~~~~~~~\ndiff --git a/src/android/camera_hal_config.cpp b/src/android/camera_hal_config.cpp\nnew file mode 100644\nindex 000000000000..12d69a3f5f3c\n--- /dev/null\n+++ b/src/android/camera_hal_config.cpp\n@@ -0,0 +1,400 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2021, Google Inc.\n+ *\n+ * camera_hal_config.cpp - Camera HAL configuration file manager\n+ */\n+#include \"camera_hal_config.h\"\n+\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <string>\n+#include <yaml.h>\n+\n+#include <hardware/camera3.h>\n+\n+#include \"libcamera/internal/log.h\"\n+\n+using namespace libcamera;\n+\n+LOG_DEFINE_CATEGORY(HALConfig)\n+\n+class CameraHalConfig::Private : public Extensible::Private\n+{\n+\tLIBCAMERA_DECLARE_PUBLIC(CameraHalConfig)\n+\n+public:\n+\tPrivate(CameraHalConfig *halConfig);\n+\n+\tint parseConfigFile(FILE *fh, std::map<std::string, CameraConfigData> *cameras);\n+\n+private:\n+\tstd::string parseValue();\n+\tstd::string parseKey();\n+\tint parseValueBlock();\n+\tint parseCameraLocation(CameraConfigData *cameraConfigData,\n+\t\t\t\tconst std::string &location);\n+\tint parseCameraConfigData(const std::string &cameraId);\n+\tint parseCameras();\n+\tint parseEntry();\n+\n+\tyaml_parser_t parser_;\n+\tstd::map<std::string, CameraConfigData> *cameras_;\n+};\n+\n+CameraHalConfig::Private::Private(CameraHalConfig *halConfig)\n+\t: Extensible::Private(halConfig)\n+{\n+}\n+\n+std::string CameraHalConfig::Private::parseValue()\n+{\n+\tyaml_token_t token;\n+\n+\t/* Make sure the token type is a value and get its content. */\n+\tyaml_parser_scan(&parser_, &token);\n+\tif (token.type != YAML_VALUE_TOKEN) {\n+\t\tyaml_token_delete(&token);\n+\t\treturn \"\";\n+\t}\n+\tyaml_token_delete(&token);\n+\n+\tyaml_parser_scan(&parser_, &token);\n+\tif (token.type != YAML_SCALAR_TOKEN) {\n+\t\tyaml_token_delete(&token);\n+\t\treturn \"\";\n+\t}\n+\n+\tstd::string value(reinterpret_cast<char *>(token.data.scalar.value),\n+\t\t\t  token.data.scalar.length);\n+\tyaml_token_delete(&token);\n+\n+\treturn value;\n+}\n+\n+std::string CameraHalConfig::Private::parseKey()\n+{\n+\tyaml_token_t token;\n+\n+\t/* Make sure the token type is a key and get its value. */\n+\tyaml_parser_scan(&parser_, &token);\n+\tif (token.type != YAML_SCALAR_TOKEN) {\n+\t\tyaml_token_delete(&token);\n+\t\treturn \"\";\n+\t}\n+\n+\tstd::string value(reinterpret_cast<char *>(token.data.scalar.value),\n+\t\t\t  token.data.scalar.length);\n+\tyaml_token_delete(&token);\n+\n+\treturn value;\n+}\n+\n+int CameraHalConfig::Private::parseValueBlock()\n+{\n+\tyaml_token_t token;\n+\n+\t/* Make sure the next token are VALUE and BLOCK_MAPPING_START. */\n+\tyaml_parser_scan(&parser_, &token);\n+\tif (token.type != YAML_VALUE_TOKEN) {\n+\t\tyaml_token_delete(&token);\n+\t\treturn -EINVAL;\n+\t}\n+\tyaml_token_delete(&token);\n+\n+\tyaml_parser_scan(&parser_, &token);\n+\tif (token.type != YAML_BLOCK_MAPPING_START_TOKEN) {\n+\t\tyaml_token_delete(&token);\n+\t\treturn -EINVAL;\n+\t}\n+\tyaml_token_delete(&token);\n+\n+\treturn 0;\n+}\n+\n+int CameraHalConfig::Private::parseCameraLocation(CameraConfigData *cameraConfigData,\n+\t\t\t\t\t\t  const std::string &location)\n+{\n+\tif (location == \"front\")\n+\t\tcameraConfigData->facing = CAMERA_FACING_FRONT;\n+\telse if (location == \"back\")\n+\t\tcameraConfigData->facing = CAMERA_FACING_BACK;\n+\telse if (location == \"external\")\n+\t\tcameraConfigData->facing = CAMERA_FACING_EXTERNAL;\n+\telse\n+\t\treturn -EINVAL;\n+\n+\treturn 0;\n+}\n+\n+int CameraHalConfig::Private::parseCameraConfigData(const std::string &cameraId)\n+{\n+\tint ret = parseValueBlock();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/*\n+\t * Parse the camera properties and store them in a cameraConfigData\n+\t * instance.\n+\t *\n+\t * Add a safety counter to make sure we don't loop indefinitely in case\n+\t * the configuration file is malformed.\n+\t */\n+\tCameraConfigData cameraConfigData;\n+\tunsigned int sentinel = 100;\n+\tbool blockEnd = false;\n+\tyaml_token_t token;\n+\n+\tdo {\n+\t\tyaml_parser_scan(&parser_, &token);\n+\t\tswitch (token.type) {\n+\t\tcase YAML_KEY_TOKEN: {\n+\t\t\tyaml_token_delete(&token);\n+\n+\t\t\t/*\n+\t\t\t * Parse the camera property key and make sure it is\n+\t\t\t * valid.\n+\t\t\t */\n+\t\t\tstd::string key = parseKey();\n+\t\t\tstd::string value = parseValue();\n+\t\t\tif (key.empty() || value.empty())\n+\t\t\t\treturn -EINVAL;\n+\n+\t\t\tif (key == \"location\") {\n+\t\t\t\tret = parseCameraLocation(&cameraConfigData, value);\n+\t\t\t\tif (ret) {\n+\t\t\t\t\tLOG(HALConfig, Error)\n+\t\t\t\t\t\t<< \"Unknown location: \" << value;\n+\t\t\t\t\treturn -EINVAL;\n+\t\t\t\t}\n+\t\t\t} else if (key == \"rotation\") {\n+\t\t\t\tret = std::stoi(value);\n+\t\t\t\tif (ret < 0 || ret >= 360) {\n+\t\t\t\t\tLOG(HALConfig, Error)\n+\t\t\t\t\t\t<< \"Unknown rotation: \" << value;\n+\t\t\t\t\treturn -EINVAL;\n+\t\t\t\t}\n+\t\t\t\tcameraConfigData.rotation = ret;\n+\t\t\t} else {\n+\t\t\t\tLOG(HALConfig, Error)\n+\t\t\t\t\t<< \"Unknown key: \" << key;\n+\t\t\t\treturn -EINVAL;\n+\t\t\t}\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tcase YAML_BLOCK_END_TOKEN:\n+\t\t\tblockEnd = true;\n+\t\t\t[[fallthrough]];\n+\t\tdefault:\n+\t\t\tyaml_token_delete(&token);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\t--sentinel;\n+\t} while (!blockEnd && sentinel);\n+\tif (!sentinel)\n+\t\treturn -EINVAL;\n+\n+\t(*cameras_)[cameraId] = cameraConfigData;\n+\n+\treturn 0;\n+}\n+\n+int CameraHalConfig::Private::parseCameras()\n+{\n+\tint ret = parseValueBlock();\n+\tif (ret) {\n+\t\tLOG(HALConfig, Error) << \"Configuration file is not valid\";\n+\t\treturn ret;\n+\t}\n+\n+\t/*\n+\t * Parse the camera properties.\n+\t *\n+\t * Each camera properties block is a list of properties associated\n+\t * with the ID (as assembled by CameraSensor::generateId()) of the\n+\t * camera they refer to.\n+\t *\n+\t * cameras:\n+\t *   \"camera0 id\":\n+\t *     key: value\n+\t *     key: value\n+\t *     ...\n+\t *\n+\t *   \"camera1 id\":\n+\t *     key: value\n+\t *     key: value\n+\t *     ...\n+\t */\n+\tbool blockEnd = false;\n+\tyaml_token_t token;\n+\tdo {\n+\t\tyaml_parser_scan(&parser_, &token);\n+\t\tswitch (token.type) {\n+\t\tcase YAML_KEY_TOKEN: {\n+\t\t\tyaml_token_delete(&token);\n+\n+\t\t\t/* Parse the camera ID as key of the property list. */\n+\t\t\tstd::string cameraId = parseKey();\n+\t\t\tif (cameraId.empty())\n+\t\t\t\treturn -EINVAL;\n+\n+\t\t\tret = parseCameraConfigData(cameraId);\n+\t\t\tif (ret)\n+\t\t\t\treturn -EINVAL;\n+\t\t\tbreak;\n+\t\t}\n+\t\tcase YAML_BLOCK_END_TOKEN:\n+\t\t\tblockEnd = true;\n+\t\t\t[[fallthrough]];\n+\t\tdefault:\n+\t\t\tyaml_token_delete(&token);\n+\t\t\tbreak;\n+\t\t}\n+\t} while (!blockEnd);\n+\n+\treturn 0;\n+}\n+\n+int CameraHalConfig::Private::parseEntry()\n+{\n+\tint ret = -EINVAL;\n+\n+\t/*\n+\t * Parse each key we find in the file.\n+\t *\n+\t * The 'cameras' keys maps to a list of (lists) of camera properties.\n+\t */\n+\n+\tstd::string key = parseKey();\n+\tif (key.empty())\n+\t\treturn ret;\n+\n+\tif (key == \"cameras\")\n+\t\tret = parseCameras();\n+\telse\n+\t\tLOG(HALConfig, Error) << \"Unknown key: \" << key;\n+\n+\treturn ret;\n+}\n+\n+int CameraHalConfig::Private::parseConfigFile(FILE *fh,\n+\t\t\t\t\t      std::map<std::string, CameraConfigData> *cameras)\n+{\n+\tcameras_ = cameras;\n+\n+\tint ret = yaml_parser_initialize(&parser_);\n+\tif (!ret) {\n+\t\tLOG(HALConfig, Error) << \"Failed to initialize yaml parser\";\n+\t\treturn -EINVAL;\n+\t}\n+\tyaml_parser_set_input_file(&parser_, fh);\n+\n+\tyaml_token_t token;\n+\tyaml_parser_scan(&parser_, &token);\n+\tif (token.type != YAML_STREAM_START_TOKEN) {\n+\t\tLOG(HALConfig, Error) << \"Configuration file is not valid\";\n+\t\tyaml_token_delete(&token);\n+\t\tyaml_parser_delete(&parser_);\n+\t\treturn -EINVAL;\n+\t}\n+\tyaml_token_delete(&token);\n+\n+\tyaml_parser_scan(&parser_, &token);\n+\tif (token.type != YAML_BLOCK_MAPPING_START_TOKEN) {\n+\t\tLOG(HALConfig, Error) << \"Configuration file is not valid\";\n+\t\tyaml_token_delete(&token);\n+\t\tyaml_parser_delete(&parser_);\n+\t\treturn -EINVAL;\n+\t}\n+\tyaml_token_delete(&token);\n+\n+\t/* Parse the file and parse each single key one by one. */\n+\tdo {\n+\t\tyaml_parser_scan(&parser_, &token);\n+\t\tswitch (token.type) {\n+\t\tcase YAML_KEY_TOKEN:\n+\t\t\tyaml_token_delete(&token);\n+\t\t\tret = parseEntry();\n+\t\t\tbreak;\n+\n+\t\tcase YAML_STREAM_END_TOKEN:\n+\t\t\tret = -ENOENT;\n+\t\t\t[[fallthrough]];\n+\t\tdefault:\n+\t\t\tyaml_token_delete(&token);\n+\t\t\tbreak;\n+\t\t}\n+\t} while (ret >= 0);\n+\tyaml_parser_delete(&parser_);\n+\n+\tif (ret && ret != -ENOENT)\n+\t\tLOG(HALConfig, Error) << \"Configuration file is not valid\";\n+\n+\treturn ret == -ENOENT ? 0 : ret;\n+}\n+\n+CameraHalConfig::CameraHalConfig()\n+\t: Extensible(new Private(this)), exists_(false), valid_(false)\n+{\n+\tparseConfigurationFile();\n+}\n+\n+/*\n+ * Open the HAL configuration file and validate its content.\n+ * Return 0 on success, a negative error code otherwise\n+ * retval -ENOENT The configuration file is not available\n+ * retval -EINVAL The configuration file is available but not valid\n+ */\n+int CameraHalConfig::parseConfigurationFile()\n+{\n+\tstd::filesystem::path filePath = LIBCAMERA_SYSCONF_DIR;\n+\tfilePath /= \"camera_hal.yaml\";\n+\tif (!std::filesystem::is_regular_file(filePath)) {\n+\t\tLOG(HALConfig, Debug)\n+\t\t\t<< \"Configuration file: \\\"\" << filePath << \"\\\" not found\";\n+\t\treturn -ENOENT;\n+\t}\n+\n+\tFILE *fh = fopen(filePath.c_str(), \"r\");\n+\tif (!fh) {\n+\t\tint ret = -errno;\n+\t\tLOG(HALConfig, Error) << \"Failed to open configuration file \"\n+\t\t\t\t      << filePath << \": \" << strerror(-ret);\n+\t\treturn ret;\n+\t}\n+\n+\texists_ = true;\n+\n+\tPrivate *const d = LIBCAMERA_D_PTR();\n+\tint ret = d->parseConfigFile(fh, &cameras_);\n+\tfclose(fh);\n+\tif (ret)\n+\t\treturn -EINVAL;\n+\n+\tvalid_ = true;\n+\n+\tfor (const auto &c : cameras_) {\n+\t\tconst std::string &cameraId = c.first;\n+\t\tconst CameraConfigData &camera = c.second;\n+\t\tLOG(HALConfig, Debug) << \"'\" << cameraId << \"' \"\n+\t\t\t\t      << \"(\" << camera.facing << \")[\"\n+\t\t\t\t      << camera.rotation << \"]\";\n+\t}\n+\n+\treturn 0;\n+}\n+\n+const CameraConfigData *CameraHalConfig::cameraConfigData(const std::string &cameraId) const\n+{\n+\tconst auto &it = cameras_.find(cameraId);\n+\tif (it == cameras_.end()) {\n+\t\tLOG(HALConfig, Error)\n+\t\t\t<< \"Camera '\" << cameraId\n+\t\t\t<< \"' not described in the HAL configuration file\";\n+\t\treturn nullptr;\n+\t}\n+\n+\treturn &it->second;\n+}\ndiff --git a/src/android/camera_hal_config.h b/src/android/camera_hal_config.h\nnew file mode 100644\nindex 000000000000..ef0b66f31b54\n--- /dev/null\n+++ b/src/android/camera_hal_config.h\n@@ -0,0 +1,40 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2021, Google Inc.\n+ *\n+ * camera_hal_config.h - Camera HAL configuration file manager\n+ */\n+#ifndef __ANDROID_CAMERA_HAL_CONFIG_H__\n+#define __ANDROID_CAMERA_HAL_CONFIG_H__\n+\n+#include <filesystem>\n+#include <map>\n+#include <string>\n+\n+#include <libcamera/class.h>\n+\n+struct CameraConfigData {\n+\tint facing = -1;\n+\tint rotation = -1;\n+};\n+\n+class CameraHalConfig final : public libcamera::Extensible\n+{\n+\tLIBCAMERA_DECLARE_PRIVATE(CameraBuffer)\n+\n+public:\n+\tCameraHalConfig();\n+\n+\tbool exists() const { return exists_; }\n+\tbool isValid() const { return valid_; }\n+\n+\tconst CameraConfigData *cameraConfigData(const std::string &cameraId) const;\n+\n+private:\n+\tbool exists_;\n+\tbool valid_;\n+\tstd::map<std::string, CameraConfigData> cameras_;\n+\n+\tint parseConfigurationFile();\n+};\n+#endif /* __ANDROID_CAMERA_HAL_CONFIG_H__ */\ndiff --git a/src/android/meson.build b/src/android/meson.build\nindex 2be20c97e118..3893e5b5b832 100644\n--- a/src/android/meson.build\n+++ b/src/android/meson.build\n@@ -3,6 +3,7 @@\n android_deps = [\n     dependency('libexif', required : get_option('android')),\n     dependency('libjpeg', required : get_option('android')),\n+    dependency('yaml-0.1', required : get_option('android')),\n ]\n \n android_enabled = true\n@@ -45,6 +46,7 @@ android_hal_sources = files([\n     'camera3_hal.cpp',\n     'camera_hal_manager.cpp',\n     'camera_device.cpp',\n+    'camera_hal_config.cpp',\n     'camera_metadata.cpp',\n     'camera_ops.cpp',\n     'camera_stream.cpp',\n","prefixes":["libcamera-devel","v6","3/5"]}