[{"id":16236,"web_url":"https://patchwork.libcamera.org/comment/16236/","msgid":"<YHXiy/EthjPtqOse@pendragon.ideasonboard.com>","date":"2021-04-13T18:28:27","subject":"Re: [libcamera-devel] [PATCH 3/5] android: Add CameraHalConfig class","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Jacopo,\n\nThank you for the patch.\n\nOn Tue, Apr 13, 2021 at 04:50:40PM +0200, Jacopo Mondi wrote:\n> Add a CameraHalConfig class to the Android Camera3 HAL layer.\n> \n> Reviewed-by: Hirokazu Honda <hiroh@chromium.org>\n> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>\n> ---\n>  README.rst                        |   2 +-\n>  src/android/camera_hal_config.cpp | 431 ++++++++++++++++++++++++++++++\n>  src/android/camera_hal_config.h   |  44 +++\n>  src/android/meson.build           |   2 +\n>  4 files changed, 478 insertions(+), 1 deletion(-)\n>  create mode 100644 src/android/camera_hal_config.cpp\n>  create mode 100644 src/android/camera_hal_config.h\n> \n> diff --git a/README.rst b/README.rst\n> index 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>  ~~~~~~~~~~~~~~~~~~~~~~\n> diff --git a/src/android/camera_hal_config.cpp b/src/android/camera_hal_config.cpp\n> new file mode 100644\n> index 000000000000..ffc9ae9bcb70\n> --- /dev/null\n> +++ b/src/android/camera_hal_config.cpp\n> @@ -0,0 +1,431 @@\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, CameraProps> *cameras);\n> +\n> +private:\n> +\tstd::string parseValue();\n> +\tstd::string parseKey();\n> +\tint parseValueBlock();\n> +\tint parseCameraLocation(CameraProps *cameraProps, const std::string &location);\n> +\tint parseCameraProps(const std::string &cameraId);\n> +\tint parseCameras();\n> +\tint parseEntry();\n> +\n> +\tyaml_parser_t parser_;\n> +\tstd::map<std::string, CameraProps> *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(CameraProps *cameraProps, const std::string &location)\n> +{\n> +\tif (location == \"front\")\n> +\t\tcameraProps->facing = CAMERA_FACING_FRONT;\n> +\telse if (location == \"back\")\n> +\t\tcameraProps->facing = CAMERA_FACING_BACK;\n> +\telse if (location == \"external\")\n> +\t\tcameraProps->facing = CAMERA_FACING_EXTERNAL;\n> +\telse\n> +\t\treturn -EINVAL;\n> +\n> +\treturn 0;\n> +}\n> +\n> +int CameraHalConfig::Private::parseCameraProps(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 cameraProps 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> +\tunsigned int sentinel = 100;\n> +\tCameraProps cameraProps;\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(&cameraProps, 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: \"\n> +\t\t\t\t\t\t<< cameraProps.rotation;\n> +\t\t\t\t\treturn -EINVAL;\n> +\t\t\t\t}\n> +\t\t\t\tcameraProps.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> +\tcameraProps.valid = true;\n> +\t(*cameras_)[cameraId] = cameraProps;\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 = parseCameraProps(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, CameraProps> *cameras)\n> +{\n> +\tcameras_ = cameras;\n> +\n> +\tint ret = yaml_parser_initialize(&parser_);\n> +\tif (!ret) {\n> +\t\tLOG(HALConfig, Fatal) << \"Failed to initialize yaml parser\";\n\nShould this be an Error ? We can handle this gracefully instead of\ncrashing.\n\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> +std::filesystem::path CameraHalConfig::findFilePath(const std::string &filename) const\n> +{\n> +\tstd::filesystem::path filePath = filename;\n> +\tif (std::filesystem::is_regular_file(filePath))\n> +\t\treturn filename;\n\nI think this is a bit dangerous. We call this function with\n\"camera_hal.yaml\", so we're looking here in the current directory, which\nisn't something we control (it depends on how the camera service is\nrun). I don't expect to ever have a camera_hal.yaml file in there in\nnormal circumstances, but if it happens for any reason, it could not be\nthe file we want. Do you have a use case for this, or should it be\ndropped ?\n\n> +\n> +\tstd::filesystem::path root = utils::libcameraSourcePath();\n> +\tif (!root.empty()) {\n> +\t\tfilePath = root / \"data\" / filename;\n> +\t\tif (std::filesystem::is_regular_file(filePath))\n> +\t\t\treturn filePath;\n> +\t}\n\nIs this needed ? It's a bit of the same question as before, we can't run\nthe camera HAL from the source directory, as the camera service always\nlooks for it in a system location.\n\n> +\n> +\troot = LIBCAMERA_SYSCONF_DIR;\n> +\tfilePath = root / filename;\n> +\tif (std::filesystem::is_regular_file(filePath))\n> +\t\treturn filePath;\n\nIf this becomes the only remaining case in this function, I'd inline it\nin the caller.\n\n> +\n> +\treturn \"\";\n> +}\n> +\n> +FILE *CameraHalConfig::openConfigFile(const std::string &filename)\n> +{\n> +\tconst std::filesystem::path filePath = findFilePath(filename);\n> +\tif (filePath.empty()) {\n> +\t\tLOG(HALConfig, Error)\n> +\t\t\t<< \"Configuration file: \\\"\" << filename << \"\\\" not found\";\n> +\t\treturn nullptr;\n\nWhen using external cameras only, this isn't an error. I'd downgrade it\nto a Debug message, or possibly an Info message if we consider that\ninternal cameras should be the norm. I'd prefer Debug personally.\n\n> +\t}\n> +\n> +\tLOG(HALConfig, Debug) << \"Reading configuration file from \" << filePath;\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 nullptr;\n> +\t}\n> +\n> +\treturn fh;\n> +}\n> +\n> +CameraHalConfig::CameraHalConfig()\n> +\t: Extensible(new Private(this)), exists_(false), valid_(false)\n> +{\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::open()\n> +{\n> +\tFILE *fh = openConfigFile(\"camera_hal.yaml\");\n> +\tif (!fh)\n> +\t\treturn -ENOENT;\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 CameraProps &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 CameraProps &CameraHalConfig::cameraProps(const std::string &cameraId) const\n> +{\n> +\tstatic CameraProps empty;\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 empty;\n\nThe caller takes the address of the returned value. Returning a const\npointer would be better, as you'll be able to return nullptr here. The\nvalid member of CameraProps can then be dropped, as all configurations\nwe store are valid.\n\n> +\t}\n> +\n> +\treturn it->second;\n> +}\n> diff --git a/src/android/camera_hal_config.h b/src/android/camera_hal_config.h\n> new file mode 100644\n> index 000000000000..8812eac87b39\n> --- /dev/null\n> +++ b/src/android/camera_hal_config.h\n> @@ -0,0 +1,44 @@\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 CameraProps {\n\nI would have named this CameraConfig, as \"props\" make me think about\nlibcamera::Camera::properties(), but that would be ambiguous with\nlibcamera::CameraConfiguration. Too many concepts in a single code base\n:-) I wonder if CameraConfigData could be a better name. If you think it\nis, feel free to rename, otherwise let's leave it as-is.\n\n> +\tint facing = -1;\n> +\tint rotation = -1;\n> +\n> +\tbool valid = false;\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 valid() const { return valid_; }\n\nWe use isValid() through the whole code base (except in MediaDevice,\nI'll send a patch for that), so let's do so here too to be consistent.\n\n> +\n> +\tint open();\n\nopen() could be called from the constructor, given that we ignore its\nreturn value in the caller and rely on exists() and valid() only, but\nthat's no big deal. I'm sure we can refactor this later if needed. I'd\nmove the function above exists() though, to match the normal call order.\n\nReviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n\n> +\tconst CameraProps &cameraProps(const std::string &cameraId) const;\n> +\n> +private:\n> +\tstd::filesystem::path findFilePath(const std::string &filename) const;\n> +\tFILE *openConfigFile(const std::string &filename);\n> +\n> +\tbool exists_;\n> +\tbool valid_;\n> +\tstd::map<std::string, CameraProps> cameras_;\n> +};\n> +#endif /* __ANDROID_CAMERA_HAL_CONFIG_H__ */\n> diff --git a/src/android/meson.build b/src/android/meson.build\n> index 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',","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 C53F0BD224\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 13 Apr 2021 18:29:19 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 49E69687FE;\n\tTue, 13 Apr 2021 20:29:19 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 7EF61602D1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 13 Apr 2021 20:29:18 +0200 (CEST)","from pendragon.ideasonboard.com (62-78-145-57.bb.dnainternet.fi\n\t[62.78.145.57])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id EAE1B9F0;\n\tTue, 13 Apr 2021 20:29:17 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"htpxf68W\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1618338558;\n\tbh=lI2xbJ6nGZwluj+GyHE150lBoK0nYLYmqi8caC+zQWg=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=htpxf68Wt+yBVF2TB6qZRdl+rb3qitjlI5zsbovZOpmi6niKGMLNwL0GNKVg6Bfhg\n\t206pQlcP/vkZr3gO4WFw/ddyLM+zTmifsTMrEZWD8PxPC19Yj/iz0j7kW+pNGrSXY2\n\tIMjCuqB+UJZuk3aciheMjI5hE3UA883hSy5Bbevc=","Date":"Tue, 13 Apr 2021 21:28:27 +0300","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Jacopo Mondi <jacopo@jmondi.org>","Message-ID":"<YHXiy/EthjPtqOse@pendragon.ideasonboard.com>","References":"<20210413145042.48185-1-jacopo@jmondi.org>\n\t<20210413145042.48185-4-jacopo@jmondi.org>","MIME-Version":"1.0","Content-Disposition":"inline","In-Reply-To":"<20210413145042.48185-4-jacopo@jmondi.org>","Subject":"Re: [libcamera-devel] [PATCH 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>","Cc":"libcamera-devel@lists.libcamera.org","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>"}}]