From patchwork Mon Sep 30 17:09:06 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 21437 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 DA01BC32D4 for ; Mon, 30 Sep 2024 17:09:25 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 725F263518; Mon, 30 Sep 2024 19:09:25 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="UljsO60+"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id ACF296350B for ; Mon, 30 Sep 2024 19:09:22 +0200 (CEST) Received: from neptunite.flets-east.jp (unknown [IPv6:2404:7a81:160:2100:4c37:91db:8b05:5548]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8C940FB8; Mon, 30 Sep 2024 19:07:50 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1727716071; bh=opyYlBTZ8EC4HwR8vsGMblmnizVr5iXK8pX6iy4C560=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UljsO60+47kNgynw6VZcpWM4MRno5iQjWiu3fvTB0NogJdJvk13g1+7gRbuJmz0qF po/yQcZ+AGCCnS7AMotKNTLpbEy8dJC59ANIzRp1V/s2ATk6I6/h8XS/KRcI27maq9 f2CNk0/mBlsyYiYQdPS1zhOw/3W41GBjNOCanhIU= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: laurent.pinchart@ideasonboard.com, Paul Elder Subject: [PATCH 1/2] pipeline: Add support for dumping capture script and metadata Date: Tue, 1 Oct 2024 02:09:06 +0900 Message-Id: <20240930170907.1404844-2-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240930170907.1404844-1-paul.elder@ideasonboard.com> References: <20240930170907.1404844-1-paul.elder@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" Add support for dumping capture scripts and metadata. 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 --- include/libcamera/internal/camera.h | 3 + include/libcamera/internal/pipeline_handler.h | 16 ++++ src/libcamera/camera.cpp | 13 +++ src/libcamera/pipeline_handler.cpp | 93 ++++++++++++++++++- 4 files changed, 124 insertions(+), 1 deletion(-) 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 0d38080369c5..fb3914185a01 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 @@ -20,6 +21,8 @@ namespace libcamera { +enum class Orientation; + class Camera; class CameraConfiguration; class CameraManager; @@ -68,6 +71,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); @@ -81,6 +87,11 @@ protected: CameraManager *manager_; private: + enum DumpMode { + Controls, + Metadata, + }; + void unlockMediaDevices(); void mediaDeviceDisconnected(MediaDevice *media); @@ -89,6 +100,8 @@ private: void doQueueRequest(Request *request); void doQueueRequests(); + void dumpRequest(Request *request, DumpMode mode); + std::vector> mediaDevices_; std::vector> cameras_; @@ -97,6 +110,9 @@ private: const char *name_; unsigned int useCount_; + std::ostream *dumpCaptureScript_; + std::ostream *dumpMetadata_; + friend class PipelineHandlerFactoryBase; }; diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp index a86f552a47bc..1282f99d839b 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 e5940469127e..7002b4323bdd 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 @@ -68,14 +69,36 @@ LOG_DEFINE_CATEGORY(Pipeline) * through the PipelineHandlerFactoryBase::create() function. */ PipelineHandler::PipelineHandler(CameraManager *manager) - : manager_(manager), useCount_(0) + : manager_(manager), useCount_(0), + dumpCaptureScript_(nullptr), dumpMetadata_(nullptr) { + /* TODO Print notification that we're dumping capture script */ + const char *file = utils::secure_getenv("LIBCAMERA_DUMP_CAPTURE_SCRIPT"); + if (!file) + return; + + dumpCaptureScript_ = new std::ofstream(file); + + /* + * Metadata needs to go into a separate file because otherwise it'll + * flood the capture script + */ + dumpMetadata_ = new std::ofstream(std::string(file) + ".metadata"); + std::string str = "frames:\n"; + dumpMetadata_->write(str.c_str(), str.size()); + dumpMetadata_->flush(); } PipelineHandler::~PipelineHandler() { for (std::shared_ptr media : mediaDevices_) media->release(); + + if (dumpCaptureScript_) + delete dumpCaptureScript_; + + if (dumpMetadata_) + delete dumpMetadata_; } /** @@ -464,6 +487,8 @@ void PipelineHandler::doQueueRequest(Request *request) request->_d()->sequence_ = data->requestSequence_++; + dumpRequest(request, DumpMode::Controls); + if (request->_d()->cancelled_) { completeRequest(request); return; @@ -555,6 +580,8 @@ void PipelineHandler::completeRequest(Request *request) request->_d()->complete(); + dumpRequest(request, DumpMode::Metadata); + Camera::Private *data = camera->_d(); while (!data->queuedRequests_.empty()) { @@ -758,6 +785,70 @@ void PipelineHandler::disconnect() * \return The CameraManager for this pipeline handler */ +void PipelineHandler::dumpConfiguration(const std::set &streams, + const Orientation &orientation) +{ + if (!dumpCaptureScript_) + return; + + std::stringstream ss; + ss << "configuration:" << std::endl; + ss << " orientation: " << orientation << std::endl; + + /* TODO Dump Sensor configuration */ + + ss << " streams:" << std::endl; + for (const auto &stream : streams) { + const StreamConfiguration &streamConfig = stream->configuration(); + ss << " - pixelFormat: " << streamConfig.pixelFormat << std::endl; + ss << " size: " << streamConfig.size << std::endl; + ss << " stride: " << streamConfig.stride << std::endl; + ss << " frameSize: " << streamConfig.frameSize << std::endl; + ss << " bufferCount: " << streamConfig.bufferCount << std::endl; + if (streamConfig.colorSpace) + ss << " colorSpace: " << streamConfig.colorSpace->toString() << std::endl; + } + + dumpCaptureScript_->write(ss.str().c_str(), ss.str().size()); + + std::string str = "frames:\n"; + dumpCaptureScript_->write(str.c_str(), str.size()); + dumpCaptureScript_->flush(); +} + +void PipelineHandler::dumpRequest(Request *request, DumpMode mode) +{ + ControlList &controls = + mode == DumpMode::Controls ? request->controls() + : request->metadata(); + std::ostream *output = + mode == DumpMode::Controls ? dumpCaptureScript_ + : dumpMetadata_; + + if (!output || controls.empty()) + return; + + std::stringstream ss; + /* TODO Figure out PFC */ + ss << " - " << request->sequence() << ":" << std::endl; + + const ControlIdMap *idMap = controls.idMap(); + for (const auto &pair : controls) { + const ControlId *ctrlId = idMap->at(pair.first); + /* TODO Prettify enums (probably by upgrading ControlValue::toString()) */ + ss << " " << ctrlId->name() << ": " << pair.second.toString() << std::endl; + } + + /* + * TODO Investigate the overhead of flushing this frequently + * Controls aren't going to be queued too frequently so it should be + * fine to dump controls every frame. Metadata on the other hand needs + * to be investigated. + */ + output->write(ss.str().c_str(), ss.str().size()); + output->flush(); +} + /** * \class PipelineHandlerFactoryBase * \brief Base class for pipeline handler factories