[{"id":16680,"web_url":"https://patchwork.libcamera.org/comment/16680/","msgid":"<20210429054540.53osulfebtcmoavb@basti-TUXEDO-Book-XA1510>","date":"2021-04-29T05:45:40","subject":"Re: [libcamera-devel] [PATCH v11] Documentation: Add IPA writers\n\tguide","submitter":{"id":78,"url":"https://patchwork.libcamera.org/api/people/78/","name":"Sebastian Fricke","email":"sebastian.fricke@posteo.net"},"content":"Hey Paul,\n\nOn 28.04.2021 18:33, Paul Elder wrote:\n>Add a guide about writing IPAs.\n\nThank you this was a nice read and very detailed as well.\n\n>\n>Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n>Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n>Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\nReviewed-by: Sebastian Fricke <sebastian.fricke@posteo.net>\n\n>\n>---\n>Changes in v11:\n>- expand introduction and concepts that should have been pre-explained\n>\n>Changes in v10:\n>- fix typos and indentation and remove todos\n>\n>Changes in v9:\n>- update documentation to include customizable init() and direct return\n>  of int32\n>\n>No change in v8\n>\n>Changes in v8:\n>- fix bullet points\n>- update wording about struct field names\n>- fix typos\n>\n>Changes in v7:\n>- fix TODO syntax\n>- update the generated struct fiels\n>  - no more postfix underscore\n>\n>Changes in v6:\n>- namespacing is now required\n>- start() is now customizable\n>- {pipeline_name} is no longer required\n>- fix code block indentations\n>\n>Changes in v5:\n>- fix doxygen compile errors\n>- update example struct names from raspberry pi changes\n>- add todo for restricting pre-start() to sync and post-start() to async\n>\n>Changes in v4.1:\n>- Add section on namespacing, custom data structures, compiling, and\n>  usage of the data structures and interface\n>- Add examples to the main ipa interface and event ipa interface\n>  sections\n>\n>New in v4\n>---\n> Documentation/guides/ipa.rst | 511 +++++++++++++++++++++++++++++++++++\n> Documentation/index.rst      |   1 +\n> Documentation/meson.build    |   1 +\n> 3 files changed, 513 insertions(+)\n> create mode 100644 Documentation/guides/ipa.rst\n>\n>diff --git a/Documentation/guides/ipa.rst b/Documentation/guides/ipa.rst\n>new file mode 100644\n>index 00000000..e53486d1\n>--- /dev/null\n>+++ b/Documentation/guides/ipa.rst\n>@@ -0,0 +1,511 @@\n>+.. SPDX-License-Identifier: CC-BY-SA-4.0\n>+\n>+IPA Writer's Guide\n>+==================\n>+\n>+IPA modules are Image Processing Algorithm modules. They provide functionality\n>+that the pipeline handler can use for image processing.\n>+\n>+This guide covers the definition of the IPA interface, and how to plumb the\n>+connection between the pipeline handler and the IPA.\n>+\n>+The IPA interface and protocol\n>+------------------------------\n>+\n>+The IPA interface defines the interface between the pipeline handler and the\n>+IPA. Specifically, it defines the functions that the IPA exposes that the\n>+pipeline handler can call, and the signals that the pipeline handler can\n>+connect to, in order to receive data from the IPA asynchronously. In addition,\n>+it contains any custom data structures that the pipeline handler and IPA may\n>+pass to each other.\n>+\n>+The IPA protocol refers to the agreement between the pipeline handler and the\n>+IPA regarding the expected response(s) from the IPA for given calls to the IPA.\n>+This protocol doesn't need to be declared anywhere in code, but it shall be\n>+documented, as there may be multiple IPA implementations for one pipeline\n>+handler.\n>+\n>+As part of the design of libcamera, IPAs may be isolated in a separate process,\n>+or run in the same process but a different thread from libcamera. The pipeline\n>+handler and IPA shall not have to change their operation based on whether the\n>+IPA is isolated or not, but the possibility of isolation needs to be kept in\n>+mind. Therefore all data that is passed between them must be serializable, so\n>+they must be defined separately in the `mojo Interface Definition Language`_\n>+(IDL), and a code generator will generate headers and serializers corresponding\n>+to the definitions. Every interface is defined in a mojom file and includes:\n>+\n>+- the functions that the pipeline handler can call from the IPA\n>+- signals in the pipeline handler that the IPA can emit\n>+- any data structures that are to be passed between the pipeline handler and the IPA\n>+\n>+All IPA modules of a given pipeline handler use the same IPA interface. The IPA\n>+interface definition is thus written by the pipeline handler author, based on\n>+how they design the interactions between the pipeline handler and the IPA.\n>+\n>+The entire IPA interface, including the functions, signals, and any custom\n>+structs shall be defined in a file named {pipeline_name}.mojom under\n>+include/libcamera/ipa/.\n>+\n>+.. _mojo Interface Definition Language: https://chromium.googlesource.com/chromium/src.git/+/master/mojo/public/tools/bindings/README.md\n>+\n>+Namespacing\n>+-----------\n>+\n>+To avoid name collisions between data types defined by different IPA interfaces\n>+and data types defined by libcamera, each IPA interface must be defined in its\n>+own namespace.\n>+\n>+The namespace is specific with mojo's module directive. It must be the first\n>+non-comment line in the mojo data definition file. For example, the Raspberry\n>+Pi IPA interface uses:\n>+\n>+.. code-block:: none\n>+\n>+        module ipa.rpi;\n>+\n>+This will become the ipa::rpi namespace in C++ code.\n>+\n>+Data containers\n>+---------------\n>+\n>+Since the data passed between the pipeline handler and the IPA must support\n>+serialization, any custom data containers must be defined with the mojo IDL.\n>+\n>+The following list of libcamera objects are supported in the interface\n>+definition, and may be used as function parameter types or struct field types:\n>+\n>+- libcamera.CameraSensorInfo\n>+- libcamera.ControlInfoMap\n>+- libcamera.ControlList\n>+- libcamera.FileDescriptor\n>+- libcamera.IPABuffer\n>+- libcamera.IPASettings\n>+- libcamera.IPAStream\n>+- libcamera.Point\n>+- libcamera.Rectangle\n>+- libcamera.Size\n>+- libcamera.SizeRange\n>+\n>+To use them, core.mojom must be included in the mojo data definition file:\n>+\n>+.. code-block:: none\n>+\n>+        import \"include/libcamera/ipa/core.mojom\";\n>+\n>+Other custom structs may be defined and used as well. There is no requirement\n>+that they must be defined before usage. enums and structs are supported.\n>+\n>+The following is an example of a definition of an enum, for the purpose of\n>+being used as flags:\n>+\n>+.. code-block:: none\n>+\n>+        enum ConfigParameters {\n>+                ConfigLsTable = 0x01,\n>+                ConfigStaggeredWrite = 0x02,\n>+                ConfigSensor = 0x04,\n>+                ConfigDropFrames = 0x08,\n>+        };\n>+\n>+The following is an example of a definition of a struct:\n>+\n>+.. code-block:: none\n>+\n>+        struct ConfigInput {\n>+                uint32 op;\n>+                uint32 transform;\n>+                libcamera.FileDescriptor lsTableHandle;\n>+                int32 lsTableHandleStatic = -1;\n>+                map<uint32, libcamera.IPAStream> streamConfig;\n>+                array<libcamera.IPABuffer> buffers;\n>+        };\n>+\n>+This example has some special things about it. First of all, it uses the\n>+FileDescriptor data type. This type must be used to ensure that the file\n>+descriptor that it contains is translated properly across the IPC boundary\n>+(when the IPA is in an isolated process).\n>+\n>+This does mean that if the file descriptor should be sent without being\n>+translated (for example, for the IPA to tell the pipeline handler which\n>+fd *that the pipeline handler holds* to act on), then it must be in a\n>+regular int32 type.\n>+\n>+This example also illustrates that struct fields may have default values, as\n>+is assigned to lsTableHandleStatic. This is the value that the field will\n>+take when the struct is constructed with the default constructor.\n>+\n>+Arrays and maps are supported as well. They are translated to C++ vectors and\n>+maps, respectively. The members of the arrays and maps are embedded, and cannot\n>+be const.\n>+\n>+Note that nullable fields, static-length arrays, handles, and unions, which\n>+are supported by mojo, are not supported by our code generator.\n>+\n>+The Main IPA interface\n>+----------------------\n>+\n>+The IPA interface is split in two parts, the Main IPA interface, which\n>+describes the functions that the pipeline handler can call from the IPA,\n>+and the Event IPA interface, which describes the signals received by the\n>+pipeline handler that the IPA can emit. Both must be defined. This section\n>+focuses on the Main IPA interface.\n>+\n>+The main interface must be named as IPA{pipeline_name}Interface.\n>+\n>+The functions that the pipeline handler can call from the IPA may be\n>+synchronous or asynchronous. Synchronous functions do not return until the IPA\n>+returns from the function, while asynchronous functions return immediately\n>+without waiting for the IPA to return.\n>+\n>+At a minimum, the following three functions must be present (and implemented):\n>+\n>+- init();\n>+- start();\n>+- stop();\n>+\n>+All three of these functions are synchronous. The parameters for start() and\n>+init() may be customized.\n>+\n>+A configure() method is recommended. Any ControlInfoMap instances that will be\n>+used by the IPA must be sent to the IPA from the pipeline handler, at configure\n>+time, for example.\n>+\n>+All input parameters will become const references, except for arithmetic types,\n>+which will be passed by value. Output parameters will become pointers, unless\n>+the first output parameter is an int32, or there is only one primitive output\n>+parameter, in which case it will become a regular return value.\n>+\n>+const is not allowed inside of arrays and maps. mojo arrays will become C++\n>+std::vector<>.\n>+\n>+By default, all methods defined in the main interface are synchronous. This\n>+means that in the case of IPC (i.e. isolated IPA), the function call will not\n>+return until the return value or output parameters are ready. To specify an\n>+asynchronous function, the [async] attribute can be used. Asynchronous\n>+methods must not have any return value or output parameters, since in the\n>+case of IPC the call needs to return immediately.\n>+\n>+It is also possible that the IPA will not be run in isolation. In this case,\n>+the IPA thread will not exist until start() is called. This means that in the\n>+case of no isolation, asynchronous calls cannot be made before start(). Since\n>+the IPA interface must be the same regardless of isolation, the same\n>+restriction applies to the case of isolation, and any function that will be\n>+called before start() must be synchronous.\n>+\n>+In addition, any call made after start() and before stop() must be\n>+asynchronous. The motivation for this is to avoid damaging real-time\n>+performance of the pipeline handler. If the pipeline handler wants some data\n>+from the IPA, the IPA should return the data asynchronously via an event\n>+(see \"The Event IPA interface\").\n>+\n>+The following is an example of a main interface definition:\n>+\n>+.. code-block:: none\n>+\n>+        interface IPARPiInterface {\n>+                init(libcamera.IPASettings settings, string sensorName)\n>+                        => (int32 ret, bool metadataSupport);\n>+                start() => (int32 ret);\n>+                stop();\n>+\n>+                configure(libcamera.CameraSensorInfo sensorInfo,\n>+                          map<uint32, libcamera.IPAStream> streamConfig,\n>+                          map<uint32, libcamera.ControlInfoMap> entityControls,\n>+                          ConfigInput ipaConfig)\n>+                        => (int32 ret, ConfigOutput results);\n>+\n>+                mapBuffers(array<IPABuffer> buffers);\n>+                unmapBuffers(array<uint32> ids);\n>+\n>+                [async] signalStatReady(uint32 bufferId);\n>+                [async] signalQueueRequest(libcamera.ControlList controls);\n>+                [async] signalIspPrepare(ISPConfig data);\n>+        };\n>+\n>+\n>+The first three functions are the required functions. Functions do not need to\n>+have return values, like stop(), mapBuffers(), and unmapBuffers(). In the case\n>+of asynchronous functions, as explained before, they *must not* have return\n>+values.\n>+\n>+The Event IPA interface\n>+-----------------------\n>+\n>+The event IPA interface describes the signals received by the pipeline handler\n>+that the IPA can emit. It must be defined. If there are no event functions,\n>+then it may be empty. These emissions are meant to notify the pipeline handler\n>+of some event, such as request data is ready, and *must not* be used to drive\n>+the camera pipeline from the IPA.\n>+\n>+The event interface must be named as IPA{pipeline_name}EventInterface.\n>+\n>+Methods defined in the event interface are implicitly asynchronous.\n>+Thus they cannot return any value. Specifying the [async] tag is not\n>+necessary.\n>+\n>+Methods defined in the event interface will become signals in the IPA\n>+interface. The IPA can emit signals, while the pipeline handler can connect\n>+slots to them.\n>+\n>+The following is an example of an event interface definition:\n>+\n>+.. code-block:: none\n>+\n>+        interface IPARPiEventInterface {\n>+                statsMetadataComplete(uint32 bufferId,\n>+                                      libcamera.ControlList controls);\n>+                runIsp(uint32 bufferId);\n>+                embeddedComplete(uint32 bufferId);\n>+                setIsp(libcamera.ControlList controls);\n>+                setStaggered(libcamera.ControlList controls);\n>+        };\n>+\n>+Compiling the IPA interface\n>+---------------------------\n>+\n>+After the IPA interface is defined in include/libcamera/ipa/{pipeline_name}.mojom,\n>+an entry for it must be added in meson so that it can be compiled. The filename\n>+must be added to the ipa_mojom_files object in include/libcamera/ipa/meson.build.\n>+\n>+For example, adding the raspberrypi.mojom file to meson:\n>+\n>+.. code-block:: none\n>+\n>+        ipa_mojom_files = [\n>+            'raspberrypi.mojom',\n>+        ]\n>+\n>+This will cause the mojo data definition file to be compiled. Specifically, it\n>+generates five files:\n>+\n>+- a header describing the custom data structures, and the complete IPA\n>+  interface (at {$build_dir}/include/libcamera/ipa/{pipeline}_ipa_interface.h)\n>+\n>+- a serializer implementing de/serialization for the custom data structures (at\n>+  {$build_dir}/include/libcamera/ipa/{pipeline}_ipa_serializer.h)\n>+\n>+- a proxy header describing a specialized IPA proxy (at\n>+  {$build_dir}/include/libcamera/ipa/{pipeline}_ipa_proxy.h)\n>+\n>+- a proxy source implementing the IPA proxy (at\n>+  {$build_dir}/src/libcamera/proxy/{pipeline}_ipa_proxy.cpp)\n>+\n>+- a proxy worker source implementing the other end of the IPA proxy (at\n>+  {$build_dir}/src/libcamera/proxy/worker/{pipeline}_ipa_proxy_worker.cpp)\n>+\n>+The IPA proxy serves as the layer between the pipeline handler and the IPA, and\n>+handles threading vs isolation transparently. The pipeline handler and the IPA\n>+only require the interface header and the proxy header. The serializer is only\n>+used internally by the proxy.\n>+\n>+Using the custom data structures\n>+--------------------------------\n>+\n>+To use the custom data structures that are defined in the mojo data definition\n>+file, the following header must be included:\n>+\n>+.. code-block:: C++\n>+\n>+   #include <libcamera/ipa/{pipeline_name}_ipa_interface.h>\n>+\n>+The POD types of the structs simply become their C++ counterparts, eg. uint32\n>+in mojo will become uint32_t in C++. mojo map becomes C++ std::map, and mojo\n>+array becomes C++ std::vector. All members of maps and vectors are embedded,\n>+and are not pointers. The members cannot be const.\n>+\n>+The names of all the fields of structs can be used in C++ in exactly the same\n>+way as they are defined in the data definition file. For example, the following\n>+struct as defined in the mojo file:\n>+\n>+.. code-block:: none\n>+\n>+   struct SensorConfig {\n>+        uint32 gainDelay = 1;\n>+        uint32 exposureDelay;\n>+        uint32 sensorMetadata;\n>+   };\n>+\n>+Will become this in C++:\n>+\n>+.. code-block:: C++\n>+\n>+   struct SensorConfig {\n>+        uint32_t gainDelay;\n>+        uint32_t exposureDelay;\n>+        uint32_t sensorMetadata;\n>+   };\n>+\n>+The generated structs will also have two constructors, a constructor that\n>+fills all fields with the default values, and a second constructor that takes\n>+a value for every field. The default value constructor will fill in the fields\n>+with the specified default value if it exists. In the above example, `gainDelay_`\n>+will be initialized to 1. If no default value is specified, then it will be\n>+filled in as zero (or -1 for a FileDescriptor type).\n>+\n>+All fields and constructors/destructors in these generated structs are public.\n>+\n>+Using the IPA interface (pipeline handler)\n>+------------------------------------------\n>+\n>+The following headers are necessary to use an IPA in the pipeline handler\n>+(with raspberrypi as an example):\n>+\n>+.. code-block:: C++\n>+\n>+   #include <libcamera/ipa/raspberrypi_ipa_interface.h>\n>+   #include <libcamera/ipa/raspberrypi_ipa_proxy.h>\n>+\n>+The first header includes definitions of the custom data structures, and\n>+the definition of the complete IPA interface (including both the Main and\n>+the Event IPA interfaces). The name of the header file comes from the name\n>+of the mojom file, which in this case was raspberrypi.mojom.\n>+\n>+The second header includes the definition of the specialized IPA proxy. It\n>+exposes the complete IPA interface. We will see how to use it in this section.\n>+\n>+In the pipeline handler, we first need to construct a specialized IPA proxy.\n>+From the point of view of the pipeline hander, this is the object that is the\n>+IPA.\n>+\n>+To do so, we invoke the IPAManager:\n>+\n>+.. code-block:: C++\n>+\n>+        std::unique_ptr<ipa::rpi::IPAProxyRPi> ipa_ =\n>+                IPAManager::createIPA<ipa::rpi::IPAProxyRPi>(pipe_, 1, 1);\n>+\n>+The ipa::rpi namespace comes from the namespace that we defined in the mojo\n>+data definition file, in the \"Namespacing\" section. The name of the proxy,\n>+IPAProxyRPi, comes from the name given to the main IPA interface,\n>+IPARPiInterface, in the \"The Main IPA interface\" section.\n>+\n>+The return value of IPAManager::createIPA shall be error-checked, to confirm\n>+that the returned pointer is not a nullptr.\n>+\n>+After this, before initializing the IPA, slots should be connected to all of\n>+the IPA's signals, as defined in the Event IPA interface:\n>+\n>+.. code-block:: C++\n>+\n>+\tipa_->statsMetadataComplete.connect(this, &RPiCameraData::statsMetadataComplete);\n>+\tipa_->runIsp.connect(this, &RPiCameraData::runIsp);\n>+\tipa_->embeddedComplete.connect(this, &RPiCameraData::embeddedComplete);\n>+\tipa_->setIsp.connect(this, &RPiCameraData::setIsp);\n>+\tipa_->setStaggered.connect(this, &RPiCameraData::setStaggered);\n>+\n>+The slot functions have a function signature based on the function definition\n>+in the Event IPA interface. All plain old data (POD) types are as-is (with\n>+their C++ versions, eg. uint32 -> uint32_t), and all structs are const references.\n>+\n>+For example, for the following entry in the Event IPA interface:\n>+\n>+.. code-block:: none\n>+\n>+   statsMetadataComplete(uint32 bufferId, ControlList controls);\n>+\n>+A function with the following function signature shall be connected to the\n>+signal:\n>+\n>+.. code-block:: C++\n>+\n>+   void statsMetadataComplete(uint32_t bufferId, const ControlList &controls);\n>+\n>+After connecting the slots to the signals, the IPA should be initialized\n>+(using the main interface definition example from earlier):\n>+\n>+.. code-block:: C++\n>+\n>+   IPASettings settings{};\n>+   bool metadataSupport;\n>+   int ret = ipa_->init(settings, \"sensor name\", &metadataSupport);\n>+\n>+At this point, any IPA functions that were defined in the Main IPA interface\n>+can be called as if they were regular member functions, for example (based on\n>+the main interface definition example from earlier):\n>+\n>+.. code-block:: C++\n>+\n>+   ipa_->start();\n>+   int ret = ipa_->configure(sensorInfo_, streamConfig, entityControls, ipaConfig, &result);\n>+   ipa_->signalStatReady(RPi::BufferMask::STATS | static_cast<unsigned int>(index));\n>+\n>+Remember that any functions designated as asynchronous *must not* be called\n>+before start().\n>+\n>+Notice that for both init() and configure(), the first output parameter is a\n>+direct return, since it is an int32, while the other output parameter is a\n>+pointer-based output parameter.\n>+\n>+Using the IPA interface (IPA Module)\n>+-----------------------------\n>+\n>+The following header is necessary to implement an IPA Module (with raspberrypi\n>+as an example):\n>+\n>+.. code-block:: C++\n>+\n>+   #include <libcamera/ipa/raspberrypi_ipa_interface.h>\n>+\n>+This header includes definitions of the custom data structures, and\n>+the definition of the complete IPA interface (including both the Main and\n>+the Event IPA interfaces). The name of the header file comes from the name\n>+of the mojom file, which in this case was raspberrypi.mojom.\n>+\n>+The IPA module must implement the IPA interface class that is defined in the\n>+header. In the case of our example, that is ipa::rpi::IPARPiInterface. The\n>+ipa::rpi namespace comes from the namespace that we defined in the mojo data\n>+definition file, in the \"Namespacing\" section. The name of the interface is the\n>+same as the name given to the Main IPA interface.\n>+\n>+The function signature rules are the same as for the slots in the pipeline\n>+handler side; PODs are passed by value, and structs are passed by const\n>+reference. For the Main IPA interface, output values are also allowed (only\n>+for synchronous calls), so there may be output parameters as well. If the\n>+first output parameter is a POD it will be returned by value, otherwise\n>+it will be returned by an output parameter pointer. The second and any other\n>+output parameters will also be returned by output parameter pointers.\n>+\n>+For example, for the following function specification in the Main IPA interface\n>+definition:\n>+\n>+.. code-block:: none\n>+\n>+   configure(libcamera.CameraSensorInfo sensorInfo,\n>+             uint32 exampleNumber,\n>+             map<uint32, libcamera.IPAStream> streamConfig,\n>+             map<uint32, libcamera.ControlInfoMap> entityControls,\n>+             ConfigInput ipaConfig)\n>+   => (int32 ret, ConfigOutput results);\n>+\n>+We will need to implement a function with the following function signature:\n>+\n>+.. code-block:: C++\n>+\n>+        int configure(const CameraSensorInfo &sensorInfo,\n>+                      uint32_t exampleNumber,\n>+                      const std::map<unsigned int, IPAStream> &streamConfig,\n>+                      const std::map<unsigned int, ControlInfoMap> &entityControls,\n>+                      const ipa::rpi::ConfigInput &data,\n>+                      ipa::rpi::ConfigOutput *response);\n>+\n>+The return value is int, because the first output parameter is int32.  The rest\n>+of the output parameters (in this case, only response) become output parameter\n>+pointers. The non-POD input parameters become const references, and the POD\n>+input parameter is passed by value.\n>+\n>+At any time after start() and before stop() (though usually only in response to\n>+an IPA call), the IPA may send data to the pipeline handler by emitting\n>+signals. These signals are defined in the C++ IPA interface class (which is in\n>+the generated and included header).\n>+\n>+For example, for the following function defined in the Event IPA interface:\n>+\n>+.. code-block:: none\n>+\n>+   statsMetadataComplete(uint32 bufferId, libcamera.ControlList controls);\n>+\n>+We can emit a signal like so:\n>+\n>+.. code-block:: C++\n>+\n>+   statsMetadataComplete.emit(bufferId & RPi::BufferMask::ID, libcameraMetadata_);\n>diff --git a/Documentation/index.rst b/Documentation/index.rst\n>index 7e3fe2f6..1f4fc485 100644\n>--- a/Documentation/index.rst\n>+++ b/Documentation/index.rst\n>@@ -17,6 +17,7 @@\n>    Developer Guide <guides/introduction>\n>    Application Writer's Guide <guides/application-developer>\n>    Pipeline Handler Writer's Guide <guides/pipeline-handler>\n>+   IPA Writer's guide <guides/ipa>\n>    Tracing guide <guides/tracing>\n>    Environment variables <environment_variables>\n>    Sensor driver requirements <sensor_driver_requirements>\n>diff --git a/Documentation/meson.build b/Documentation/meson.build\n>index 9950465d..8cf68a07 100644\n>--- a/Documentation/meson.build\n>+++ b/Documentation/meson.build\n>@@ -54,6 +54,7 @@ if sphinx.found()\n>         'environment_variables.rst',\n>         'guides/application-developer.rst',\n>         'guides/introduction.rst',\n>+        'guides/ipa.rst',\n>         'guides/pipeline-handler.rst',\n>         'guides/tracing.rst',\n>         'index.rst',\n>-- \n>2.27.0\n>\n>_______________________________________________\n>libcamera-devel mailing list\n>libcamera-devel@lists.libcamera.org\n>https://lists.libcamera.org/listinfo/libcamera-devel","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 E8D37BDE45\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 29 Apr 2021 05:45:44 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 1A0936890F;\n\tThu, 29 Apr 2021 07:45:44 +0200 (CEST)","from mout01.posteo.de (mout01.posteo.de [185.67.36.65])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id CA78E688AF\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 29 Apr 2021 07:45:42 +0200 (CEST)","from submission (posteo.de [89.146.220.130]) \n\tby mout01.posteo.de (Postfix) with ESMTPS id 343B7240026\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 29 Apr 2021 07:45:42 +0200 (CEST)","from customer (localhost [127.0.0.1])\n\tby submission (posteo.de) with ESMTPSA id 4FW4Gs4QZmz6tmF;\n\tThu, 29 Apr 2021 07:45:41 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n\tunprotected) header.d=posteo.net header.i=@posteo.net\n\theader.b=\"DfisA+EZ\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=posteo.net; s=2017;\n\tt=1619675142; bh=uvSpRbdWY9ad7Gqm6rfpSKdexOWK4nWVnfMBgy25OAA=;\n\th=Date:From:To:Cc:Subject:From;\n\tb=DfisA+EZRwwTHuMNeE9VoLCuuFwLmG8dSKzOgZobxVmIY7CPl30CIhrVB1tzEaCAS\n\tZ6aOhUNiaewV1KSI72EZzKpTrM9s3a2GI8jznwfqnQMxVaA09BWjgnMthSpMz+ugyX\n\tF+WQzNnbWPwbS29RQtMKCdioaaIWcqFTDGxwJicXGGnBaLumCIlPkm3CNkHl2BXpQg\n\tkGMTWKXMNZNzDFiGOArFzDN36gkItYA+DekTIEzKXm8Wcsju3iIQTLI5lRdMvDvl5i\n\tDsncQw3W1jRhUeeMD+w5bPxbCpyqVcyXkk+99AcmyeTfBH/3FTEb17xkoP5HJE0Zpg\n\tq1JXejY6zSHiw==","Date":"Thu, 29 Apr 2021 05:45:40 +0000","From":"Sebastian Fricke <sebastian.fricke@posteo.net>","To":"Paul Elder <paul.elder@ideasonboard.com>","Message-ID":"<20210429054540.53osulfebtcmoavb@basti-TUXEDO-Book-XA1510>","References":"<20210428093331.478728-1-paul.elder@ideasonboard.com>","MIME-Version":"1.0","Content-Disposition":"inline","In-Reply-To":"<20210428093331.478728-1-paul.elder@ideasonboard.com>","Subject":"Re: [libcamera-devel] [PATCH v11] Documentation: Add IPA writers\n\tguide","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-Transfer-Encoding":"7bit","Content-Type":"text/plain; charset=\"us-ascii\"; Format=\"flowed\"","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]