Show a patch.

GET /api/1.1/patches/21820/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 21820,
    "url": "https://patchwork.libcamera.org/api/1.1/patches/21820/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/21820/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/1.1/projects/1/?format=api",
        "name": "libcamera",
        "link_name": "libcamera",
        "list_id": "libcamera_core",
        "list_email": "libcamera-devel@lists.libcamera.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20241106175901.83960-3-jacopo.mondi@ideasonboard.com>",
    "date": "2024-11-06T17:58:52",
    "name": "[2/3] pipeline: Add support for dumping capture script and metadata",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "e558593a31581f6d0813c0765c92a48293b9fa4b",
    "submitter": {
        "id": 143,
        "url": "https://patchwork.libcamera.org/api/1.1/people/143/?format=api",
        "name": "Jacopo Mondi",
        "email": "jacopo.mondi@ideasonboard.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/21820/mbox/",
    "series": [
        {
            "id": 4773,
            "url": "https://patchwork.libcamera.org/api/1.1/series/4773/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=4773",
            "date": "2024-11-06T17:58:50",
            "name": "libcamera: Add support for dumping capture script in YAML",
            "version": 1,
            "mbox": "https://patchwork.libcamera.org/series/4773/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/21820/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/21820/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 8CA56BE173\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed,  6 Nov 2024 17:59:15 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id D325D65437;\n\tWed,  6 Nov 2024 18:59:13 +0100 (CET)",
            "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 42C9165436\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed,  6 Nov 2024 18:59:10 +0100 (CET)",
            "from ideasonboard.com (93-61-96-190.ip145.fastwebnet.it\n\t[93.61.96.190])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id D13DD475;\n\tWed,  6 Nov 2024 18:59:01 +0100 (CET)"
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"Nk/qfJC8\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1730915942;\n\tbh=xLJFCIv1YwnDXvk5Oodq7BJFfBd99jga0gCntxrLLdw=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=Nk/qfJC8nRWYgjkJB/xvaI7XJ0BZe12O0kffwZcOPjNcketQNVoQSzEHBxnzZs1+L\n\tUY8wb18T39Qci+N2a7gc8Zve4UIq/xv2PVH8jn8uVKYSepmonNP0Mrz0IhPrWWkpqC\n\tjg1i6vMB7vqQXnLjjn6P1H4moXOVSM658UItdscg=",
        "From": "Jacopo Mondi <jacopo.mondi@ideasonboard.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Cc": "Jacopo Mondi <jacopo.mondi@ideasonboard.com>,\n\tPaul Elder <paul.elder@ideasonboard.com>",
        "Subject": "[PATCH 2/3] pipeline: Add support for dumping capture script and\n\tmetadata",
        "Date": "Wed,  6 Nov 2024 18:58:52 +0100",
        "Message-ID": "<20241106175901.83960-3-jacopo.mondi@ideasonboard.com>",
        "X-Mailer": "git-send-email 2.47.0",
        "In-Reply-To": "<20241106175901.83960-1-jacopo.mondi@ideasonboard.com>",
        "References": "<20241106175901.83960-1-jacopo.mondi@ideasonboard.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "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>",
        "Errors-To": "libcamera-devel-bounces@lists.libcamera.org",
        "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"
    },
    "content": "From: Paul Elder <paul.elder@ideasonboard.com>\n\nAdd support for dumping capture scripts and metadata using the Yaml\nEmitter helpers.\n\nThe capture scripts can then be fed into the cam application and a\ncapture can thus be \"replayed\". Metadata can also be dumped.\n\nCamera configuration is also dumped to the capture script. The cam\napplication currently does not support loading configuration from the\ncapture script, but support for that will be added in a subsequent\npatch.\n\nThese can be enabled by a new environment variable.\n\nSigned-off-by: Paul Elder <paul.elder@ideasonboard.com>\n[rebased on the YamlEmitter classes]\nSigned-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n---\n include/libcamera/internal/camera.h           |  3 +\n include/libcamera/internal/pipeline_handler.h | 23 +++++\n src/libcamera/camera.cpp                      | 13 +++\n src/libcamera/pipeline_handler.cpp            | 93 +++++++++++++++++++\n 4 files changed, 132 insertions(+)",
    "diff": "diff --git a/include/libcamera/internal/camera.h b/include/libcamera/internal/camera.h\nindex 0add0428bb5d..a42f03d4c755 100644\n--- a/include/libcamera/internal/camera.h\n+++ b/include/libcamera/internal/camera.h\n@@ -19,6 +19,8 @@\n \n namespace libcamera {\n \n+enum class Orientation;\n+\n class CameraControlValidator;\n class PipelineHandler;\n class Stream;\n@@ -65,6 +67,7 @@ private:\n \tstd::string id_;\n \tstd::set<Stream *> streams_;\n \tstd::set<const Stream *> activeStreams_;\n+\tOrientation orientation_;\n \n \tbool disconnected_;\n \tstd::atomic<State> state_;\ndiff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h\nindex 0d38080369c5..89d10b373cfa 100644\n--- a/include/libcamera/internal/pipeline_handler.h\n+++ b/include/libcamera/internal/pipeline_handler.h\n@@ -9,6 +9,7 @@\n \n #include <memory>\n #include <queue>\n+#include <set>\n #include <string>\n #include <sys/types.h>\n #include <vector>\n@@ -18,8 +19,12 @@\n #include <libcamera/controls.h>\n #include <libcamera/stream.h>\n \n+#include \"libcamera/internal/yaml_emitter.h\"\n+\n namespace libcamera {\n \n+enum class Orientation;\n+\n class Camera;\n class CameraConfiguration;\n class CameraManager;\n@@ -68,6 +73,9 @@ public:\n \n \tCameraManager *cameraManager() const { return manager_; }\n \n+\tvoid dumpConfiguration(const std::set<const Stream *> &streams,\n+\t\t\t       const Orientation &orientation);\n+\n protected:\n \tvoid registerCamera(std::shared_ptr<Camera> camera);\n \tvoid hotplugMediaDevice(MediaDevice *media);\n@@ -81,6 +89,11 @@ protected:\n \tCameraManager *manager_;\n \n private:\n+\tenum DumpMode {\n+\t\tControls,\n+\t\tMetadata,\n+\t};\n+\n \tvoid unlockMediaDevices();\n \n \tvoid mediaDeviceDisconnected(MediaDevice *media);\n@@ -89,6 +102,8 @@ private:\n \tvoid doQueueRequest(Request *request);\n \tvoid doQueueRequests();\n \n+\tvoid dumpRequest(Request *request, DumpMode mode);\n+\n \tstd::vector<std::shared_ptr<MediaDevice>> mediaDevices_;\n \tstd::vector<std::weak_ptr<Camera>> cameras_;\n \n@@ -97,6 +112,14 @@ private:\n \tconst char *name_;\n \tunsigned int useCount_;\n \n+\tYamlRoot controlsEmitter_;\n+\tYamlDict controlsDict_;\n+\tYamlList controlsList_;\n+\n+\tYamlRoot metadataEmitter_;\n+\tYamlDict metadataDict_;\n+\tYamlList metadataList_;\n+\n \tfriend class PipelineHandlerFactoryBase;\n };\n \ndiff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\nindex 7507e9ddae77..9d28b1202397 100644\n--- a/src/libcamera/camera.cpp\n+++ b/src/libcamera/camera.cpp\n@@ -1215,6 +1215,9 @@ int Camera::configure(CameraConfiguration *config)\n \t\td->activeStreams_.insert(stream);\n \t}\n \n+\t/* TODO Save sensor configuration for dumping it to capture script */\n+\td->orientation_ = config->orientation;\n+\n \td->setState(Private::CameraConfigured);\n \n \treturn 0;\n@@ -1356,6 +1359,16 @@ int Camera::start(const ControlList *controls)\n \n \tASSERT(d->requestSequence_ == 0);\n \n+\t/*\n+\t * Invoke method in blocking mode to avoid the risk of writing after\n+\t * streaming has started.\n+\t * This needs to be here as PipelineHandler::start is a virtual function\n+\t * so it is impractical to add the dumping there.\n+\t * TODO Pass the sensor configuration, once it is supported\n+\t */\n+\td->pipe_->invokeMethod(&PipelineHandler::dumpConfiguration,\n+\t\t\t       ConnectionTypeBlocking, d->activeStreams_, d->orientation_);\n+\n \tret = d->pipe_->invokeMethod(&PipelineHandler::start,\n \t\t\t\t     ConnectionTypeBlocking, this, controls);\n \tif (ret)\ndiff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp\nindex e5940469127e..34111589ab22 100644\n--- a/src/libcamera/pipeline_handler.cpp\n+++ b/src/libcamera/pipeline_handler.cpp\n@@ -8,6 +8,7 @@\n #include \"libcamera/internal/pipeline_handler.h\"\n \n #include <chrono>\n+#include <fstream>\n #include <sys/stat.h>\n #include <sys/sysmacros.h>\n \n@@ -464,6 +465,8 @@ void PipelineHandler::doQueueRequest(Request *request)\n \n \trequest->_d()->sequence_ = data->requestSequence_++;\n \n+\tdumpRequest(request, DumpMode::Controls);\n+\n \tif (request->_d()->cancelled_) {\n \t\tcompleteRequest(request);\n \t\treturn;\n@@ -555,6 +558,8 @@ void PipelineHandler::completeRequest(Request *request)\n \n \trequest->_d()->complete();\n \n+\tdumpRequest(request, DumpMode::Metadata);\n+\n \tCamera::Private *data = camera->_d();\n \n \twhile (!data->queuedRequests_.empty()) {\n@@ -758,6 +763,94 @@ void PipelineHandler::disconnect()\n  * \\return The CameraManager for this pipeline handler\n  */\n \n+/**\n+ * \\brief Dump the camera configuration to YAML format\n+ *\n+ * Dump to the file path specified in the LIBCAMERA_DUMP_CAPTURE_SCRIPT\n+ * environment variable, if any, the Camera configuration in YAML format.\n+ */\n+void PipelineHandler::dumpConfiguration(const std::set<const Stream *> &streams,\n+\t\t\t\t\tconst Orientation &orientation)\n+{\n+\tconst char *file = utils::secure_getenv(\"LIBCAMERA_DUMP_CAPTURE_SCRIPT\");\n+\tif (!file)\n+\t\treturn;\n+\n+\tstd::string filePath(file);\n+\tLOG(Pipeline, Debug) << \"Dumping controls in YAML format to: \"\n+\t\t\t     << filePath;\n+\n+\t/* Create the YAML roots for controls and metadata output files. */\n+\n+\tcontrolsEmitter_ = YamlEmitter::root(filePath);\n+\tcontrolsDict_ = controlsEmitter_.dict();\n+\n+\t/*\n+\t * Metadata needs to go into a separate file because otherwise it'll\n+\t * flood the capture script\n+\t */\n+\tfilePath += \".metadata\";\n+\tLOG(Pipeline, Debug) << \"Dumping metadata in YAML format to: \"\n+\t\t\t     << filePath;\n+\tmetadataEmitter_ = YamlEmitter::root(filePath);\n+\tmetadataDict_ = metadataEmitter_.dict();\n+\tmetadataList_ = metadataDict_.list(\"frames\");\n+\n+\tYamlDict configurationDict = controlsDict_.dict(\"configuration\");\n+\tstd::stringstream o;\n+\to << orientation;\n+\tconfigurationDict[\"orientation\"] = o.str();\n+\n+\t/* \\todo Dump Sensor configuration */\n+\n+\tYamlList streamsList = configurationDict.list(\"streams\");\n+\n+\tfor (const auto &stream : streams) {\n+\t\tconst StreamConfiguration &streamConfig = stream->configuration();\n+\t\tYamlDict yamlStream = streamsList.dict();\n+\n+\t\tyamlStream[\"pixelformat\"] = streamConfig.pixelFormat.toString();\n+\t\tyamlStream[\"size\"] = streamConfig.size.toString();\n+\t\tyamlStream[\"stride\"] = std::to_string(streamConfig.stride);\n+\t\tyamlStream[\"frameSize\"] = std::to_string(streamConfig.frameSize);\n+\t\tyamlStream[\"bufferCount\"] = std::to_string(streamConfig.bufferCount);\n+\n+\t\tif (streamConfig.colorSpace)\n+\t\t\tyamlStream[\"colorSpace\"] =\n+\t\t\t\tstreamConfig.colorSpace->toString();\n+\t}\n+}\n+\n+void PipelineHandler::dumpRequest(Request *request, DumpMode mode)\n+{\n+\tif (!controlsEmitter_.valid())\n+\t\treturn;\n+\n+\tControlList &controls = mode == DumpMode::Controls ? request->controls()\n+\t\t\t\t\t\t\t   : request->metadata();\n+\tif (controls.empty())\n+\t\treturn;\n+\n+\tYamlDict yamlFrame;\n+\tif (mode == DumpMode::Controls) {\n+\t\tif (!controlsList_.valid())\n+\t\t\tcontrolsList_ = controlsDict_.list(\"frames\");\n+\n+\t\tyamlFrame = controlsList_.dict();\n+\t} else {\n+\t\tyamlFrame = metadataList_.dict();\n+\t}\n+\n+\tYamlDict yamlCtrls = yamlFrame.dict(std::to_string(request->sequence()));\n+\n+\tconst ControlIdMap *idMap = controls.idMap();\n+\tfor (const auto &pair : controls) {\n+\t\tconst ControlId *ctrlId = idMap->at(pair.first);\n+\n+\t\tyamlCtrls[ctrlId->name()] = pair.second.toString();\n+\t}\n+}\n+\n /**\n  * \\class PipelineHandlerFactoryBase\n  * \\brief Base class for pipeline handler factories\n",
    "prefixes": [
        "2/3"
    ]
}