From patchwork Mon Dec 2 13:34:01 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 22148 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 93301BEFBE for ; Mon, 2 Dec 2024 13:34:18 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1072566067; Mon, 2 Dec 2024 14:34:15 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="dUJ3eRGx"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 353CA66040 for ; Mon, 2 Dec 2024 14:34:11 +0100 (CET) Received: from ideasonboard.com (mob-5-90-236-68.net.vodafone.it [5.90.236.68]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 54321514; Mon, 2 Dec 2024 14:33:44 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1733146424; bh=jo+JyqtQ+CQ9klqotyraZiGry867K5K1pXQicjk5rbQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=dUJ3eRGxucKjnuG9a/T52xSOulsOXdplYNUHBZb+oFw8xpYOTVS2Mr9PgwefQyMUZ t4kfEqHUV30WdCjHOPGBj8afeIHWcrOwhTWiOHxtzs8JptEz4JE9PIAaMlraV5Nm89 RIvozCBmNXKC9KLGiXnFJKj1ZGtpKEy6K3jPMuxs= From: Jacopo Mondi To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder Subject: [PATCH v2 2/4] pipeline: Add support for dumping capture script and metadata Date: Mon, 2 Dec 2024 14:34:01 +0100 Message-ID: <20241202133404.41431-3-jacopo.mondi@ideasonboard.com> X-Mailer: git-send-email 2.47.0 In-Reply-To: <20241202133404.41431-1-jacopo.mondi@ideasonboard.com> References: <20241202133404.41431-1-jacopo.mondi@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" From: Paul Elder Add support for dumping capture scripts and metadata using the Yaml Emitter helpers. The capture scripts can then be fed into the cam application and a capture can thus be "replayed". Metadata can also be dumped. Camera configuration is also dumped to the capture script. The cam application currently does not support loading configuration from the capture script, but support for that will be added in a subsequent patch. These can be enabled by a new environment variable. Signed-off-by: Paul Elder [rebased on the YamlEmitter classes] Signed-off-by: Jacopo Mondi --- include/libcamera/internal/camera.h | 3 + include/libcamera/internal/pipeline_handler.h | 23 +++++ src/libcamera/camera.cpp | 13 +++ src/libcamera/pipeline_handler.cpp | 93 +++++++++++++++++++ 4 files changed, 132 insertions(+) diff --git a/include/libcamera/internal/camera.h b/include/libcamera/internal/camera.h index 0add0428bb5d..a42f03d4c755 100644 --- a/include/libcamera/internal/camera.h +++ b/include/libcamera/internal/camera.h @@ -19,6 +19,8 @@ namespace libcamera { +enum class Orientation; + class CameraControlValidator; class PipelineHandler; class Stream; @@ -65,6 +67,7 @@ private: std::string id_; std::set streams_; std::set activeStreams_; + Orientation orientation_; bool disconnected_; std::atomic state_; diff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h index fb28a18d0f46..8e8450ae4e11 100644 --- a/include/libcamera/internal/pipeline_handler.h +++ b/include/libcamera/internal/pipeline_handler.h @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -18,8 +19,12 @@ #include #include +#include "libcamera/internal/yaml_emitter.h" + namespace libcamera { +enum class Orientation; + class Camera; class CameraConfiguration; class CameraManager; @@ -69,6 +74,9 @@ public: CameraManager *cameraManager() const { return manager_; } + void dumpConfiguration(const std::set &streams, + const Orientation &orientation); + protected: void registerCamera(std::shared_ptr camera); void hotplugMediaDevice(MediaDevice *media); @@ -82,6 +90,11 @@ protected: CameraManager *manager_; private: + enum DumpMode { + Controls, + Metadata, + }; + void unlockMediaDevices(); void mediaDeviceDisconnected(MediaDevice *media); @@ -90,6 +103,8 @@ private: void doQueueRequest(Request *request); void doQueueRequests(); + void dumpRequest(Request *request, DumpMode mode); + std::vector> mediaDevices_; std::vector> cameras_; @@ -98,6 +113,14 @@ private: const char *name_; unsigned int useCount_; + YamlRoot controlsEmitter_; + YamlDict controlsDict_; + YamlList controlsList_; + + YamlRoot metadataEmitter_; + YamlDict metadataDict_; + YamlList metadataList_; + friend class PipelineHandlerFactoryBase; }; diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp index 4c865a46af53..421dde0f67f0 100644 --- a/src/libcamera/camera.cpp +++ b/src/libcamera/camera.cpp @@ -1215,6 +1215,9 @@ int Camera::configure(CameraConfiguration *config) d->activeStreams_.insert(stream); } + /* TODO Save sensor configuration for dumping it to capture script */ + d->orientation_ = config->orientation; + d->setState(Private::CameraConfigured); return 0; @@ -1356,6 +1359,16 @@ int Camera::start(const ControlList *controls) ASSERT(d->requestSequence_ == 0); + /* + * Invoke method in blocking mode to avoid the risk of writing after + * streaming has started. + * This needs to be here as PipelineHandler::start is a virtual function + * so it is impractical to add the dumping there. + * TODO Pass the sensor configuration, once it is supported + */ + d->pipe_->invokeMethod(&PipelineHandler::dumpConfiguration, + ConnectionTypeBlocking, d->activeStreams_, d->orientation_); + ret = d->pipe_->invokeMethod(&PipelineHandler::start, ConnectionTypeBlocking, this, controls); if (ret) diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp index caa5c20e7483..e1ff6e6a5ce9 100644 --- a/src/libcamera/pipeline_handler.cpp +++ b/src/libcamera/pipeline_handler.cpp @@ -8,6 +8,7 @@ #include "libcamera/internal/pipeline_handler.h" #include +#include #include #include @@ -462,6 +463,8 @@ void PipelineHandler::doQueueRequest(Request *request) request->_d()->sequence_ = data->requestSequence_++; + dumpRequest(request, DumpMode::Controls); + if (request->_d()->cancelled_) { completeRequest(request); return; @@ -551,6 +554,8 @@ void PipelineHandler::completeRequest(Request *request) request->_d()->complete(); + dumpRequest(request, DumpMode::Metadata); + Camera::Private *data = camera->_d(); while (!data->queuedRequests_.empty()) { @@ -772,6 +777,94 @@ void PipelineHandler::disconnect() * \return The CameraManager for this pipeline handler */ +/** + * \brief Dump the camera configuration to YAML format + * + * Dump to the file path specified in the LIBCAMERA_DUMP_CAPTURE_SCRIPT + * environment variable, if any, the Camera configuration in YAML format. + */ +void PipelineHandler::dumpConfiguration(const std::set &streams, + const Orientation &orientation) +{ + const char *file = utils::secure_getenv("LIBCAMERA_DUMP_CAPTURE_SCRIPT"); + if (!file) + return; + + std::string filePath(file); + LOG(Pipeline, Debug) << "Dumping controls in YAML format to: " + << filePath; + + /* Create the YAML roots for controls and metadata output files. */ + + controlsEmitter_ = YamlEmitter::root(filePath); + controlsDict_ = controlsEmitter_.dict(); + + /* + * Metadata needs to go into a separate file because otherwise it'll + * flood the capture script + */ + filePath += ".metadata"; + LOG(Pipeline, Debug) << "Dumping metadata in YAML format to: " + << filePath; + metadataEmitter_ = YamlEmitter::root(filePath); + metadataDict_ = metadataEmitter_.dict(); + metadataList_ = metadataDict_.list("frames"); + + YamlDict configurationDict = controlsDict_.dict("configuration"); + std::stringstream o; + o << orientation; + configurationDict.scalar("orientation", o.str()); + + /* \todo Dump Sensor configuration */ + + YamlList streamsList = configurationDict.list("streams"); + + for (const auto &stream : streams) { + const StreamConfiguration &streamConfig = stream->configuration(); + YamlDict yamlStream = streamsList.dict(); + + yamlStream.scalar("pixelformat", streamConfig.pixelFormat.toString()); + yamlStream.scalar("size", streamConfig.size.toString()); + yamlStream.scalar("stride", std::to_string(streamConfig.stride)); + yamlStream.scalar("frameSize", std::to_string(streamConfig.frameSize)); + yamlStream.scalar("bufferCount", std::to_string(streamConfig.bufferCount)); + + if (streamConfig.colorSpace) + yamlStream.scalar("colorSpace", + streamConfig.colorSpace->toString()); + } +} + +void PipelineHandler::dumpRequest(Request *request, DumpMode mode) +{ + if (!controlsEmitter_.valid()) + return; + + ControlList &controls = mode == DumpMode::Controls ? request->controls() + : request->metadata(); + if (controls.empty()) + return; + + YamlDict yamlFrame; + if (mode == DumpMode::Controls) { + if (!controlsList_.valid()) + controlsList_ = controlsDict_.list("frames"); + + yamlFrame = controlsList_.dict(); + } else { + yamlFrame = metadataList_.dict(); + } + + YamlDict yamlCtrls = yamlFrame.dict(std::to_string(request->sequence())); + + const ControlIdMap *idMap = controls.idMap(); + for (const auto &pair : controls) { + const ControlId *ctrlId = idMap->at(pair.first); + + yamlCtrls.scalar(ctrlId->name(), pair.second.toString()); + } +} + /** * \class PipelineHandlerFactoryBase * \brief Base class for pipeline handler factories