[{"id":3084,"web_url":"https://patchwork.libcamera.org/comment/3084/","msgid":"<20191118220858.GN8072@bigcity.dyn.berto.se>","date":"2019-11-18T22:08:58","subject":"Re: [libcamera-devel] [PATCH v2 19/24] ipa: Define a plain C API","submitter":{"id":5,"url":"https://patchwork.libcamera.org/api/people/5/","name":"Niklas Söderlund","email":"niklas.soderlund@ragnatech.se"},"content":"Hi Laurent,\n\nThanks for your work.\n\nOn 2019-11-08 22:54:04 +0200, Laurent Pinchart wrote:\n> The C++ objects that are expected to convey data through the IPA API\n> will have associated methods that would require IPAs to link to\n> libcamera. Even though the libcamera license allows this, suppliers of\n> closed-source IPAs may have a different interpretation. To ease their\n> mind and clearly separate vendor code and libcamera code, define a plain\n> C IPA API. The corresponding C objects are stored in plain C structures\n> or have their binary format documented, removing the need for linking to\n> libcamera code on the IPA side.\n> \n> The C API adds three new C structures, ipa_context, ipa_context_ops and\n> ipa_callback_ops. The ipa_context_ops and ipa_callback_ops contain\n> function pointers for all the IPA interface methods and signals,\n> respectively. The ipa_context represents a context of operation for the\n> IPA, and is passed to the IPA oparations. The IPAInterface class is\n> retained as it is easier to use than a plain C API for pipeline\n> handlers, and wrappers will be developed to translate between the C and\n> C++ APIs.\n> \n> Switching to the C API internally will be done in a second step.\n> \n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>\n\nReviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\n\n> ---\n>  include/ipa/ipa_interface.h     |  77 +++++++++\n>  src/libcamera/ipa_interface.cpp | 295 ++++++++++++++++++++++++++++----\n>  2 files changed, 340 insertions(+), 32 deletions(-)\n> \n> diff --git a/include/ipa/ipa_interface.h b/include/ipa/ipa_interface.h\n> index 35dc4b4e3165..3a423e37671f 100644\n> --- a/include/ipa/ipa_interface.h\n> +++ b/include/ipa/ipa_interface.h\n> @@ -7,6 +7,82 @@\n>  #ifndef __LIBCAMERA_IPA_INTERFACE_H__\n>  #define __LIBCAMERA_IPA_INTERFACE_H__\n>  \n> +#include <stddef.h>\n> +#include <stdint.h>\n> +\n> +#ifdef __cplusplus\n> +extern \"C\" {\n> +#endif\n> +\n> +struct ipa_context {\n> +\tconst struct ipa_context_ops *ops;\n> +};\n> +\n> +struct ipa_stream {\n> +\tunsigned int id;\n> +\tunsigned int pixel_format;\n> +\tunsigned int width;\n> +\tunsigned int height;\n> +};\n> +\n> +struct ipa_control_info_map {\n> +\tunsigned int id;\n> +\tconst uint8_t *data;\n> +\tsize_t size;\n> +};\n> +\n> +struct ipa_buffer_plane {\n> +\tint dmabuf;\n> +\tsize_t length;\n> +};\n> +\n> +struct ipa_buffer {\n> +\tunsigned int id;\n> +\tunsigned int num_planes;\n> +\tstruct ipa_buffer_plane planes[3];\n> +};\n> +\n> +struct ipa_control_list {\n> +\tconst uint8_t *data;\n> +\tunsigned int size;\n> +};\n> +\n> +struct ipa_operation_data {\n> +\tunsigned int operation;\n> +\tconst uint32_t *data;\n> +\tunsigned int num_data;\n> +\tconst struct ipa_control_list *lists;\n> +\tunsigned int num_lists;\n> +};\n> +\n> +struct ipa_callback_ops {\n> +\tvoid (*queue_frame_action)(void *cb_ctx, unsigned int frame,\n> +\t\t\t\t   struct ipa_operation_data &data);\n> +};\n> +\n> +struct ipa_context_ops {\n> +\tvoid (*destroy)(struct ipa_context *ctx);\n> +\tvoid (*init)(struct ipa_context *ctx);\n> +\tvoid (*register_callbacks)(struct ipa_context *ctx,\n> +\t\t\t\t   const struct ipa_callback_ops *callbacks,\n> +\t\t\t\t   void *cb_ctx);\n> +\tvoid (*configure)(struct ipa_context *ctx,\n> +\t\t\t  const struct ipa_stream *streams,\n> +\t\t\t  unsigned int num_streams,\n> +\t\t\t  const struct ipa_control_info_map *maps,\n> +\t\t\t  unsigned int num_maps);\n> +\tvoid (*map_buffers)(struct ipa_context *ctx,\n> +\t\t\t    const struct ipa_buffer *buffers,\n> +\t\t\t    size_t num_buffers);\n> +\tvoid (*unmap_buffers)(struct ipa_context *ctx, const unsigned int *ids,\n> +\t\t\t      size_t num_buffers);\n> +\tvoid (*process_event)(struct ipa_context *ctx,\n> +\t\t\t      const struct ipa_operation_data *data);\n> +};\n> +\n> +#ifdef __cplusplus\n> +}\n> +\n>  #include <map>\n>  #include <vector>\n>  \n> @@ -53,5 +129,6 @@ public:\n>  };\n>  \n>  } /* namespace libcamera */\n> +#endif\n>  \n>  #endif /* __LIBCAMERA_IPA_INTERFACE_H__ */\n> diff --git a/src/libcamera/ipa_interface.cpp b/src/libcamera/ipa_interface.cpp\n> index 0571b8e6bf8b..89fce0e8347f 100644\n> --- a/src/libcamera/ipa_interface.cpp\n> +++ b/src/libcamera/ipa_interface.cpp\n> @@ -11,27 +11,254 @@\n>   * \\file ipa_interface.h\n>   * \\brief Image Processing Algorithm interface\n>   *\n> - * Pipeline handlers communicate with IPAs through a C++ interface defined by\n> - * the IPAInterface class. The class defines high-level methods and signals to\n> - * configure the IPA, notify it of events, and receive actions back from the\n> - * IPA.\n> - *\n> - * Pipeline handlers define the set of events and actions used to communicate\n> - * with their IPA. These are collectively referred to as IPA operations and\n> - * define the pipeline handler-specific IPA protocol. Each operation defines the\n> - * data that it carries, and how the data is encoded in the IPAOperationData\n> - * structure.\n> - *\n> - * IPAs can be isolated in a separate process. This implies that all arguments\n> - * to the IPA interface may need to be transferred over IPC. The IPA interface\n> - * thus uses serialisable data types only. The IPA interface defines custom data\n> - * structures that mirror core libcamera structures when the latter are not\n> - * suitable, such as IPAStream to carry StreamConfiguration data.\n> + * Every pipeline handler in libcamera may attach some or all of its cameras to\n> + * an Image Processing Algorithm (IPA) module. An IPA module is developed for a\n> + * specific pipeline handler and each pipeline handler may be compatible with\n> + * multiple IPA implementations, both open and closed source. To support this,\n> + * libcamera communicates with IPA modules through a standard plain C interface.\n> + *\n> + * IPA modules shall expose a public function named ipaCreate() with the\n> + * following prototype.\n> + *\n> + * \\code{.c}\n> + * struct ipa_context *ipaCreate();\n> + * \\endcode\n> + *\n> + * The ipaCreate() function creates an instance of an IPA context, which models\n> + * a context of execution for the IPA. IPA modules shall support creating one\n> + * context per camera, as required by their associated pipeline handler.\n> + *\n> + * The IPA module context operations are defined in the struct ipa_context_ops.\n> + * They model a low-level interface to configure the IPA, notify it of events,\n> + * and receive IPA actions through callbacks. An IPA module stores a pointer to\n> + * the operations corresponding to its context in the ipa_context::ops field.\n> + * That pointer is immutable for the lifetime of the context, and may differ\n> + * between different contexts created by the same IPA module.\n> + *\n> + * The IPA interface defines base data types and functions to exchange data. On\n> + * top of this, each pipeline handler is responsible for defining the set of\n> + * events and actions used to communicate with their IPA. These are collectively\n> + * referred to as IPA operations and define the pipeline handler-specific IPA\n> + * protocol. Each operation defines the data that it carries, and how that data\n> + * is encoded in the ipa_context_ops functions arguments.\n> + *\n> + * \\todo Add reference to how pipelines shall document their protocol.\n> + *\n> + * IPAs can be isolated in a separate process. This implies that arguments to\n> + * the IPA interface functions may need to be transferred over IPC. All\n> + * arguments use Plain Old Data types and are documented either in the form of C\n> + * data types, or as a textual description of byte arrays for types that can't\n> + * be expressed using C data types (such as arrays of mixed data types). IPA\n> + * modules can thus use the C API without calling into libcamera to access the\n> + * data passed to the IPA context operations.\n>   *\n>   * Due to IPC, synchronous communication between pipeline handlers and IPAs can\n>   * be costly. For that reason, the interface operates asynchronously. This\n> - * implies that methods don't return a status, and that both methods and signals\n> - * may copy their arguments.\n> + * implies that methods don't return a status, and that all methods may copy\n> + * their arguments.\n> + *\n> + * The IPAInterface class is a C++ representation of the ipa_context_ops, using\n> + * C++ data classes provided by libcamera. This is the API exposed to pipeline\n> + * handlers to communicate with IPA modules. IPA modules may use the\n> + * IPAInterface API internally if they want to benefit from the data and helper\n> + * classes offered by libcamera.\n> + */\n> +\n> +/**\n> + * \\struct ipa_context\n> + * \\brief IPA module context of execution\n> + *\n> + * This structure models a context of execution for an IPA module. It is\n> + * instantiated by the IPA module ipaCreate() function. IPA modules allocate\n> + * context instances in an implementation-defined way, contexts shall thus be\n> + * destroyed using the ipa_operation::destroy function only.\n> + *\n> + * The ipa_context structure provides a pointer to the IPA context operations.\n> + * It shall otherwise be treated as a constant black-box cookie and passed\n> + * unmodified to the functions defined in struct ipa_context_ops.\n> + *\n> + * IPA modules are expected to extend struct ipa_context by inheriting from it,\n> + * either through structure embedding to model inheritance in plain C, or\n> + * through C++ class inheritance. A simple example of the latter is available\n> + * in the IPAContextWrapper class implementation.\n> + *\n> + * \\var ipa_context::ops\n> + * \\brief The IPA context operations\n> + */\n> +\n> +/**\n> + * \\struct ipa_stream\n> + * \\brief Stream information for the IPA context operations\n> + *\n> + * \\var ipa_stream::id\n> + * \\brief Identifier for the stream, defined by the IPA protocol\n> + *\n> + * \\var ipa_stream::pixel_format\n> + * \\brief The stream pixel format, as defined by the PixelFormat class\n> + *\n> + * \\var ipa_stream::width\n> + * \\brief The stream width in pixels\n> + *\n> + * \\var ipa_stream::height\n> + * \\brief The stream height in pixels\n> + */\n> +\n> +/**\n> + * \\struct ipa_control_info_map\n> + * \\brief ControlInfoMap description for the IPA context operations\n> + *\n> + * \\var ipa_control_info_map::id\n> + * \\brief Identifier for the ControlInfoMap, defined by the IPA protocol\n> + *\n> + * \\var ipa_control_info_map::data\n> + * \\brief Pointer to a control packet for the ControlInfoMap\n> + * \\sa ipa_controls.h\n> + *\n> + * \\var ipa_control_info_map::size\n> + * \\brief The size of the control packet in bytes\n> + */\n> +\n> +/**\n> + * \\struct ipa_buffer_plane\n> + * \\brief A plane for an ipa_buffer\n> + *\n> + * \\var ipa_buffer_plane::dmabuf\n> + * \\brief The dmabuf file descriptor for the plane (-1 for unused planes)\n> + *\n> + * \\var ipa_buffer_plane::length\n> + * \\brief The plane length in bytes (0 for unused planes)\n> + */\n> +\n> +/**\n> + * \\struct ipa_buffer\n> + * \\brief Buffer information for the IPA context operations\n> + *\n> + * \\var ipa_buffer::id\n> + * \\brief The buffer unique ID (see \\ref libcamera::IPABuffer::id)\n> + *\n> + * \\var ipa_buffer::num_planes\n> + * \\brief The number of used planes in the ipa_buffer::planes array\n> + *\n> + * \\var ipa_buffer::planes\n> + * \\brief The buffer planes (up to 3)\n> + */\n> +\n> +/**\n> + * \\struct ipa_control_list\n> + * \\brief ControlList description for the IPA context operations\n> + *\n> + * \\var ipa_control_list::data\n> + * \\brief Pointer to a control packet for the ControlList\n> + * \\sa ipa_controls.h\n> + *\n> + * \\var ipa_control_list::size\n> + * \\brief The size of the control packet in bytes\n> + */\n> +\n> +/**\n> + * \\struct ipa_operation_data\n> + * \\brief IPA operation data for the IPA context operations\n> + * \\sa libcamera::IPAOperationData\n> + *\n> + * \\var ipa_operation_data::operation\n> + * \\brief IPA protocol operation\n> + *\n> + * \\var ipa_operation_data::data\n> + * \\brief Pointer to the operation data array\n> + *\n> + * \\var ipa_operation_data::num_data\n> + * \\brief Number of entries in the ipa_operation_data::data array\n> + *\n> + * \\var ipa_operation_data::lists\n> + * \\brief Pointer to an array of ipa_control_list\n> + *\n> + * \\var ipa_operation_data::num_lists\n> + * \\brief Number of entries in the ipa_control_list array\n> + */\n> +\n> +/**\n> + * \\struct ipa_callback_ops\n> + * \\brief IPA context operations as a set of function pointers\n> + */\n> +\n> +/**\n> + * \\var ipa_callback_ops::queue_frame_action\n> + * \\brief Queue an action associated with a frame to the pipeline handler\n> + * \\param[in] cb_ctx The callback context registered with\n> + * ipa_context_ops::register_callbacks\n> + * \\param[in] frame The frame number\n> + *\n> + * \\sa libcamera::IPAInterface::queueFrameAction\n> + */\n> +\n> +/**\n> + * \\struct ipa_context_ops\n> + * \\brief IPA context operations as a set of function pointers\n> + *\n> + * To allow for isolation of IPA modules in separate processes, the functions\n> + * defined in the ipa_context_ops structure return only data related to the\n> + * libcamera side of the operations. In particular, error related to the\n> + * libcamera side of the IPC may be returned. Data returned by the IPA,\n> + * including status information, shall be provided through callbacks from the\n> + * IPA to libcamera.\n> + */\n> +\n> +/**\n> + * \\var ipa_context_ops::destroy\n> + * \\brief Destroy the IPA context created by the module's ipaCreate() function\n> + * \\param[in] ctx The IPA context\n> + */\n> +\n> +/**\n> + * \\var ipa_context_ops::init\n> + * \\brief Initialise the IPA context\n> + * \\param[in] ctx The IPA context\n> + *\n> + * \\sa libcamera::IPAInterface::init()\n> + */\n> +\n> +/**\n> + * \\var ipa_context_ops::register_callbacks\n> + * \\brief Register callback operation from the IPA to the pipeline handler\n> + * \\param[in] ctx The IPA context\n> + * \\param[in] callback The IPA callback operations\n> + * \\param[in] cb_ctx The callback context, passed to all callback operations\n> + */\n> +\n> +/**\n> + * \\var ipa_context_ops::configure\n> + * \\brief Configure the IPA stream and sensor settings\n> + * \\param[in] ctx The IPA context\n> + *\n> + * \\sa libcamera::IPAInterface::configure()\n> + */\n> +\n> +/**\n> + * \\var ipa_context_ops::map_buffers\n> + * \\brief Map buffers shared between the pipeline handler and the IPA\n> + * \\param[in] ctx The IPA context\n> + * \\param[in] buffers The buffers to map\n> + * \\param[in] num_buffers The number of entries in the \\a buffers array\n> + *\n> + * \\sa libcamera::IPAInterface::mapBuffers()\n> + */\n> +\n> +/**\n> + * \\var ipa_context_ops::unmap_buffers\n> + * \\brief Unmap buffers shared by the pipeline to the IPA\n> + * \\param[in] ctx The IPA context\n> + * \\param[in] ids The IDs of the buffers to unmap\n> + * \\param[in] num_buffers The number of entries in the \\a ids array\n> + *\n> + * \\sa libcamera::IPAInterface::unmapBuffers()\n> + */\n> +\n> +/**\n> + * \\var ipa_context_ops::process_event\n> + * \\brief Process an event from the pipeline handler\n> + * \\param[in] ctx The IPA context\n> + *\n> + * \\sa libcamera::IPAInterface::processEvent()\n>   */\n>  \n>  namespace libcamera {\n> @@ -122,26 +349,30 @@ namespace libcamera {\n>  \n>  /**\n>   * \\class IPAInterface\n> - * \\brief Interface for IPA implementation\n> + * \\brief C++ Interface for IPA implementation\n>   *\n> - * Every pipeline handler in libcamera may attach all or some of its cameras to\n> - * an Image Processing Algorithm (IPA) module. An IPA module is developed for a\n> - * specific pipeline handler and each pipeline handler may have multiple\n> - * compatible IPA implementations, both open and closed source.\n> + * This pure virtual class defines a C++ API corresponding to the ipa_context,\n> + * ipa_context_ops and ipa_callback_ops API. It is used by pipeline handlers to\n> + * interact with IPA modules, and may be used internally in IPA modules if\n> + * desired to benefit from the data and helper classes provided by libcamera.\n> + *\n> + * Functions defined in the ipa_context_ops structure are mapped to IPAInterface\n> + * methods, while functions defined in the ipa_callback_ops are mapped to\n> + * IPAInterface signals. As with the C API, the IPA C++ interface uses\n> + * serializable data types only. It reuses structures defined by the C API, or\n> + * defines corresponding classes using C++ containers when required.\n>   *\n> - * To allow for multiple IPA modules for the same pipeline handler, a standard\n> - * interface for the pipeline handler and IPA communication is needed.\n> - * IPAInterface is this interface.\n> + * Due to process isolation all arguments to the IPAInterface methods and\n> + * signals may need to be transferred over IPC. The class thus uses serializable\n> + * data types only. The IPA C++ interface defines custom data structures that\n> + * mirror core libcamera structures when the latter are not suitable, such as\n> + * IPAStream to carry StreamConfiguration data.\n>   *\n> - * The interface defines base data types and methods to exchange data. On top of\n> - * this, each pipeline handler is responsible for defining specific operations\n> - * that make up its IPA protocol, shared by all IPA modules compatible with the\n> - * pipeline handler.\n> + * As for the functions defined in struct ipa_context_ops, the methods defined\n> + * by this class shall not return data from the IPA.\n>   *\n>   * The pipeline handler shall use the IPAManager to locate a compatible\n>   * IPAInterface. The interface may then be used to interact with the IPA module.\n> - *\n> - * \\todo Add reference to how pipelines shall document their protocol.\n>   */\n>  \n>  /**\n> -- \n> Regards,\n> \n> Laurent Pinchart\n> \n> _______________________________________________\n> libcamera-devel mailing list\n> libcamera-devel@lists.libcamera.org\n> https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<niklas.soderlund@ragnatech.se>","Received":["from mail-lf1-x141.google.com (mail-lf1-x141.google.com\n\t[IPv6:2a00:1450:4864:20::141])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 9F41960F1C\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 18 Nov 2019 23:09:01 +0100 (CET)","by mail-lf1-x141.google.com with SMTP id v24so4258387lfi.7\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 18 Nov 2019 14:09:01 -0800 (PST)","from localhost (h-93-159.A463.priv.bahnhof.se. [46.59.93.159])\n\tby smtp.gmail.com with ESMTPSA id\n\ty189sm11448169lfc.9.2019.11.18.14.08.59\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tMon, 18 Nov 2019 14:08:59 -0800 (PST)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=ragnatech-se.20150623.gappssmtp.com; s=20150623;\n\th=date:from:to:cc:subject:message-id:references:mime-version\n\t:content-disposition:content-transfer-encoding:in-reply-to\n\t:user-agent; bh=y2WCHAUP7y4ffh9gyf6ZiTEbiSjZVpbfwxxwHTlSqC0=;\n\tb=gRlACVeq5T0CSfoC9h50jwdG57xmTQkW9iwCn6k72iQneM1xMqL80mogmu8lwVLf0x\n\tpkY7zHJPXBCtSVr5IwAR+dr3XFP/gT/JmFDgGL9lLtgFbvU0dstWE8WO+EgVVUHt9H+X\n\tV3/mFEBWVH5LtupxchVvhni8jQ6ktMhCvYso+djtH+0bSuSfGTXdOTFvQMyGe8iamnN7\n\tjTDAribnml2vGx360ruIk9M0i3WgLsQSNGjgrC3vnfVNqsvy6D5eYRI+JVCb9ZejB+PX\n\tJ/ztML9FKPCxYh7RtrHKhz9lTCXWZbNafSxSzYcGuXovHdsq4Gr8/fLsDEbjkVgIohPA\n\tweow==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20161025;\n\th=x-gm-message-state:date:from:to:cc:subject:message-id:references\n\t:mime-version:content-disposition:content-transfer-encoding\n\t:in-reply-to:user-agent;\n\tbh=y2WCHAUP7y4ffh9gyf6ZiTEbiSjZVpbfwxxwHTlSqC0=;\n\tb=H5V9ppxSUGDsgrj9FZa3jtY1BCe8Km4lFFyGRkeDYIHJPSOLx0qTe8XzQXgvzD/OGj\n\tf+xqd6yqx22J+KtsUGyABL3WJ29JnxZSb5tEjdyktR2NSruA8PHpO3ptUazbhegpdPQx\n\tk418wfkh0BCHOM52kNiv7qJOfmVpR6kkOGa+/QitlN41aWlCA5Xi6NT16GZ7k2uSw9j+\n\t56H3nUfpy+oIXTe/ipB8/5+jqXombEDgdKXpk10hX+mP/oafkKUYzAepNLKrwjbWjv5O\n\tcq/V9DhgNknesXCPNy9BEshWz5wcuo3ofA/aQGC93m+RHoVkC2912BBjBtIKqZqbi055\n\tiCxQ==","X-Gm-Message-State":"APjAAAUuTL6aKQg4RXQO9UtGxhCK/PNo2yjfgdU8jXlx07vilL+2EpB4\n\tr2IWC40vXmKvTzltc9ycsHUih0I/31k=","X-Google-Smtp-Source":"APXvYqx4l1eA7wAXuhqMeInEAwyBrD4wnvIzdiaDNMMfdhpruLDlGJuWKoD+G0uWmqsydWcthB+b+Q==","X-Received":"by 2002:ac2:488e:: with SMTP id x14mr1127146lfc.81.1574114940771;\n\tMon, 18 Nov 2019 14:09:00 -0800 (PST)","Date":"Mon, 18 Nov 2019 23:08:58 +0100","From":"Niklas =?iso-8859-1?q?S=F6derlund?= <niklas.soderlund@ragnatech.se>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20191118220858.GN8072@bigcity.dyn.berto.se>","References":"<20191108205409.18845-1-laurent.pinchart@ideasonboard.com>\n\t<20191108205409.18845-20-laurent.pinchart@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=iso-8859-1","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<20191108205409.18845-20-laurent.pinchart@ideasonboard.com>","User-Agent":"Mutt/1.12.1 (2019-06-15)","Subject":"Re: [libcamera-devel] [PATCH v2 19/24] ipa: Define a plain C API","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>","X-List-Received-Date":"Mon, 18 Nov 2019 22:09:01 -0000"}}]