From patchwork Mon Oct 21 09:49:49 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 21712 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 33D1FC3308 for ; Mon, 21 Oct 2024 09:50:20 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3471C65397; Mon, 21 Oct 2024 11:50:17 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="mfsZjz/I"; 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 588F46538B for ; Mon, 21 Oct 2024 11:50:13 +0200 (CEST) Received: from ideasonboard.com (mob-5-90-62-78.net.vodafone.it [5.90.62.78]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 59A7B18B9; Mon, 21 Oct 2024 11:48:27 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1729504107; bh=6RvijhuVcz/qVbhAnx944ewCoc9YkX8D0jaV43Ajw7U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=mfsZjz/I6qLRi28OBCJXjBbhmzyk8uv9jkJ2mN30iWlJIeMGezDXuqz76gl54M6e4 WTQwfIna1Cpm8xqovkEaMwq06WqIjCOYrxI5kkC0ZT1QAhd1gAQAI2SCOYRFs4fa4Z XW2nmMx8Q0dPp3yqa03zvQ8u/HVnNPemVywbiRaE= From: Jacopo Mondi To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder Subject: [RFC v3 1/4] pipeline: Add support for dumping capture script and metadata Date: Mon, 21 Oct 2024 11:49:49 +0200 Message-ID: <20241021094955.26991-2-jacopo.mondi@ideasonboard.com> X-Mailer: git-send-email 2.47.0 In-Reply-To: <20241021094955.26991-1-jacopo.mondi@ideasonboard.com> References: <20241021094955.26991-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. 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 7507e9ddae77..9d28b1202397 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 From patchwork Mon Oct 21 09:49:50 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 21713 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 05447C3309 for ; Mon, 21 Oct 2024 09:50:21 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5D9D865399; Mon, 21 Oct 2024 11:50:19 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="oYlqIozi"; 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 D97686538F for ; Mon, 21 Oct 2024 11:50:13 +0200 (CEST) Received: from ideasonboard.com (mob-5-90-62-78.net.vodafone.it [5.90.62.78]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id EC7031779; Mon, 21 Oct 2024 11:48:27 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1729504108; bh=HQdAG/Sb95bwqSseHASkwf0DjmEcIyxVKLKvE07909c=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=oYlqIozi75cEguFWPQzE4twEH8yi4YyefVG+eeOQGglHi1i3J9ebxlCO4NCBZ07yM vNIJk94HPmO2A7OnecMinsAbOGo3NHpO91ScneL7MX7FXMZD3naZzgEdxtfQzFAX2u I1ptA7vaFX34IUjMPO9FLhUmxWK9R2zU1qyE7zoU= From: Jacopo Mondi To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Paul Elder Subject: [RFC v3 2/4] apps: cam: Add support for loading configuration from capture script Date: Mon, 21 Oct 2024 11:49:50 +0200 Message-ID: <20241021094955.26991-3-jacopo.mondi@ideasonboard.com> X-Mailer: git-send-email 2.47.0 In-Reply-To: <20241021094955.26991-1-jacopo.mondi@ideasonboard.com> References: <20241021094955.26991-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 to the cam application for loading the camera configuration from a capture script. These are not expected to be written by hand, but rather dumped via the LIBCAMERA_DUMP_CAPTURE_SCRIPT environment variable. If any configuration options are specified by command line parameters, those will take precedence. Signed-off-by: Paul Elder --- src/apps/cam/camera_session.cpp | 22 +++-- src/apps/cam/capture_script.cpp | 164 ++++++++++++++++++++++++++++++++ src/apps/cam/capture_script.h | 8 ++ 3 files changed, 184 insertions(+), 10 deletions(-) diff --git a/src/apps/cam/camera_session.cpp b/src/apps/cam/camera_session.cpp index 6e9890ccfda1..f31b5282c706 100644 --- a/src/apps/cam/camera_session.cpp +++ b/src/apps/cam/camera_session.cpp @@ -70,6 +70,18 @@ CameraSession::CameraSession(CameraManager *cm, return; } + if (options_.isSet(OptCaptureScript)) { + std::string scriptName = options_[OptCaptureScript].toString(); + script_ = std::make_unique(camera_, scriptName); + if (!script_->valid()) { + std::cerr << "Invalid capture script '" << scriptName + << "'" << std::endl; + return; + } + + script_->populateConfiguration(config.get()); + } + if (options_.isSet(OptOrientation)) { std::string orientOpt = options_[OptOrientation].toString(); static const std::map orientations{ @@ -119,16 +131,6 @@ CameraSession::CameraSession(CameraManager *cm, } #endif - if (options_.isSet(OptCaptureScript)) { - std::string scriptName = options_[OptCaptureScript].toString(); - script_ = std::make_unique(camera_, scriptName); - if (!script_->valid()) { - std::cerr << "Invalid capture script '" << scriptName - << "'" << std::endl; - return; - } - } - switch (config->validate()) { case CameraConfiguration::Valid: break; diff --git a/src/apps/cam/capture_script.cpp b/src/apps/cam/capture_script.cpp index fc1dfa75f2d4..7f166f45657e 100644 --- a/src/apps/cam/capture_script.cpp +++ b/src/apps/cam/capture_script.cpp @@ -7,6 +7,7 @@ #include "capture_script.h" +#include #include #include #include @@ -162,6 +163,10 @@ int CaptureScript::parseScript(FILE *script) ret = parseFrames(); if (ret) return ret; + } else if (section == "configuration") { + ret = parseConfiguration(); + if (ret) + return ret; } else { std::cerr << "Unsupported section '" << section << "'" << std::endl; @@ -322,6 +327,165 @@ int CaptureScript::parseControl(EventPtr event, ControlList &controls) return 0; } +int CaptureScript::parseOrientation(EventPtr event) +{ + static const std::map orientations{ + { "Rotate0", libcamera::Orientation::Rotate0 }, + { "Rotate0Mirror", libcamera::Orientation::Rotate0Mirror }, + { "Rotate180", libcamera::Orientation::Rotate180 }, + { "Rotate180Mirror", libcamera::Orientation::Rotate180Mirror }, + { "Rotate90Mirror", libcamera::Orientation::Rotate90Mirror }, + { "Rotate270", libcamera::Orientation::Rotate270 }, + { "Rotate270Mirror", libcamera::Orientation::Rotate270Mirror }, + { "Rotate90", libcamera::Orientation::Rotate90 }, + }; + + std::string orientation = eventScalarValue(event); + + auto it = orientations.find(orientation); + if (it == orientations.end()) { + std::cerr << "Invalid orientation '" << orientation + << "' in capture script" << std::endl; + return -EINVAL; + } + + orientation_ = it->second; + + return 0; +} + +int CaptureScript::parseStream(EventPtr event, unsigned int index) +{ + if (!checkEvent(event, YAML_MAPPING_START_EVENT)) + return -EINVAL; + + StreamConfiguration config; + while (1) { + event = nextEvent(); + if (!event) + return -EINVAL; + if (event->type == YAML_MAPPING_END_EVENT) + break; + + std::string key = eventScalarValue(event); + + event = nextEvent(); + if (!event) + return -EINVAL; + if (event->type == YAML_MAPPING_END_EVENT) + break; + + std::string value = eventScalarValue(event); + + if (key == "pixelFormat") { + config.pixelFormat = libcamera::PixelFormat::fromString(value); + } else if (key == "size") { + unsigned int split = value.find("x"); + if (split == std::string::npos) { + std::cerr << "Invalid size '" << value + << "' in stream configuration " + << index << std::endl; + } + + std::string width = value.substr(0, split); + std::string height = value.substr(split + 1); + config.size = Size(std::stoi(width), std::stoi(height)); + } else if (key == "stride") { + config.stride = std::stoi(value); + } else if (key == "frameSize") { + config.frameSize = std::stoi(value); + } else if (key == "bufferCount") { + config.bufferCount = std::stoi(value); + } else if (key == "colorSpace") { + config.colorSpace = libcamera::ColorSpace::fromString(value); + } else { + std::cerr << "Unknown key-value pair '" + << key << "': '" << value + << "' in stream configuration " + << index << std::endl; + return -EINVAL; + } + } + + streamConfigs_.push_back(config); + + return 0; +} + +int CaptureScript::parseStreams(EventPtr event) +{ + if (!checkEvent(event, YAML_SEQUENCE_START_EVENT)) + return -EINVAL; + + unsigned int index = 0; + while (1) { + event = nextEvent(); + if (!event) + return -EINVAL; + if (event->type == YAML_SEQUENCE_END_EVENT) + return 0; + + if (event->type == YAML_MAPPING_START_EVENT) { + parseStream(std::move(event), index++); + continue; + } else { + std::cerr << "UNKNOWN TYPE" << std::endl; + return -EINVAL; + } + } + + return 0; +} + +int CaptureScript::parseConfiguration() +{ + int ret; + + EventPtr event = nextEvent(YAML_MAPPING_START_EVENT); + if (!event) + return -EINVAL; + + while (1) { + event = nextEvent(); + if (!event) + return -EINVAL; + if (event->type == YAML_MAPPING_END_EVENT) + break; + + std::string key = eventScalarValue(event); + + event = nextEvent(); + if (!event) + return -EINVAL; + if (event->type == YAML_MAPPING_END_EVENT) + break; + + /* TODO Load sensor configuration */ + if (key == "orientation") { + ret = parseOrientation(std::move(event)); + if (ret) + return ret; + } else if (key == "streams") { + ret = parseStreams(std::move(event)); + if (ret) + return ret; + } + } + + return 0; +} + +void CaptureScript::populateConfiguration(CameraConfiguration *configuration) const +{ + if (!configuration) + return; + + configuration->orientation = orientation_; + + for (unsigned int i = 0; i < streamConfigs_.size(); i++) + (*configuration)[i] = streamConfigs_[i]; +} + std::string CaptureScript::parseScalar() { EventPtr event = nextEvent(YAML_SCALAR_EVENT); diff --git a/src/apps/cam/capture_script.h b/src/apps/cam/capture_script.h index 294b920363ba..4ba862d742cf 100644 --- a/src/apps/cam/capture_script.h +++ b/src/apps/cam/capture_script.h @@ -26,6 +26,7 @@ public: const libcamera::ControlList &frameControls(unsigned int frame); + void populateConfiguration(libcamera::CameraConfiguration *configuration) const; private: struct EventDeleter { void operator()(yaml_event_t *event) const @@ -43,6 +44,9 @@ private: unsigned int loop_; bool valid_; + libcamera::Orientation orientation_; + std::vector streamConfigs_; + EventPtr nextEvent(yaml_event_type_t expectedType = YAML_NO_EVENT); bool checkEvent(const EventPtr &event, yaml_event_type_t expectedType) const; static std::string eventScalarValue(const EventPtr &event); @@ -55,6 +59,10 @@ private: int parseFrames(); int parseFrame(EventPtr event); int parseControl(EventPtr event, libcamera::ControlList &controls); + int parseConfiguration(); + int parseOrientation(EventPtr event); + int parseStreams(EventPtr event); + int parseStream(EventPtr event, unsigned int index); libcamera::ControlValue parseScalarControl(const libcamera::ControlId *id, const std::string repr); From patchwork Mon Oct 21 09:49:51 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 21714 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 A5C42C330A for ; Mon, 21 Oct 2024 09:50:23 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8C94965397; Mon, 21 Oct 2024 11:50:21 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="nrSV49VJ"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6492A6538B for ; Mon, 21 Oct 2024 11:50:14 +0200 (CEST) Received: from ideasonboard.com (mob-5-90-62-78.net.vodafone.it [5.90.62.78]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7E8CD18B9; Mon, 21 Oct 2024 11:48:28 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1729504108; bh=pTrILWZAB7I/LxeI6m+s+r3ui6Hzh4dg15Q4S9zZCIc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=nrSV49VJjsp0FnPSsiBUrX3EPcWSmb1x0ezNVTzOt9XVNgmMQVC4D680i4l5ngrn+ dQ4nHd+sdnghUwWLLbXe5SZgIwqsMOatjoHKbc75GR/zQRgUESkSZiLR6IyRsMXmFv GqWtoCwe6wWowalUzSaVxTU60bgjaeKlko3h/Gyg= From: Jacopo Mondi To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC v3 3/4] libcamera: Implement YamlEmitter Date: Mon, 21 Oct 2024 11:49:51 +0200 Message-ID: <20241021094955.26991-4-jacopo.mondi@ideasonboard.com> X-Mailer: git-send-email 2.47.0 In-Reply-To: <20241021094955.26991-1-jacopo.mondi@ideasonboard.com> References: <20241021094955.26991-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" Signed-off-by: Jacopo Mondi --- Documentation/Doxyfile-internal.in | 2 + include/libcamera/internal/meson.build | 1 + include/libcamera/internal/yaml_emitter.h | 183 +++++++++++ src/libcamera/meson.build | 1 + src/libcamera/yaml_emitter.cpp | 360 ++++++++++++++++++++++ 5 files changed, 547 insertions(+) create mode 100644 include/libcamera/internal/yaml_emitter.h create mode 100644 src/libcamera/yaml_emitter.cpp diff --git a/Documentation/Doxyfile-internal.in b/Documentation/Doxyfile-internal.in index cf9825537866..e0ba64da1bef 100644 --- a/Documentation/Doxyfile-internal.in +++ b/Documentation/Doxyfile-internal.in @@ -21,9 +21,11 @@ EXCLUDE = @TOP_SRCDIR@/include/libcamera/base/span.h \ @TOP_SRCDIR@/include/libcamera/internal/device_enumerator_sysfs.h \ @TOP_SRCDIR@/include/libcamera/internal/device_enumerator_udev.h \ @TOP_SRCDIR@/include/libcamera/internal/ipc_pipe_unixsocket.h \ + @TOP_SRCDIR@/include/libcamera/internal/yaml_emitter.h \ @TOP_SRCDIR@/src/libcamera/device_enumerator_sysfs.cpp \ @TOP_SRCDIR@/src/libcamera/device_enumerator_udev.cpp \ @TOP_SRCDIR@/src/libcamera/ipc_pipe_unixsocket.cpp \ + @TOP_SRCDIR@/src/libcamera/yaml_emitter.cpp \ @TOP_SRCDIR@/src/libcamera/pipeline/ \ @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \ @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \ diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build index 1c5eef9cab80..7533b075fde2 100644 --- a/include/libcamera/internal/meson.build +++ b/include/libcamera/internal/meson.build @@ -41,6 +41,7 @@ libcamera_internal_headers = files([ 'v4l2_pixelformat.h', 'v4l2_subdevice.h', 'v4l2_videodevice.h', + 'yaml_emitter.h', 'yaml_parser.h', ]) diff --git a/include/libcamera/internal/yaml_emitter.h b/include/libcamera/internal/yaml_emitter.h new file mode 100644 index 000000000000..bcbb574061cc --- /dev/null +++ b/include/libcamera/internal/yaml_emitter.h @@ -0,0 +1,183 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board Oy + * + * libcamera YAML emitter helper + */ + +#pragma once + +#include +#include + +#include +#include +#include + +#include + +namespace libcamera { + +class YamlDict; +class YamlEvent; +class YamlList; +class YamlRoot; +class YamlScalar; + +class YamlEmitter final +{ +public: + ~YamlEmitter(); + + static YamlRoot root(std::string_view path); + + int emit(); + yaml_event_t *event() { return &event_; } + +private: + LIBCAMERA_DISABLE_COPY(YamlEmitter) + + class Emitter + { + public: + ~Emitter(); + + void init(File *file); + + int emit(yaml_event_t *event); + + private: + void logError(); + + yaml_emitter_t emitter_; + }; + + YamlEmitter() = default; + + void init(); + + File file_; + yaml_event_t event_; + Emitter emitter_; +}; + +class YamlOutput +{ +public: + virtual ~YamlOutput() {}; + + YamlOutput() = default; + + YamlOutput(YamlOutput &&other) + { + emitter_ = other.emitter_; + other.emitter_ = nullptr; + } + + bool initialized() { return !!emitter_; } + + YamlScalar scalar(); + YamlDict dict(); + YamlList list(); + +protected: + YamlOutput(YamlEmitter *emitter) + : emitter_(emitter) + { + } + + YamlOutput &operator=(YamlOutput &&other) + { + emitter_ = other.emitter_; + other.emitter_ = nullptr; + + return *this; + } + + int emitScalar(std::string_view scalar); + int emitMappingStart(); + int emitMappingEnd(); + int emitSequenceStart(); + int emitSequenceEnd(); + + YamlEmitter *emitter_ = nullptr; + yaml_event_t event_; +}; + +class YamlRoot : public YamlOutput +{ +public: + YamlRoot() = default; + YamlRoot(YamlRoot &&other) = default; + ~YamlRoot(); + + YamlRoot &operator=(YamlRoot &&other) = default; + + YamlList list(); + YamlDict dict(); + void scalar(std::string_view scalar); + +private: + friend class YamlEmitter; + + YamlRoot(std::unique_ptr emitter) + : YamlOutput(emitter.get()), emitterRoot_(std::move(emitter)) + { + } + + std::unique_ptr emitterRoot_; +}; + +class YamlScalar : public YamlOutput +{ +public: + ~YamlScalar() = default; + + void operator=(std::string_view scalar); + +private: + friend class YamlOutput; + + YamlScalar(YamlEmitter *emitter); +}; + +class YamlList : public YamlOutput +{ +public: + YamlList() = default; + YamlList(YamlList &&other) = default; + ~YamlList(); + + YamlList &operator=(YamlList &&other) = default; + + YamlList list(); + YamlDict dict(); + void scalar(std::string_view scalar); + +private: + friend class YamlOutput; + + YamlList(YamlEmitter *emitter); +}; + +class YamlDict : public YamlOutput +{ +public: + YamlDict() = default; + YamlDict(YamlDict &&other) = default; + ~YamlDict(); + + YamlDict &operator=(YamlDict &&other) = default; + + YamlList list(std::string_view key); + YamlDict dict(std::string_view key); + + YamlScalar operator[](std::string_view key); + +private: + friend class YamlOutput; + + YamlDict(YamlEmitter *emitter); +}; + +} /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index aa9ab0291854..5b8af4103085 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -51,6 +51,7 @@ libcamera_internal_sources = files([ 'v4l2_pixelformat.cpp', 'v4l2_subdevice.cpp', 'v4l2_videodevice.cpp', + 'yaml_emitter.cpp', 'yaml_parser.cpp', ]) diff --git a/src/libcamera/yaml_emitter.cpp b/src/libcamera/yaml_emitter.cpp new file mode 100644 index 000000000000..9542ad2d9c18 --- /dev/null +++ b/src/libcamera/yaml_emitter.cpp @@ -0,0 +1,360 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board Oy + * + * libcamera YAML emitter helper + */ + +#include "libcamera/internal/yaml_emitter.h" + +#include + +/** + * \file yaml_emitter.h + * \brief A YAML emitter helper + * + * The YAML Emitter helpers allows users to emit output in YAML format. + * + * To emit YAML users of this classes should create a root node with + * + * \code + std::string filePath("..."); + auto root = YamlEmitter::root(filePath); + \endcode + * + * A YamlRoot implements RAII-style handling of YAML sequence and document + * events handling. Creating a YamlRoot emits the STREAM_START and DOC_START + * events. Once a YamlRoot gets deleted the DOC_SEND and STREAM_END events + * gets automatically deleted. + * + * Once a root node has been created it is possible to populate it with + * scalars, list or dictionaries. + * + * YamlList and YamlDict can only be created wrapped in unique_ptr<> instances, + * to implement a RAII-style handling of YAML of lists and dictionaries. + * Creating a YamlList and a YamlDict emits the YAML sequence and mapping start + * events. Once an instance gets deleted, the sequence and mapping gets + * automatically emitted. + * + * A YamlScalar is a simpler object that can be assigned to different types + * to emit them as strings. + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(YamlEmitter) + +namespace { + +int yamlWrite(void *data, unsigned char *buffer, size_t size) +{ + File *file = static_cast(data); + + Span buf{ buffer, size }; + ssize_t ret = file->write(buf); + if (ret < 0) { + ret = errno; + LOG(YamlEmitter, Error) << "Write error : " << strerror(ret); + return 0; + } + + return 1; +} + +} /* namespace */ + +/** + * \class YamlEmitter + * + * Yaml Emitter entry point. Allows to create a YamlRoot object that users + * can populate. + */ + +YamlEmitter::~YamlEmitter() +{ + yaml_event_delete(&event_); +} + +/** + * \brief Create a YamlRoot that applications can start populating with YamlOutput + * \param[in] path The YAML output file path + * \return A unique pointer to a YamlRoot + */ +YamlRoot YamlEmitter::root(std::string_view path) +{ + std::unique_ptr emitter{ new YamlEmitter() }; + + std::string filePath(path); + emitter->file_.setFileName(filePath); + emitter->file_.open(File::OpenModeFlag::WriteOnly); + + emitter->init(); + + return YamlRoot(std::move(emitter)); +} + +/** + * \brief Emit a yaml event + */ +int YamlEmitter::emit() +{ + return emitter_.emit(&event_); +} + +void YamlEmitter::init() +{ + emitter_.init(&file_); + + yaml_stream_start_event_initialize(&event_, YAML_UTF8_ENCODING); + emitter_.emit(&event_); + + yaml_document_start_event_initialize(&event_, NULL, NULL, NULL, 0); + emitter_.emit(&event_); +} + +/** + * \class YamlEmitter::Emitter + * + * yaml_emitter_t wrapper. Initialize the yaml_emitter_t on creation allows + * YamlOutput classes to emit events. + */ + +YamlEmitter::Emitter::~Emitter() +{ + yaml_emitter_delete(&emitter_); +} + +void YamlEmitter::Emitter::init(File *file) +{ + yaml_emitter_initialize(&emitter_); + yaml_emitter_set_output(&emitter_, yamlWrite, file); +} + +void YamlEmitter::Emitter::logError() +{ + switch (emitter_.error) { + case YAML_MEMORY_ERROR: + LOG(YamlEmitter, Error) + << "Memory error: Not enough memory for emitting"; + break; + + case YAML_WRITER_ERROR: + LOG(YamlEmitter, Error) + << "Writer error: " << emitter_.problem; + break; + + case YAML_EMITTER_ERROR: + LOG(YamlEmitter, Error) + << "Emitter error: " << emitter_.problem; + break; + + default: + LOG(YamlEmitter, Error) << "Internal problem"; + break; + } +} + +int YamlEmitter::Emitter::emit(yaml_event_t *event) +{ + int ret = yaml_emitter_emit(&emitter_, event); + if (!ret) { + logError(); + return -EINVAL; + } + + return 0; +} + +/** + * \class YamlOutput + * + * The YamlOutput base class. From this class YamlScalar, YamlList and YamlDict + * are derived. + * + * The YamlOutput class provides functions to create a scalar, a list or a + * dictionary. + * + * The class cannot be instantiated directly by applications. + */ + +YamlScalar YamlOutput::scalar() +{ + return YamlScalar(emitter_); +} + +YamlDict YamlOutput::dict() +{ + return YamlDict(emitter_); +} + +YamlList YamlOutput::list() +{ + return YamlList(emitter_); +} + +int YamlOutput::emitScalar(std::string_view scalar) +{ + const unsigned char *value = reinterpret_cast + (scalar.data()); + yaml_scalar_event_initialize(emitter_->event(), NULL, NULL, value, + scalar.length(), true, false, + YAML_PLAIN_SCALAR_STYLE); + return emitter_->emit(); +} + +int YamlOutput::emitMappingStart() +{ + yaml_mapping_start_event_initialize(emitter_->event(), NULL, NULL, + true, YAML_BLOCK_MAPPING_STYLE); + return emitter_->emit(); +} + +int YamlOutput::emitMappingEnd() +{ + yaml_mapping_end_event_initialize(emitter_->event()); + return emitter_->emit(); +} + +int YamlOutput::emitSequenceStart() +{ + yaml_sequence_start_event_initialize(emitter_->event(), NULL, NULL, true, + YAML_BLOCK_SEQUENCE_STYLE); + return emitter_->emit(); +} + +int YamlOutput::emitSequenceEnd() +{ + yaml_sequence_end_event_initialize(emitter_->event()); + return emitter_->emit(); +} + +/** + * \class YamlRoot + * + * Yaml root node. A root node can be populated with a scalar, a list or a dict. + */ + +YamlRoot::~YamlRoot() +{ + if (!initialized()) + return; + + yaml_document_end_event_initialize(emitter_->event(), 0); + emitterRoot_->emit(); + + yaml_stream_end_event_initialize(emitter_->event()); + emitterRoot_->emit(); +} + +YamlDict YamlRoot::dict() +{ + emitMappingStart(); + + return YamlOutput::dict(); +} + +YamlList YamlRoot::list() +{ + emitSequenceStart(); + + return YamlOutput::list(); +} + +/** + * \class YamlScalar + * + * A YamlScalar can be assigned to an std::string_view and other libcamera + * types to emit them as YAML scalars. + */ + +YamlScalar::YamlScalar(YamlEmitter *emitter) + : YamlOutput(emitter) +{ +} + +void YamlScalar::operator=(std::string_view scalar) +{ + emitScalar(scalar); +} + +/** + * \class YamlList + * + * A YamlList can be populated with scalar and allows to create other lists + * and dictionaries. + */ + +YamlList::YamlList(YamlEmitter *emitter) + : YamlOutput(emitter) +{ +} + +YamlList::~YamlList() +{ + if (initialized()) + emitSequenceEnd(); +} + +void YamlList::scalar(std::string_view scalar) +{ + emitScalar(scalar); +} + +YamlList YamlList::list() +{ + emitSequenceStart(); + + return YamlOutput::list(); +} + +YamlDict YamlList::dict() +{ + emitMappingStart(); + + return YamlOutput::dict(); +} + +/** + * \class YamlDict + * + * A YamlDict derives an unordered_map that maps strings to YAML scalar. + * + * A YamlDict can be populated with scalars using operator[] and allows to + * create other lists and dictionaries. + */ + +YamlDict::YamlDict(YamlEmitter *emitter) + : YamlOutput(emitter) +{ +} + +YamlDict::~YamlDict() +{ + if (initialized()) + emitMappingEnd(); +} + +YamlList YamlDict::list(std::string_view key) +{ + emitScalar(key); + emitSequenceStart(); + + return YamlOutput::list(); +} + +YamlDict YamlDict::dict(std::string_view key) +{ + emitScalar(key); + emitMappingStart(); + + return YamlOutput::dict(); +} + +YamlScalar YamlDict::operator[](std::string_view key) +{ + emitScalar(key); + + return YamlOutput::scalar(); +} + +} /* namespace libcamera */ From patchwork Mon Oct 21 09:49:52 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 21715 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 BA129C3304 for ; Mon, 21 Oct 2024 09:50:24 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id CAC7E65395; Mon, 21 Oct 2024 11:50:23 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Da0oR1Fc"; 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 0A48265395 for ; Mon, 21 Oct 2024 11:50:14 +0200 (CEST) Received: from ideasonboard.com (mob-5-90-62-78.net.vodafone.it [5.90.62.78]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 0611319F8; Mon, 21 Oct 2024 11:48:28 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1729504109; bh=YnaqdHf4COiwyOo9ZyS6UvZnQLokg1OBDK6AnEZiopo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Da0oR1FcRRFj4UhGgSl/bpTJCkiWZEF2GrNF0LI6PcnZ/Lbq6CH6oRcWBUUtzUE12 2dFaZfWqUV8szqDxaJyE4T+spl4JrxWAum7x3V36xi4kKC9x7cfcpNqNOm3dJoPAM7 jsrX02y/RjbraUMdWVQkqTHXkBdN75ZBFwpi6lDA= From: Jacopo Mondi To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC v3 4/4] libcamera: pipeline_handler: Use YamlEmitter Date: Mon, 21 Oct 2024 11:49:52 +0200 Message-ID: <20241021094955.26991-5-jacopo.mondi@ideasonboard.com> X-Mailer: git-send-email 2.47.0 In-Reply-To: <20241021094955.26991-1-jacopo.mondi@ideasonboard.com> References: <20241021094955.26991-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" Replace raw output usage with YamlEmitter. Signed-off-by: Jacopo Mondi --- include/libcamera/internal/pipeline_handler.h | 11 ++- src/libcamera/pipeline_handler.cpp | 99 +++++++++---------- 2 files changed, 58 insertions(+), 52 deletions(-) diff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h index fb3914185a01..89d10b373cfa 100644 --- a/include/libcamera/internal/pipeline_handler.h +++ b/include/libcamera/internal/pipeline_handler.h @@ -19,6 +19,8 @@ #include #include +#include "libcamera/internal/yaml_emitter.h" + namespace libcamera { enum class Orientation; @@ -110,8 +112,13 @@ private: const char *name_; unsigned int useCount_; - std::ostream *dumpCaptureScript_; - std::ostream *dumpMetadata_; + YamlRoot controlsEmitter_; + YamlDict controlsDict_; + YamlList controlsList_; + + YamlRoot metadataEmitter_; + YamlDict metadataDict_; + YamlList metadataList_; friend class PipelineHandlerFactoryBase; }; diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp index 7002b4323bdd..b5d613f630a2 100644 --- a/src/libcamera/pipeline_handler.cpp +++ b/src/libcamera/pipeline_handler.cpp @@ -69,36 +69,31 @@ LOG_DEFINE_CATEGORY(Pipeline) * through the PipelineHandlerFactoryBase::create() function. */ PipelineHandler::PipelineHandler(CameraManager *manager) - : manager_(manager), useCount_(0), - dumpCaptureScript_(nullptr), dumpMetadata_(nullptr) + : manager_(manager), useCount_(0) { /* 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); + std::string filePath(file); + controlsEmitter_ = YamlEmitter::root(filePath); + controlsDict_ = controlsEmitter_.dict(); /* * 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(); + std::string metadataFilePath = filePath + ".metadata"; + metadataEmitter_ = YamlEmitter::root(metadataFilePath); + metadataDict_ = metadataEmitter_.dict(); + metadataList_ = metadataDict_.list("frames"); } PipelineHandler::~PipelineHandler() { for (std::shared_ptr media : mediaDevices_) media->release(); - - if (dumpCaptureScript_) - delete dumpCaptureScript_; - - if (dumpMetadata_) - delete dumpMetadata_; } /** @@ -785,68 +780,72 @@ void PipelineHandler::disconnect() * \return The CameraManager for this pipeline handler */ +/** + * \brief todo + */ void PipelineHandler::dumpConfiguration(const std::set &streams, const Orientation &orientation) { - if (!dumpCaptureScript_) + if (!controlsEmitter_.initialized()) return; - std::stringstream ss; - ss << "configuration:" << std::endl; - ss << " orientation: " << orientation << std::endl; + YamlDict configurationDict = controlsDict_.dict("configuration"); + + std::stringstream o; + o << orientation; + configurationDict["orientation"] = o.str(); /* TODO Dump Sensor configuration */ - ss << " streams:" << std::endl; + YamlList streamsList = configurationDict.list("streams"); + 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; - } + YamlDict yamlStream = streamsList.dict(); - dumpCaptureScript_->write(ss.str().c_str(), ss.str().size()); + yamlStream["pixelformat"] = streamConfig.pixelFormat.toString(); + yamlStream["size"] = streamConfig.size.toString(); + yamlStream["stride"] = std::to_string(streamConfig.stride); + yamlStream["frameSize"] = std::to_string(streamConfig.frameSize); + yamlStream["bufferCount"] = std::to_string(streamConfig.bufferCount); - std::string str = "frames:\n"; - dumpCaptureScript_->write(str.c_str(), str.size()); - dumpCaptureScript_->flush(); + if (streamConfig.colorSpace) + yamlStream["colorSpace"] = + streamConfig.colorSpace->toString(); + } } 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()) + if (!controlsEmitter_.initialized()) return; - std::stringstream ss; + ControlList &controls = mode == DumpMode::Controls ? request->controls() + : request->metadata(); + if (controls.empty()) + return; + + YamlDict yamlFrame; + if (mode == DumpMode::Controls) { + if (!controlsList_.initialized()) + controlsList_ = controlsDict_.list("frames"); + + yamlFrame = controlsList_.dict(); + } else { + yamlFrame = metadataList_.dict(); + } + + YamlDict yamlCtrls = yamlFrame.dict(std::to_string(request->sequence())); + /* 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; + yamlCtrls[ctrlId->name()] = pair.second.toString(); } - - /* - * 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(); } /**