From patchwork Mon Mar 29 15:28:04 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 11767 X-Patchwork-Delegate: jacopo@jmondi.org 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 A27DBC32F0 for ; Mon, 29 Mar 2021 15:27:41 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 51FAC6878F; Mon, 29 Mar 2021 17:27:41 +0200 (CEST) Received: from relay2-d.mail.gandi.net (relay2-d.mail.gandi.net [217.70.183.194]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6CDEC68783 for ; Mon, 29 Mar 2021 17:27:38 +0200 (CEST) X-Originating-IP: 93.34.118.233 Received: from uno.lan (93-34-118-233.ip49.fastwebnet.it [93.34.118.233]) (Authenticated sender: jacopo@jmondi.org) by relay2-d.mail.gandi.net (Postfix) with ESMTPSA id D1C5F40008; Mon, 29 Mar 2021 15:27:37 +0000 (UTC) From: Jacopo Mondi To: libcamera-devel@lists.libcamera.org Date: Mon, 29 Mar 2021 17:28:04 +0200 Message-Id: <20210329152807.28331-3-jacopo@jmondi.org> X-Mailer: git-send-email 2.30.0 In-Reply-To: <20210329152807.28331-1-jacopo@jmondi.org> References: <20210329152807.28331-1-jacopo@jmondi.org> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 3/6] android: Add CameraHalConfig class 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" Add a CameraHalConfig class to the Android Camera3 HAL layer. Signed-off-by: Jacopo Mondi --- README.rst | 2 +- src/android/camera_hal_config.cpp | 463 ++++++++++++++++++++++++++++++ src/android/camera_hal_config.h | 53 ++++ src/android/meson.build | 2 + 4 files changed, 519 insertions(+), 1 deletion(-) create mode 100644 src/android/camera_hal_config.cpp create mode 100644 src/android/camera_hal_config.h diff --git a/README.rst b/README.rst index 7a98164bbb0a..f68d435196de 100644 --- a/README.rst +++ b/README.rst @@ -88,7 +88,7 @@ for tracing with lttng: [optional] liblttng-ust-dev python3-jinja2 lttng-tools for android: [optional] - libexif libjpeg + libexif libjpeg libyaml Using GStreamer plugin ~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/android/camera_hal_config.cpp b/src/android/camera_hal_config.cpp new file mode 100644 index 000000000000..846dd7357b0a --- /dev/null +++ b/src/android/camera_hal_config.cpp @@ -0,0 +1,463 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2021, Google Inc. + * + * camera_hal_config.cpp - Camera HAL configuration file manager + */ +#include "camera_hal_config.h" + +#include +#include +#include + +#include + +#include "libcamera/internal/file.h" +#include "libcamera/internal/log.h" + +using namespace libcamera; + +LOG_DEFINE_CATEGORY(HALConfig) + +CameraHalConfig::CameraHalConfig() +{ + if (!yaml_parser_initialize(&parser_)) + LOG(HALConfig, Fatal) << "Failed to initialize yaml parser"; +} + +CameraHalConfig::~CameraHalConfig() +{ + yaml_parser_delete(&parser_); +} + +/* + * Configuration files can be stored in system paths, which are identified + * through the build configuration. + * + * However, when running uninstalled - the source location takes precedence. + */ +std::string CameraHalConfig::findFilePath(const std::string &filename) const +{ + static std::array searchPaths = { + LIBCAMERA_SYSCONF_DIR, + LIBCAMERA_DATA_DIR, + }; + + if (File::exists(filename)) + return filename; + + std::string root = utils::libcameraSourcePath(); + if (!root.empty()) { + std::string configurationPath = root + "data/" + filename; + if (File::exists(configurationPath)) + return configurationPath; + } + + for (std::string &path : searchPaths) { + std::string configurationPath = path + "/" + filename; + if (File::exists(configurationPath)) + return configurationPath; + } + + return ""; +} + +FILE *CameraHalConfig::openFile(const std::string &filename) +{ + const std::string filePath = findFilePath(filename); + if (filePath.empty()) { + LOG(HALConfig, Error) + << "Configuration file: \"" << filename << "\" not found"; + return nullptr; + } + + LOG(HALConfig, Debug) << "Reading configuration file from " << filePath; + + FILE *fh = fopen(filePath.c_str(), "r"); + if (!fh) { + int ret = -errno; + LOG(HALConfig, Error) << "Failed to open configuration file " + << filePath << ": " << strerror(-ret); + return nullptr; + } + + return fh; +} + +/* + * Open the HAL configuration file and validate its content. + * Return 0 on success, a negative error code otherwise + */ +int CameraHalConfig::open() +{ + int ret; + + FILE *fh = openFile("camera_hal.yaml"); + if (!fh) + return -ENOENT; + + yaml_parser_set_input_file(&parser_, fh); + ret = parseConfigFile(); + fclose(fh); + if (ret) + return ret; + + LOG(HALConfig, Info) << "Device model: " << model_; + LOG(HALConfig, Info) << "Device maker: " << maker_; + for (const auto &c : cameras_) { + const std::string &cameraId = c.first; + const CameraProps &camera = c.second; + LOG(HALConfig, Info) << "'" << cameraId << "' " + << " model: " << camera.model + << "(" << camera.location << ")[" + << camera.rotation << "]"; + } + + return 0; +} + +int CameraHalConfig::cameraLocation(const std::string &camera) const +{ + const auto &it = cameras_.find(camera); + if (it == cameras_.end()) { + LOG(HALConfig, Error) + << "Camera '" << camera + << "' not described in the HAL configuration file"; + return -EINVAL; + } + + const CameraProps &cam = it->second; + if (cam.location.empty()) { + LOG(HALConfig, Error) << "Location for camera '" << camera + << "' not available"; + return -EINVAL; + } + + if (cam.location == "front") + return CAMERA_FACING_FRONT; + else if (cam.location == "back") + return CAMERA_FACING_BACK; + else if (cam.location == "external") + return CAMERA_FACING_EXTERNAL; + + LOG(HALConfig, Error) << "Unsupported camera location " << cam.location; + return -EINVAL; +} + +unsigned int CameraHalConfig::cameraRotation(const std::string &camera) const +{ + const auto &it = cameras_.find(camera); + if (it == cameras_.end()) { + LOG(HALConfig, Error) + << "Camera '" << camera + << "' not described in the HAL configuration file"; + return 0; + } + + const CameraProps &cam = it->second; + return cam.rotation; +} + +const std::string &CameraHalConfig::cameraModel(const std::string &camera) const +{ + static const std::string empty(""); + const auto &it = cameras_.find(camera); + if (it == cameras_.end()) { + LOG(HALConfig, Error) + << "Camera '" << camera + << "' not described in the HAL configuration file"; + return empty; + } + + const CameraProps &cam = it->second; + return cam.model; +} + +std::string CameraHalConfig::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) { + LOG(HALConfig, Error) << "Configuration file is not valid"; + return ""; + } + yaml_token_delete(&token); + + yaml_parser_scan(&parser_, &token); + if (token.type != YAML_SCALAR_TOKEN) { + LOG(HALConfig, Error) << "Configuration file is not valid"; + return ""; + } + + char *scalar = reinterpret_cast(token.data.scalar.value); + std::string value(scalar, token.data.scalar.length); + yaml_token_delete(&token); + + return value; +} + +std::string CameraHalConfig::parseKey(yaml_token_t &token) +{ + /* Make sure the token type is a key and get its value. */ + yaml_parser_scan(&parser_, &token); + if (token.type != YAML_SCALAR_TOKEN) { + LOG(HALConfig, Error) << "Configuration file is not valid"; + return ""; + } + + char *scalar = reinterpret_cast(token.data.scalar.value); + std::string key(scalar, token.data.scalar.length); + yaml_token_delete(&token); + + return key; +} + +int CameraHalConfig::parseCameraProps(yaml_token_t &token, + const std::string &cameraID) +{ + yaml_parser_scan(&parser_, &token); + if (token.type != YAML_VALUE_TOKEN) { + LOG(HALConfig, Error) << "Configuration file is not valid"; + return -EINVAL; + } + yaml_token_delete(&token); + + yaml_parser_scan(&parser_, &token); + if (token.type != YAML_BLOCK_MAPPING_START_TOKEN) { + LOG(HALConfig, Error) << "Configuration file is not valid"; + return -EINVAL; + } + yaml_token_delete(&token); + + /* + * Parse the camera properties and store them in a cameraBlock instance. + * + * Add a safety counter to make sure we don't loop indefinitely in case + * the configuration file is malformed. + */ + unsigned int sentinel = 100; + CameraProps cameraBlock{}; + bool blockEnd = false; + do { + yaml_parser_scan(&parser_, &token); + switch (token.type) { + case YAML_KEY_TOKEN: { + yaml_token_delete(&token); + + /* + * Parse the camera property key and make sure it is + * valid. + */ + std::string key = parseKey(token); + std::string value = parseValue(token); + if (key.empty() || value.empty()) { + LOG(HALConfig, Error) + << "Configuration file is not valid"; + return -EINVAL; + } + + if (key == "location") { + if (value != "front" && value != "back" && + value != "external") { + LOG(HALConfig, Error) + << "Unknown location: " << value; + return -EINVAL; + } + cameraBlock.location = value; + } else if (key == "rotation") { + cameraBlock.rotation = strtoul(value.c_str(), + NULL, 10); + if (cameraBlock.rotation < 0) { + LOG(HALConfig, Error) + << "Unknown rotation: " + << cameraBlock.rotation; + return -EINVAL; + } + } else if (key == "model") { + cameraBlock.model = value; + } else { + LOG(HALConfig, Error) + << "Configuration file is not valid " + << "unknown key: " << key; + return -EINVAL; + } + break; + } + case YAML_BLOCK_END_TOKEN: + yaml_token_delete(&token); + blockEnd = true; + break; + default: + yaml_token_delete(&token); + break; + } + + --sentinel; + } while (!blockEnd && sentinel); + if (!sentinel) { + LOG(HALConfig, Error) << "Configuration file is not valid "; + return -EINVAL; + } + + cameras_[cameraID] = cameraBlock; + + return 0; +} + +int CameraHalConfig::parseCameras(yaml_token_t &token) +{ + int ret; + + /* The 'cameras' key maps a BLOCK_MAPPING_START block. */ + yaml_parser_scan(&parser_, &token); + if (token.type != YAML_VALUE_TOKEN) { + LOG(HALConfig, Error) << "Configuration file is not valid"; + return -EINVAL; + } + yaml_token_delete(&token); + + yaml_parser_scan(&parser_, &token); + if (token.type != YAML_BLOCK_MAPPING_START_TOKEN) { + LOG(HALConfig, Error) << "Configuration file is not valid"; + return -EINVAL; + } + yaml_token_delete(&token); + + /* + * Parse the camera properties. + * + * Each camera properties block is a list of properties associated + * with the ID (as assembled by CameraSensor::generateId()) of the + * camera they refer to. + * + * cameras: + * "camera0 id": + * key: value + * key: value + * ... + * + * "camera1 id": + * key: value + * key: value + * ... + */ + bool blockEnd = false; + do { + yaml_parser_scan(&parser_, &token); + switch (token.type) { + case YAML_KEY_TOKEN: { + yaml_token_delete(&token); + + /* Parse the camera ID as key of the property list. */ + std::string cameraId = parseKey(token); + if (cameraId.empty()) { + LOG(HALConfig, Error) + << "Configuration file is not valid"; + return -EINVAL; + } + + ret = parseCameraProps(token, cameraId); + if (ret) { + LOG(HALConfig, Error) + << "Configuration file is not valid"; + return -EINVAL; + } + break; + } + case YAML_BLOCK_END_TOKEN: + yaml_token_delete(&token); + blockEnd = true; + break; + default: + yaml_token_delete(&token); + LOG(HALConfig, Error) + << "Configuration file is not valid"; + return -EINVAL; + } + } while (!blockEnd); + + return 0; +} + +int CameraHalConfig::parseEntry(yaml_token_t &token) +{ + int ret; + + /* + * Parse each key we find in the file. + * + * Keys like 'model' and 'maker' are device properties and gets + * stored as class members. + * + * The 'cameras' keys maps to a list of (lists) of camera properties. + */ + + std::string key = parseKey(token); + if (key.empty()) { + LOG(HALConfig, Error) << "Configuration file is not valid"; + return -EINVAL; + } + + if (key == "cameras") { + ret = parseCameras(token); + if (ret) + return ret; + } else if (key == "manufacturer") { + maker_ = parseValue(token); + if (maker_.empty()) { + LOG(HALConfig, Error) << "Configuration file is not valid"; + return -EINVAL; + } + } else if (key == "model") { + model_ = parseValue(token); + if (model_.empty()) { + LOG(HALConfig, Error) << "Configuration file is not valid"; + return -EINVAL; + } + } else { + LOG(HALConfig, Error) << "Unknown key " << key; + return -EINVAL; + } + + return 0; +} + +int CameraHalConfig::parseConfigFile() +{ + yaml_token_t token; + int ret; + + yaml_parser_scan(&parser_, &token); + if (token.type != YAML_STREAM_START_TOKEN) { + LOG(HALConfig, Error) << "Configuration file is not valid"; + return -EINVAL; + } + yaml_token_delete(&token); + + yaml_parser_scan(&parser_, &token); + if (token.type != YAML_BLOCK_MAPPING_START_TOKEN) { + LOG(HALConfig, Error) << "Configuration file is not valid"; + return -EINVAL; + } + yaml_token_delete(&token); + + /* Parse the file and parse each single key one by one. */ + do { + yaml_parser_scan(&parser_, &token); + switch (token.type) { + case YAML_KEY_TOKEN: + yaml_token_delete(&token); + ret = parseEntry(token); + break; + case YAML_STREAM_END_TOKEN: + /* Resources are released after the loop exit. */ + break; + default: + yaml_token_delete(&token); + break; + } + } while (token.type != YAML_STREAM_END_TOKEN && ret >= 0); + yaml_token_delete(&token); + + return ret; +} diff --git a/src/android/camera_hal_config.h b/src/android/camera_hal_config.h new file mode 100644 index 000000000000..ce77b1ee1582 --- /dev/null +++ b/src/android/camera_hal_config.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2021, Google Inc. + * + * camera_hal_config.h - Camera HAL configuration file manager + */ +#ifndef __ANDROID_CAMERA_HAL_CONFIG_H__ +#define __ANDROID_CAMERA_HAL_CONFIG_H__ + +#include +#include +#include + +class CameraHalConfig +{ +public: + CameraHalConfig(); + ~CameraHalConfig(); + + int open(); + + const std::string &deviceModel() const { return model_; } + const std::string &deviceMaker() const { return maker_; } + + int cameraLocation(const std::string &camera) const; + unsigned int cameraRotation(const std::string &camera) const; + const std::string &cameraModel(const std::string &camera) const; + +private: + yaml_parser_t parser_; + + std::string model_; + std::string maker_; + class CameraProps + { + public: + std::string location; + std::string model; + unsigned int rotation; + }; + std::map cameras_; + + std::string findFilePath(const std::string &filename) const; + FILE *openFile(const std::string &filename); + std::string parseValue(yaml_token_t &token); + std::string parseKey(yaml_token_t &token); + int parseCameraProps(yaml_token_t &token, const std::string &cameraID); + int parseCameras(yaml_token_t &token); + int parseEntry(yaml_token_t &token); + int parseConfigFile(); +}; + +#endif /* __ANDROID_CAMERA_HAL_CONFIG_H__ */ diff --git a/src/android/meson.build b/src/android/meson.build index 8e7d07d9be3c..c0ede407bc38 100644 --- a/src/android/meson.build +++ b/src/android/meson.build @@ -3,6 +3,7 @@ android_deps = [ dependency('libexif', required : get_option('android')), dependency('libjpeg', required : get_option('android')), + dependency('yaml-0.1', required : get_option('android')), ] android_enabled = true @@ -41,6 +42,7 @@ android_hal_sources = files([ 'camera3_hal.cpp', 'camera_hal_manager.cpp', 'camera_device.cpp', + 'camera_hal_config.cpp', 'camera_metadata.cpp', 'camera_ops.cpp', 'camera_stream.cpp',