Show a patch.

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

{
    "id": 21655,
    "url": "https://patchwork.libcamera.org/api/1.1/patches/21655/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/21655/",
    "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": "<20241017125220.60567-2-jacopo.mondi@ideasonboard.com>",
    "date": "2024-10-17T12:52:16",
    "name": "[RFC,v2,1/4] pipeline: Add support for dumping capture script and metadata",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "05c2a7e27a0d6e1c5cc895ae14a0e5b8b11f49c0",
    "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/21655/mbox/",
    "series": [
        {
            "id": 4711,
            "url": "https://patchwork.libcamera.org/api/1.1/series/4711/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=4711",
            "date": "2024-10-17T12:52:15",
            "name": "libcamera: Implement YamlEmitter",
            "version": 2,
            "mbox": "https://patchwork.libcamera.org/series/4711/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/21655/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/21655/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 E8554C326C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 17 Oct 2024 12:52:36 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 3CD3C6538B;\n\tThu, 17 Oct 2024 14:52:34 +0200 (CEST)",
            "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 43E6B6537F\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 17 Oct 2024 14:52:31 +0200 (CEST)",
            "from ideasonboard.com (unknown [5.77.64.27])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 73F2F827;\n\tThu, 17 Oct 2024 14:50:47 +0200 (CEST)"
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"cmzNJars\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1729169448;\n\tbh=6RvijhuVcz/qVbhAnx944ewCoc9YkX8D0jaV43Ajw7U=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=cmzNJarsnzqume36FUwMhRdkWDei4rLBylbDgreIgoWL8JPPaGNLtKzN7c4UhqNKf\n\t89EEtOzCPJIeQMcQD9PdbhVaPZ3K6yzsdQ/MhzkUqnhFv9oYzaly5QlCy77Qceohgq\n\taXrFfWE4BbSQ3gaLPyFDoO//+lhzx8CpH0Big040=",
        "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": "[RFC v2 1/4] pipeline: Add support for dumping capture script and\n\tmetadata",
        "Date": "Thu, 17 Oct 2024 14:52:16 +0200",
        "Message-ID": "<20241017125220.60567-2-jacopo.mondi@ideasonboard.com>",
        "X-Mailer": "git-send-email 2.47.0",
        "In-Reply-To": "<20241017125220.60567-1-jacopo.mondi@ideasonboard.com>",
        "References": "<20241017125220.60567-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. The capture\nscripts can then be fed into the cam application and a capture can thus\nbe \"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---\n include/libcamera/internal/camera.h           |  3 +\n include/libcamera/internal/pipeline_handler.h | 16 ++++\n src/libcamera/camera.cpp                      | 13 +++\n src/libcamera/pipeline_handler.cpp            | 93 ++++++++++++++++++-\n 4 files changed, 124 insertions(+), 1 deletion(-)",
    "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..fb3914185a01 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@@ -20,6 +21,8 @@\n \n namespace libcamera {\n \n+enum class Orientation;\n+\n class Camera;\n class CameraConfiguration;\n class CameraManager;\n@@ -68,6 +71,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 +87,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 +100,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 +110,9 @@ private:\n \tconst char *name_;\n \tunsigned int useCount_;\n \n+\tstd::ostream *dumpCaptureScript_;\n+\tstd::ostream *dumpMetadata_;\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..7002b4323bdd 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@@ -68,14 +69,36 @@ LOG_DEFINE_CATEGORY(Pipeline)\n  * through the PipelineHandlerFactoryBase::create() function.\n  */\n PipelineHandler::PipelineHandler(CameraManager *manager)\n-\t: manager_(manager), useCount_(0)\n+\t: manager_(manager), useCount_(0),\n+\t  dumpCaptureScript_(nullptr), dumpMetadata_(nullptr)\n {\n+\t/* TODO Print notification that we're dumping capture script */\n+\tconst char *file = utils::secure_getenv(\"LIBCAMERA_DUMP_CAPTURE_SCRIPT\");\n+\tif (!file)\n+\t\treturn;\n+\n+\tdumpCaptureScript_ = new std::ofstream(file);\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+\tdumpMetadata_ = new std::ofstream(std::string(file) + \".metadata\");\n+\tstd::string str = \"frames:\\n\";\n+\tdumpMetadata_->write(str.c_str(), str.size());\n+\tdumpMetadata_->flush();\n }\n \n PipelineHandler::~PipelineHandler()\n {\n \tfor (std::shared_ptr<MediaDevice> media : mediaDevices_)\n \t\tmedia->release();\n+\n+\tif (dumpCaptureScript_)\n+\t\tdelete dumpCaptureScript_;\n+\n+\tif (dumpMetadata_)\n+\t\tdelete dumpMetadata_;\n }\n \n /**\n@@ -464,6 +487,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 +580,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 +785,70 @@ void PipelineHandler::disconnect()\n  * \\return The CameraManager for this pipeline handler\n  */\n \n+void PipelineHandler::dumpConfiguration(const std::set<const Stream *> &streams,\n+\t\t\t\t\tconst Orientation &orientation)\n+{\n+\tif (!dumpCaptureScript_)\n+\t\treturn;\n+\n+\tstd::stringstream ss;\n+\tss << \"configuration:\" << std::endl;\n+\tss << \"  orientation: \" << orientation << std::endl;\n+\n+\t/* TODO Dump Sensor configuration */\n+\n+\tss << \"  streams:\" << std::endl;\n+\tfor (const auto &stream : streams) {\n+\t\tconst StreamConfiguration &streamConfig = stream->configuration();\n+\t\tss << \"    - pixelFormat: \" << streamConfig.pixelFormat << std::endl;\n+\t\tss << \"      size: \" << streamConfig.size << std::endl;\n+\t\tss << \"      stride: \" << streamConfig.stride << std::endl;\n+\t\tss << \"      frameSize: \" << streamConfig.frameSize << std::endl;\n+\t\tss << \"      bufferCount: \" << streamConfig.bufferCount << std::endl;\n+\t\tif (streamConfig.colorSpace)\n+\t\t\tss << \"      colorSpace: \" << streamConfig.colorSpace->toString() << std::endl;\n+\t}\n+\n+\tdumpCaptureScript_->write(ss.str().c_str(), ss.str().size());\n+\n+\tstd::string str = \"frames:\\n\";\n+\tdumpCaptureScript_->write(str.c_str(), str.size());\n+\tdumpCaptureScript_->flush();\n+}\n+\n+void PipelineHandler::dumpRequest(Request *request, DumpMode mode)\n+{\n+\tControlList &controls =\n+\t\tmode == DumpMode::Controls ? request->controls()\n+\t\t\t\t\t   : request->metadata();\n+\tstd::ostream *output =\n+\t\tmode == DumpMode::Controls ? dumpCaptureScript_\n+\t\t\t\t\t   : dumpMetadata_;\n+\n+\tif (!output || controls.empty())\n+\t\treturn;\n+\n+\tstd::stringstream ss;\n+\t/* TODO Figure out PFC */\n+\tss << \"  - \" << request->sequence() << \":\" << std::endl;\n+\n+\tconst ControlIdMap *idMap = controls.idMap();\n+\tfor (const auto &pair : controls) {\n+\t\tconst ControlId *ctrlId = idMap->at(pair.first);\n+\t\t/* TODO Prettify enums (probably by upgrading ControlValue::toString()) */\n+\t\tss << \"      \" << ctrlId->name() << \": \" << pair.second.toString() << std::endl;\n+\t}\n+\n+\t/*\n+\t * TODO Investigate the overhead of flushing this frequently\n+\t * Controls aren't going to be queued too frequently so it should be\n+\t * fine to dump controls every frame. Metadata on the other hand needs\n+\t * to be investigated.\n+\t */\n+\toutput->write(ss.str().c_str(), ss.str().size());\n+\toutput->flush();\n+}\n+\n /**\n  * \\class PipelineHandlerFactoryBase\n  * \\brief Base class for pipeline handler factories\n",
    "prefixes": [
        "RFC",
        "v2",
        "1/4"
    ]
}