summaryrefslogtreecommitdiff
path: root/src/cam
diff options
context:
space:
mode:
authorLaurent Pinchart <laurent.pinchart@ideasonboard.com>2022-10-20 00:44:55 +0300
committerLaurent Pinchart <laurent.pinchart@ideasonboard.com>2022-10-20 13:36:25 +0300
commit84ad104499d9efc0253dae1a60ee070ed375ad95 (patch)
treed10fd53eb79cebb28fa3f72b18b46dddb6382b83 /src/cam
parentdaf3f4b59f4ea0ecb42c6a39fe909f071d3a2842 (diff)
Move test applications to src/apps/
The cam and qcam test application share code, currently through a crude hack that references the cam source files directly from the qcam meson.build file. To prepare for the introduction of hosting that code in a static library, move all applications to src/apps/. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Diffstat (limited to 'src/cam')
-rw-r--r--src/cam/camera_session.cpp450
-rw-r--r--src/cam/camera_session.h79
-rw-r--r--src/cam/capture-script.yaml71
-rw-r--r--src/cam/capture_script.cpp535
-rw-r--r--src/cam/capture_script.h68
-rw-r--r--src/cam/dng_writer.cpp653
-rw-r--r--src/cam/dng_writer.h27
-rw-r--r--src/cam/drm.cpp717
-rw-r--r--src/cam/drm.h334
-rw-r--r--src/cam/event_loop.cpp150
-rw-r--r--src/cam/event_loop.h68
-rw-r--r--src/cam/file_sink.cpp137
-rw-r--r--src/cam/file_sink.h43
-rw-r--r--src/cam/frame_sink.cpp67
-rw-r--r--src/cam/frame_sink.h32
-rw-r--r--src/cam/image.cpp109
-rw-r--r--src/cam/image.h50
-rw-r--r--src/cam/kms_sink.cpp538
-rw-r--r--src/cam/kms_sink.h83
-rw-r--r--src/cam/main.cpp362
-rw-r--r--src/cam/main.h26
-rw-r--r--src/cam/meson.build74
-rw-r--r--src/cam/options.cpp1141
-rw-r--r--src/cam/options.h157
-rw-r--r--src/cam/sdl_sink.cpp214
-rw-r--r--src/cam/sdl_sink.h48
-rw-r--r--src/cam/sdl_texture.cpp36
-rw-r--r--src/cam/sdl_texture.h30
-rw-r--r--src/cam/sdl_texture_mjpg.cpp83
-rw-r--r--src/cam/sdl_texture_mjpg.h23
-rw-r--r--src/cam/sdl_texture_yuv.cpp33
-rw-r--r--src/cam/sdl_texture_yuv.h26
-rw-r--r--src/cam/stream_options.cpp134
-rw-r--r--src/cam/stream_options.h28
34 files changed, 0 insertions, 6626 deletions
diff --git a/src/cam/camera_session.cpp b/src/cam/camera_session.cpp
deleted file mode 100644
index 6b409c98..00000000
--- a/src/cam/camera_session.cpp
+++ /dev/null
@@ -1,450 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2019, Google Inc.
- *
- * camera_session.cpp - Camera capture session
- */
-
-#include <iomanip>
-#include <iostream>
-#include <limits.h>
-#include <sstream>
-
-#include <libcamera/control_ids.h>
-#include <libcamera/property_ids.h>
-
-#include "camera_session.h"
-#include "capture_script.h"
-#include "event_loop.h"
-#include "file_sink.h"
-#ifdef HAVE_KMS
-#include "kms_sink.h"
-#endif
-#include "main.h"
-#ifdef HAVE_SDL
-#include "sdl_sink.h"
-#endif
-#include "stream_options.h"
-
-using namespace libcamera;
-
-CameraSession::CameraSession(CameraManager *cm,
- const std::string &cameraId,
- unsigned int cameraIndex,
- const OptionsParser::Options &options)
- : options_(options), cameraIndex_(cameraIndex), last_(0),
- queueCount_(0), captureCount_(0), captureLimit_(0),
- printMetadata_(false)
-{
- char *endptr;
- unsigned long index = strtoul(cameraId.c_str(), &endptr, 10);
- if (*endptr == '\0' && index > 0 && index <= cm->cameras().size())
- camera_ = cm->cameras()[index - 1];
- else
- camera_ = cm->get(cameraId);
-
- if (!camera_) {
- std::cerr << "Camera " << cameraId << " not found" << std::endl;
- return;
- }
-
- if (camera_->acquire()) {
- std::cerr << "Failed to acquire camera " << cameraId
- << std::endl;
- return;
- }
-
- StreamRoles roles = StreamKeyValueParser::roles(options_[OptStream]);
-
- std::unique_ptr<CameraConfiguration> config =
- camera_->generateConfiguration(roles);
- if (!config || config->size() != roles.size()) {
- std::cerr << "Failed to get default stream configuration"
- << std::endl;
- return;
- }
-
- /* Apply configuration if explicitly requested. */
- if (StreamKeyValueParser::updateConfiguration(config.get(),
- options_[OptStream])) {
- std::cerr << "Failed to update configuration" << std::endl;
- return;
- }
-
- bool strictFormats = options_.isSet(OptStrictFormats);
-
-#ifdef HAVE_KMS
- if (options_.isSet(OptDisplay)) {
- if (options_.isSet(OptFile)) {
- std::cerr << "--display and --file options are mutually exclusive"
- << std::endl;
- return;
- }
-
- if (roles.size() != 1) {
- std::cerr << "Display doesn't support multiple streams"
- << std::endl;
- return;
- }
-
- if (roles[0] != StreamRole::Viewfinder) {
- std::cerr << "Display requires a viewfinder stream"
- << std::endl;
- return;
- }
- }
-#endif
-
- if (options_.isSet(OptCaptureScript)) {
- std::string scriptName = options_[OptCaptureScript].toString();
- script_ = std::make_unique<CaptureScript>(camera_, scriptName);
- if (!script_->valid()) {
- std::cerr << "Invalid capture script '" << scriptName
- << "'" << std::endl;
- return;
- }
- }
-
- switch (config->validate()) {
- case CameraConfiguration::Valid:
- break;
-
- case CameraConfiguration::Adjusted:
- if (strictFormats) {
- std::cout << "Adjusting camera configuration disallowed by --strict-formats argument"
- << std::endl;
- return;
- }
- std::cout << "Camera configuration adjusted" << std::endl;
- break;
-
- case CameraConfiguration::Invalid:
- std::cout << "Camera configuration invalid" << std::endl;
- return;
- }
-
- config_ = std::move(config);
-}
-
-CameraSession::~CameraSession()
-{
- if (camera_)
- camera_->release();
-}
-
-void CameraSession::listControls() const
-{
- for (const auto &[id, info] : camera_->controls()) {
- std::cout << "Control: " << id->name() << ": "
- << info.toString() << std::endl;
- }
-}
-
-void CameraSession::listProperties() const
-{
- for (const auto &[key, value] : camera_->properties()) {
- const ControlId *id = properties::properties.at(key);
-
- std::cout << "Property: " << id->name() << " = "
- << value.toString() << std::endl;
- }
-}
-
-void CameraSession::infoConfiguration() const
-{
- unsigned int index = 0;
- for (const StreamConfiguration &cfg : *config_) {
- std::cout << index << ": " << cfg.toString() << std::endl;
-
- const StreamFormats &formats = cfg.formats();
- for (PixelFormat pixelformat : formats.pixelformats()) {
- std::cout << " * Pixelformat: "
- << pixelformat << " "
- << formats.range(pixelformat).toString()
- << std::endl;
-
- for (const Size &size : formats.sizes(pixelformat))
- std::cout << " - " << size << std::endl;
- }
-
- index++;
- }
-}
-
-int CameraSession::start()
-{
- int ret;
-
- queueCount_ = 0;
- captureCount_ = 0;
- captureLimit_ = options_[OptCapture].toInteger();
- printMetadata_ = options_.isSet(OptMetadata);
-
- ret = camera_->configure(config_.get());
- if (ret < 0) {
- std::cout << "Failed to configure camera" << std::endl;
- return ret;
- }
-
- streamNames_.clear();
- for (unsigned int index = 0; index < config_->size(); ++index) {
- StreamConfiguration &cfg = config_->at(index);
- streamNames_[cfg.stream()] = "cam" + std::to_string(cameraIndex_)
- + "-stream" + std::to_string(index);
- }
-
- camera_->requestCompleted.connect(this, &CameraSession::requestComplete);
-
-#ifdef HAVE_KMS
- if (options_.isSet(OptDisplay))
- sink_ = std::make_unique<KMSSink>(options_[OptDisplay].toString());
-#endif
-
-#ifdef HAVE_SDL
- if (options_.isSet(OptSDL))
- sink_ = std::make_unique<SDLSink>();
-#endif
-
- if (options_.isSet(OptFile)) {
- if (!options_[OptFile].toString().empty())
- sink_ = std::make_unique<FileSink>(camera_.get(), streamNames_,
- options_[OptFile]);
- else
- sink_ = std::make_unique<FileSink>(camera_.get(), streamNames_);
- }
-
- if (sink_) {
- ret = sink_->configure(*config_);
- if (ret < 0) {
- std::cout << "Failed to configure frame sink"
- << std::endl;
- return ret;
- }
-
- sink_->requestProcessed.connect(this, &CameraSession::sinkRelease);
- }
-
- allocator_ = std::make_unique<FrameBufferAllocator>(camera_);
-
- return startCapture();
-}
-
-void CameraSession::stop()
-{
- int ret = camera_->stop();
- if (ret)
- std::cout << "Failed to stop capture" << std::endl;
-
- if (sink_) {
- ret = sink_->stop();
- if (ret)
- std::cout << "Failed to stop frame sink" << std::endl;
- }
-
- sink_.reset();
-
- requests_.clear();
-
- allocator_.reset();
-}
-
-int CameraSession::startCapture()
-{
- int ret;
-
- /* Identify the stream with the least number of buffers. */
- unsigned int nbuffers = UINT_MAX;
- for (StreamConfiguration &cfg : *config_) {
- ret = allocator_->allocate(cfg.stream());
- if (ret < 0) {
- std::cerr << "Can't allocate buffers" << std::endl;
- return -ENOMEM;
- }
-
- unsigned int allocated = allocator_->buffers(cfg.stream()).size();
- nbuffers = std::min(nbuffers, allocated);
- }
-
- /*
- * TODO: make cam tool smarter to support still capture by for
- * example pushing a button. For now run all streams all the time.
- */
-
- for (unsigned int i = 0; i < nbuffers; i++) {
- std::unique_ptr<Request> request = camera_->createRequest();
- if (!request) {
- std::cerr << "Can't create request" << std::endl;
- return -ENOMEM;
- }
-
- for (StreamConfiguration &cfg : *config_) {
- Stream *stream = cfg.stream();
- const std::vector<std::unique_ptr<FrameBuffer>> &buffers =
- allocator_->buffers(stream);
- const std::unique_ptr<FrameBuffer> &buffer = buffers[i];
-
- ret = request->addBuffer(stream, buffer.get());
- if (ret < 0) {
- std::cerr << "Can't set buffer for request"
- << std::endl;
- return ret;
- }
-
- if (sink_)
- sink_->mapBuffer(buffer.get());
- }
-
- requests_.push_back(std::move(request));
- }
-
- if (sink_) {
- ret = sink_->start();
- if (ret) {
- std::cout << "Failed to start frame sink" << std::endl;
- return ret;
- }
- }
-
- ret = camera_->start();
- if (ret) {
- std::cout << "Failed to start capture" << std::endl;
- if (sink_)
- sink_->stop();
- return ret;
- }
-
- for (std::unique_ptr<Request> &request : requests_) {
- ret = queueRequest(request.get());
- if (ret < 0) {
- std::cerr << "Can't queue request" << std::endl;
- camera_->stop();
- if (sink_)
- sink_->stop();
- return ret;
- }
- }
-
- if (captureLimit_)
- std::cout << "cam" << cameraIndex_
- << ": Capture " << captureLimit_ << " frames"
- << std::endl;
- else
- std::cout << "cam" << cameraIndex_
- << ": Capture until user interrupts by SIGINT"
- << std::endl;
-
- return 0;
-}
-
-int CameraSession::queueRequest(Request *request)
-{
- if (captureLimit_ && queueCount_ >= captureLimit_)
- return 0;
-
- if (script_)
- request->controls() = script_->frameControls(queueCount_);
-
- queueCount_++;
-
- return camera_->queueRequest(request);
-}
-
-void CameraSession::requestComplete(Request *request)
-{
- if (request->status() == Request::RequestCancelled)
- return;
-
- /*
- * Defer processing of the completed request to the event loop, to avoid
- * blocking the camera manager thread.
- */
- EventLoop::instance()->callLater([=]() { processRequest(request); });
-}
-
-void CameraSession::processRequest(Request *request)
-{
- /*
- * If we've reached the capture limit, we're done. This doesn't
- * duplicate the check below that emits the captureDone signal, as this
- * function will be called for each request still in flight after the
- * capture limit is reached and we don't want to emit the signal every
- * single time.
- */
- if (captureLimit_ && captureCount_ >= captureLimit_)
- return;
-
- const Request::BufferMap &buffers = request->buffers();
-
- /*
- * Compute the frame rate. The timestamp is arbitrarily retrieved from
- * the first buffer, as all buffers should have matching timestamps.
- */
- uint64_t ts = buffers.begin()->second->metadata().timestamp;
- double fps = ts - last_;
- fps = last_ != 0 && fps ? 1000000000.0 / fps : 0.0;
- last_ = ts;
-
- bool requeue = true;
-
- std::stringstream info;
- info << ts / 1000000000 << "."
- << std::setw(6) << std::setfill('0') << ts / 1000 % 1000000
- << " (" << std::fixed << std::setprecision(2) << fps << " fps)";
-
- for (const auto &[stream, buffer] : buffers) {
- const FrameMetadata &metadata = buffer->metadata();
-
- info << " " << streamNames_[stream]
- << " seq: " << std::setw(6) << std::setfill('0') << metadata.sequence
- << " bytesused: ";
-
- unsigned int nplane = 0;
- for (const FrameMetadata::Plane &plane : metadata.planes()) {
- info << plane.bytesused;
- if (++nplane < metadata.planes().size())
- info << "/";
- }
- }
-
- if (sink_) {
- if (!sink_->processRequest(request))
- requeue = false;
- }
-
- std::cout << info.str() << std::endl;
-
- if (printMetadata_) {
- const ControlList &requestMetadata = request->metadata();
- for (const auto &[key, value] : requestMetadata) {
- const ControlId *id = controls::controls.at(key);
- std::cout << "\t" << id->name() << " = "
- << value.toString() << std::endl;
- }
- }
-
- /*
- * Notify the user that capture is complete if the limit has just been
- * reached.
- */
- captureCount_++;
- if (captureLimit_ && captureCount_ >= captureLimit_) {
- captureDone.emit();
- return;
- }
-
- /*
- * If the frame sink holds on the request, we'll requeue it later in the
- * complete handler.
- */
- if (!requeue)
- return;
-
- request->reuse(Request::ReuseBuffers);
- queueRequest(request);
-}
-
-void CameraSession::sinkRelease(Request *request)
-{
- request->reuse(Request::ReuseBuffers);
- queueRequest(request);
-}
diff --git a/src/cam/camera_session.h b/src/cam/camera_session.h
deleted file mode 100644
index d562caae..00000000
--- a/src/cam/camera_session.h
+++ /dev/null
@@ -1,79 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2019, Google Inc.
- *
- * camera_session.h - Camera capture session
- */
-
-#pragma once
-
-#include <memory>
-#include <stdint.h>
-#include <string>
-#include <vector>
-
-#include <libcamera/base/signal.h>
-
-#include <libcamera/camera.h>
-#include <libcamera/camera_manager.h>
-#include <libcamera/framebuffer.h>
-#include <libcamera/framebuffer_allocator.h>
-#include <libcamera/request.h>
-#include <libcamera/stream.h>
-
-#include "options.h"
-
-class CaptureScript;
-class FrameSink;
-
-class CameraSession
-{
-public:
- CameraSession(libcamera::CameraManager *cm,
- const std::string &cameraId, unsigned int cameraIndex,
- const OptionsParser::Options &options);
- ~CameraSession();
-
- bool isValid() const { return config_ != nullptr; }
- const OptionsParser::Options &options() { return options_; }
-
- libcamera::Camera *camera() { return camera_.get(); }
- libcamera::CameraConfiguration *config() { return config_.get(); }
-
- void listControls() const;
- void listProperties() const;
- void infoConfiguration() const;
-
- int start();
- void stop();
-
- libcamera::Signal<> captureDone;
-
-private:
- int startCapture();
-
- int queueRequest(libcamera::Request *request);
- void requestComplete(libcamera::Request *request);
- void processRequest(libcamera::Request *request);
- void sinkRelease(libcamera::Request *request);
-
- const OptionsParser::Options &options_;
- std::shared_ptr<libcamera::Camera> camera_;
- std::unique_ptr<libcamera::CameraConfiguration> config_;
-
- std::unique_ptr<CaptureScript> script_;
-
- std::map<const libcamera::Stream *, std::string> streamNames_;
- std::unique_ptr<FrameSink> sink_;
- unsigned int cameraIndex_;
-
- uint64_t last_;
-
- unsigned int queueCount_;
- unsigned int captureCount_;
- unsigned int captureLimit_;
- bool printMetadata_;
-
- std::unique_ptr<libcamera::FrameBufferAllocator> allocator_;
- std::vector<std::unique_ptr<libcamera::Request>> requests_;
-};
diff --git a/src/cam/capture-script.yaml b/src/cam/capture-script.yaml
deleted file mode 100644
index 7118865e..00000000
--- a/src/cam/capture-script.yaml
+++ /dev/null
@@ -1,71 +0,0 @@
-# SPDX-License-Identifier: CC0-1.0
-
-# Capture script example
-#
-# A capture script allows to associate a list of controls and their values
-# to frame numbers.
-#
-# The script allows defining a list of frames associated with controls
-# and an optional list of properties that can control the script behaviour.
-
-# properties:
-# # Repeat the controls every 'idx' frames.
-# - loop: idx
-#
-# # List of frame number with associated a list of controls to be applied
-# frames:
-# - frame-number:
-# Control1: value1
-# Control2: value2
-
-# \todo Formally define the capture script structure with a schema
-
-# Notes:
-# - Controls have to be specified by name, as defined in the
-# libcamera::controls:: enumeration
-# - Controls not supported by the camera currently operated are ignored
-# - Frame numbers shall be monotonically incrementing, gaps are allowed
-# - If a loop limit is specified, frame numbers in the 'frames' list shall be
-# less than the loop control
-
-# Example: Turn brightness up and down every 460 frames
-
-properties:
- - loop: 460
-
-frames:
- - 0:
- Brightness: 0.0
-
- - 40:
- Brightness: 0.2
-
- - 80:
- Brightness: 0.4
-
- - 120:
- Brightness: 0.8
-
- - 160:
- Brightness: 0.4
-
- - 200:
- Brightness: 0.2
-
- - 240:
- Brightness: 0.0
-
- - 280:
- Brightness: -0.2
-
- - 300:
- Brightness: -0.4
-
- - 340:
- Brightness: -0.8
-
- - 380:
- Brightness: -0.4
-
- - 420:
- Brightness: -0.2
diff --git a/src/cam/capture_script.cpp b/src/cam/capture_script.cpp
deleted file mode 100644
index 5a27361c..00000000
--- a/src/cam/capture_script.cpp
+++ /dev/null
@@ -1,535 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2022, Ideas on Board Oy
- *
- * capture_script.cpp - Capture session configuration script
- */
-
-#include "capture_script.h"
-
-#include <iostream>
-#include <stdio.h>
-#include <stdlib.h>
-
-using namespace libcamera;
-
-CaptureScript::CaptureScript(std::shared_ptr<Camera> camera,
- const std::string &fileName)
- : camera_(camera), loop_(0), valid_(false)
-{
- FILE *fh = fopen(fileName.c_str(), "r");
- if (!fh) {
- int ret = -errno;
- std::cerr << "Failed to open capture script " << fileName
- << ": " << strerror(-ret) << std::endl;
- return;
- }
-
- /*
- * Map the camera's controls to their name so that they can be
- * easily identified when parsing the script file.
- */
- for (const auto &[control, info] : camera_->controls())
- controls_[control->name()] = control;
-
- int ret = parseScript(fh);
- fclose(fh);
- if (ret)
- return;
-
- valid_ = true;
-}
-
-/* Retrieve the control list associated with a frame number. */
-const ControlList &CaptureScript::frameControls(unsigned int frame)
-{
- static ControlList controls{};
- unsigned int idx = frame;
-
- /* If we loop, repeat the controls every 'loop_' frames. */
- if (loop_)
- idx = frame % loop_;
-
- auto it = frameControls_.find(idx);
- if (it == frameControls_.end())
- return controls;
-
- return it->second;
-}
-
-CaptureScript::EventPtr CaptureScript::nextEvent(yaml_event_type_t expectedType)
-{
- EventPtr event(new yaml_event_t);
-
- if (!yaml_parser_parse(&parser_, event.get()))
- return nullptr;
-
- if (expectedType != YAML_NO_EVENT && !checkEvent(event, expectedType))
- return nullptr;
-
- return event;
-}
-
-bool CaptureScript::checkEvent(const EventPtr &event, yaml_event_type_t expectedType) const
-{
- if (event->type != expectedType) {
- std::cerr << "Capture script error on line " << event->start_mark.line
- << " column " << event->start_mark.column << ": "
- << "Expected " << eventTypeName(expectedType)
- << " event, got " << eventTypeName(event->type)
- << std::endl;
- return false;
- }
-
- return true;
-}
-
-std::string CaptureScript::eventScalarValue(const EventPtr &event)
-{
- return std::string(reinterpret_cast<char *>(event->data.scalar.value),
- event->data.scalar.length);
-}
-
-std::string CaptureScript::eventTypeName(yaml_event_type_t type)
-{
- static const std::map<yaml_event_type_t, std::string> typeNames = {
- { YAML_STREAM_START_EVENT, "stream-start" },
- { YAML_STREAM_END_EVENT, "stream-end" },
- { YAML_DOCUMENT_START_EVENT, "document-start" },
- { YAML_DOCUMENT_END_EVENT, "document-end" },
- { YAML_ALIAS_EVENT, "alias" },
- { YAML_SCALAR_EVENT, "scalar" },
- { YAML_SEQUENCE_START_EVENT, "sequence-start" },
- { YAML_SEQUENCE_END_EVENT, "sequence-end" },
- { YAML_MAPPING_START_EVENT, "mapping-start" },
- { YAML_MAPPING_END_EVENT, "mapping-end" },
- };
-
- auto it = typeNames.find(type);
- if (it == typeNames.end())
- return "[type " + std::to_string(type) + "]";
-
- return it->second;
-}
-
-int CaptureScript::parseScript(FILE *script)
-{
- int ret = yaml_parser_initialize(&parser_);
- if (!ret) {
- std::cerr << "Failed to initialize yaml parser" << std::endl;
- return ret;
- }
-
- /* Delete the parser upon function exit. */
- struct ParserDeleter {
- ParserDeleter(yaml_parser_t *parser) : parser_(parser) { }
- ~ParserDeleter() { yaml_parser_delete(parser_); }
- yaml_parser_t *parser_;
- } deleter(&parser_);
-
- yaml_parser_set_input_file(&parser_, script);
-
- EventPtr event = nextEvent(YAML_STREAM_START_EVENT);
- if (!event)
- return -EINVAL;
-
- event = nextEvent(YAML_DOCUMENT_START_EVENT);
- if (!event)
- return -EINVAL;
-
- 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)
- return 0;
-
- if (!checkEvent(event, YAML_SCALAR_EVENT))
- return -EINVAL;
-
- std::string section = eventScalarValue(event);
-
- if (section == "properties") {
- ret = parseProperties();
- if (ret)
- return ret;
- } else if (section == "frames") {
- ret = parseFrames();
- if (ret)
- return ret;
- } else {
- std::cerr << "Unsupported section '" << section << "'"
- << std::endl;
- return -EINVAL;
- }
- }
-}
-
-int CaptureScript::parseProperty()
-{
- EventPtr event = nextEvent(YAML_MAPPING_START_EVENT);
- if (!event)
- return -EINVAL;
-
- std::string prop = parseScalar();
- if (prop.empty())
- return -EINVAL;
-
- if (prop == "loop") {
- event = nextEvent();
- if (!event)
- return -EINVAL;
-
- std::string value = eventScalarValue(event);
- if (value.empty())
- return -EINVAL;
-
- loop_ = atoi(value.c_str());
- if (!loop_) {
- std::cerr << "Invalid loop limit '" << loop_ << "'"
- << std::endl;
- return -EINVAL;
- }
- } else {
- std::cerr << "Unsupported property '" << prop << "'" << std::endl;
- return -EINVAL;
- }
-
- event = nextEvent(YAML_MAPPING_END_EVENT);
- if (!event)
- return -EINVAL;
-
- return 0;
-}
-
-int CaptureScript::parseProperties()
-{
- EventPtr event = nextEvent(YAML_SEQUENCE_START_EVENT);
- if (!event)
- return -EINVAL;
-
- while (1) {
- if (event->type == YAML_SEQUENCE_END_EVENT)
- return 0;
-
- int ret = parseProperty();
- if (ret)
- return ret;
-
- event = nextEvent();
- if (!event)
- return -EINVAL;
- }
-
- return 0;
-}
-
-int CaptureScript::parseFrames()
-{
- EventPtr event = nextEvent(YAML_SEQUENCE_START_EVENT);
- if (!event)
- return -EINVAL;
-
- while (1) {
- event = nextEvent();
- if (!event)
- return -EINVAL;
-
- if (event->type == YAML_SEQUENCE_END_EVENT)
- return 0;
-
- int ret = parseFrame(std::move(event));
- if (ret)
- return ret;
- }
-}
-
-int CaptureScript::parseFrame(EventPtr event)
-{
- if (!checkEvent(event, YAML_MAPPING_START_EVENT))
- return -EINVAL;
-
- std::string key = parseScalar();
- if (key.empty())
- return -EINVAL;
-
- unsigned int frameId = atoi(key.c_str());
- if (loop_ && frameId >= loop_) {
- std::cerr
- << "Frame id (" << frameId << ") shall be smaller than"
- << "loop limit (" << loop_ << ")" << std::endl;
- return -EINVAL;
- }
-
- event = nextEvent(YAML_MAPPING_START_EVENT);
- if (!event)
- return -EINVAL;
-
- ControlList controls{};
-
- while (1) {
- event = nextEvent();
- if (!event)
- return -EINVAL;
-
- if (event->type == YAML_MAPPING_END_EVENT)
- break;
-
- int ret = parseControl(std::move(event), controls);
- if (ret)
- return ret;
- }
-
- frameControls_[frameId] = std::move(controls);
-
- event = nextEvent(YAML_MAPPING_END_EVENT);
- if (!event)
- return -EINVAL;
-
- return 0;
-}
-
-int CaptureScript::parseControl(EventPtr event, ControlList &controls)
-{
- /* We expect a value after a key. */
- std::string name = eventScalarValue(event);
- if (name.empty())
- return -EINVAL;
-
- /* If the camera does not support the control just ignore it. */
- auto it = controls_.find(name);
- if (it == controls_.end()) {
- std::cerr << "Unsupported control '" << name << "'" << std::endl;
- return -EINVAL;
- }
-
- const ControlId *controlId = it->second;
-
- ControlValue val = unpackControl(controlId);
- if (val.isNone()) {
- std::cerr << "Error unpacking control '" << name << "'"
- << std::endl;
- return -EINVAL;
- }
-
- controls.set(controlId->id(), val);
-
- return 0;
-}
-
-std::string CaptureScript::parseScalar()
-{
- EventPtr event = nextEvent(YAML_SCALAR_EVENT);
- if (!event)
- return "";
-
- return eventScalarValue(event);
-}
-
-ControlValue CaptureScript::parseRectangles()
-{
- std::vector<libcamera::Rectangle> rectangles;
-
- std::vector<std::vector<std::string>> arrays = parseArrays();
- if (arrays.empty())
- return {};
-
- for (const std::vector<std::string> &values : arrays) {
- if (values.size() != 4) {
- std::cerr << "Error parsing Rectangle: expected "
- << "array with 4 parameters" << std::endl;
- return {};
- }
-
- Rectangle rect = unpackRectangle(values);
- rectangles.push_back(rect);
- }
-
- ControlValue controlValue;
- controlValue.set(Span<const Rectangle>(rectangles));
-
- return controlValue;
-}
-
-std::vector<std::vector<std::string>> CaptureScript::parseArrays()
-{
- EventPtr event = nextEvent(YAML_SEQUENCE_START_EVENT);
- if (!event)
- return {};
-
- event = nextEvent();
- if (!event)
- return {};
-
- std::vector<std::vector<std::string>> valueArrays;
-
- /* Parse single array. */
- if (event->type == YAML_SCALAR_EVENT) {
- std::string firstValue = eventScalarValue(event);
- if (firstValue.empty())
- return {};
-
- std::vector<std::string> remaining = parseSingleArray();
-
- std::vector<std::string> values = { firstValue };
- values.insert(std::end(values),
- std::begin(remaining), std::end(remaining));
- valueArrays.push_back(values);
-
- return valueArrays;
- }
-
- /* Parse array of arrays. */
- while (1) {
- switch (event->type) {
- case YAML_SEQUENCE_START_EVENT: {
- std::vector<std::string> values = parseSingleArray();
- valueArrays.push_back(values);
- break;
- }
- case YAML_SEQUENCE_END_EVENT:
- return valueArrays;
- default:
- return {};
- }
-
- event = nextEvent();
- if (!event)
- return {};
- }
-}
-
-std::vector<std::string> CaptureScript::parseSingleArray()
-{
- std::vector<std::string> values;
-
- while (1) {
- EventPtr event = nextEvent();
- if (!event)
- return {};
-
- switch (event->type) {
- case YAML_SCALAR_EVENT: {
- std::string value = eventScalarValue(event);
- if (value.empty())
- return {};
- values.push_back(value);
- break;
- }
- case YAML_SEQUENCE_END_EVENT:
- return values;
- default:
- return {};
- }
- }
-}
-
-void CaptureScript::unpackFailure(const ControlId *id, const std::string &repr)
-{
- static const std::map<unsigned int, const char *> typeNames = {
- { ControlTypeNone, "none" },
- { ControlTypeBool, "bool" },
- { ControlTypeByte, "byte" },
- { ControlTypeInteger32, "int32" },
- { ControlTypeInteger64, "int64" },
- { ControlTypeFloat, "float" },
- { ControlTypeString, "string" },
- { ControlTypeRectangle, "Rectangle" },
- { ControlTypeSize, "Size" },
- };
-
- const char *typeName;
- auto it = typeNames.find(id->type());
- if (it != typeNames.end())
- typeName = it->second;
- else
- typeName = "unknown";
-
- std::cerr << "Unsupported control '" << repr << "' for "
- << typeName << " control " << id->name() << std::endl;
-}
-
-ControlValue CaptureScript::unpackControl(const ControlId *id)
-{
- /* Parse complex types. */
- switch (id->type()) {
- case ControlTypeRectangle:
- return parseRectangles();
- case ControlTypeSize:
- /* \todo Parse Sizes. */
- return {};
- default:
- break;
- }
-
- /* Parse basic types represented by a single scalar. */
- const std::string repr = parseScalar();
- if (repr.empty())
- return {};
-
- ControlValue value{};
-
- switch (id->type()) {
- case ControlTypeNone:
- break;
- case ControlTypeBool: {
- bool val;
-
- if (repr == "true") {
- val = true;
- } else if (repr == "false") {
- val = false;
- } else {
- unpackFailure(id, repr);
- return value;
- }
-
- value.set<bool>(val);
- break;
- }
- case ControlTypeByte: {
- uint8_t val = strtol(repr.c_str(), NULL, 10);
- value.set<uint8_t>(val);
- break;
- }
- case ControlTypeInteger32: {
- int32_t val = strtol(repr.c_str(), NULL, 10);
- value.set<int32_t>(val);
- break;
- }
- case ControlTypeInteger64: {
- int64_t val = strtoll(repr.c_str(), NULL, 10);
- value.set<int64_t>(val);
- break;
- }
- case ControlTypeFloat: {
- float val = strtof(repr.c_str(), NULL);
- value.set<float>(val);
- break;
- }
- case ControlTypeString: {
- value.set<std::string>(repr);
- break;
- }
- default:
- std::cerr << "Unsupported control type" << std::endl;
- break;
- }
-
- return value;
-}
-
-libcamera::Rectangle CaptureScript::unpackRectangle(const std::vector<std::string> &strVec)
-{
- int x = strtol(strVec[0].c_str(), NULL, 10);
- int y = strtol(strVec[1].c_str(), NULL, 10);
- unsigned int width = strtoul(strVec[2].c_str(), NULL, 10);
- unsigned int height = strtoul(strVec[3].c_str(), NULL, 10);
-
- return Rectangle(x, y, width, height);
-}
diff --git a/src/cam/capture_script.h b/src/cam/capture_script.h
deleted file mode 100644
index 7a0ddebb..00000000
--- a/src/cam/capture_script.h
+++ /dev/null
@@ -1,68 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2022, Ideas on Board Oy
- *
- * capture_script.h - Capture session configuration script
- */
-
-#pragma once
-
-#include <map>
-#include <memory>
-#include <string>
-
-#include <libcamera/camera.h>
-#include <libcamera/controls.h>
-
-#include <yaml.h>
-
-class CaptureScript
-{
-public:
- CaptureScript(std::shared_ptr<libcamera::Camera> camera,
- const std::string &fileName);
-
- bool valid() const { return valid_; }
-
- const libcamera::ControlList &frameControls(unsigned int frame);
-
-private:
- struct EventDeleter {
- void operator()(yaml_event_t *event) const
- {
- yaml_event_delete(event);
- delete event;
- }
- };
- using EventPtr = std::unique_ptr<yaml_event_t, EventDeleter>;
-
- std::map<std::string, const libcamera::ControlId *> controls_;
- std::map<unsigned int, libcamera::ControlList> frameControls_;
- std::shared_ptr<libcamera::Camera> camera_;
- yaml_parser_t parser_;
- unsigned int loop_;
- bool valid_;
-
- 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);
- static std::string eventTypeName(yaml_event_type_t type);
-
- int parseScript(FILE *script);
-
- int parseProperties();
- int parseProperty();
- int parseFrames();
- int parseFrame(EventPtr event);
- int parseControl(EventPtr event, libcamera::ControlList &controls);
-
- std::string parseScalar();
- libcamera::ControlValue parseRectangles();
- std::vector<std::vector<std::string>> parseArrays();
- std::vector<std::string> parseSingleArray();
-
- void unpackFailure(const libcamera::ControlId *id,
- const std::string &repr);
- libcamera::ControlValue unpackControl(const libcamera::ControlId *id);
- libcamera::Rectangle unpackRectangle(const std::vector<std::string> &strVec);
-};
diff --git a/src/cam/dng_writer.cpp b/src/cam/dng_writer.cpp
deleted file mode 100644
index c945edce..00000000
--- a/src/cam/dng_writer.cpp
+++ /dev/null
@@ -1,653 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2020, Raspberry Pi Ltd
- *
- * dng_writer.cpp - DNG writer
- */
-
-#include "dng_writer.h"
-
-#include <algorithm>
-#include <iostream>
-#include <map>
-
-#include <tiffio.h>
-
-#include <libcamera/control_ids.h>
-#include <libcamera/formats.h>
-#include <libcamera/property_ids.h>
-
-using namespace libcamera;
-
-enum CFAPatternColour : uint8_t {
- CFAPatternRed = 0,
- CFAPatternGreen = 1,
- CFAPatternBlue = 2,
-};
-
-struct FormatInfo {
- uint8_t bitsPerSample;
- CFAPatternColour pattern[4];
- void (*packScanline)(void *output, const void *input,
- unsigned int width);
- void (*thumbScanline)(const FormatInfo &info, void *output,
- const void *input, unsigned int width,
- unsigned int stride);
-};
-
-struct Matrix3d {
- Matrix3d()
- {
- }
-
- Matrix3d(float m0, float m1, float m2,
- float m3, float m4, float m5,
- float m6, float m7, float m8)
- {
- m[0] = m0, m[1] = m1, m[2] = m2;
- m[3] = m3, m[4] = m4, m[5] = m5;
- m[6] = m6, m[7] = m7, m[8] = m8;
- }
-
- Matrix3d(const Span<const float> &span)
- : Matrix3d(span[0], span[1], span[2],
- span[3], span[4], span[5],
- span[6], span[7], span[8])
- {
- }
-
- static Matrix3d diag(float diag0, float diag1, float diag2)
- {
- return Matrix3d(diag0, 0, 0, 0, diag1, 0, 0, 0, diag2);
- }
-
- static Matrix3d identity()
- {
- return Matrix3d(1, 0, 0, 0, 1, 0, 0, 0, 1);
- }
-
- Matrix3d transpose() const
- {
- return { m[0], m[3], m[6], m[1], m[4], m[7], m[2], m[5], m[8] };
- }
-
- Matrix3d cofactors() const
- {
- return { m[4] * m[8] - m[5] * m[7],
- -(m[3] * m[8] - m[5] * m[6]),
- m[3] * m[7] - m[4] * m[6],
- -(m[1] * m[8] - m[2] * m[7]),
- m[0] * m[8] - m[2] * m[6],
- -(m[0] * m[7] - m[1] * m[6]),
- m[1] * m[5] - m[2] * m[4],
- -(m[0] * m[5] - m[2] * m[3]),
- m[0] * m[4] - m[1] * m[3] };
- }
-
- Matrix3d adjugate() const
- {
- return cofactors().transpose();
- }
-
- float determinant() const
- {
- return m[0] * (m[4] * m[8] - m[5] * m[7]) -
- m[1] * (m[3] * m[8] - m[5] * m[6]) +
- m[2] * (m[3] * m[7] - m[4] * m[6]);
- }
-
- Matrix3d inverse() const
- {
- return adjugate() * (1.0 / determinant());
- }
-
- Matrix3d operator*(const Matrix3d &other) const
- {
- Matrix3d result;
- for (unsigned int i = 0; i < 3; i++) {
- for (unsigned int j = 0; j < 3; j++) {
- result.m[i * 3 + j] =
- m[i * 3 + 0] * other.m[0 + j] +
- m[i * 3 + 1] * other.m[3 + j] +
- m[i * 3 + 2] * other.m[6 + j];
- }
- }
- return result;
- }
-
- Matrix3d operator*(float f) const
- {
- Matrix3d result;
- for (unsigned int i = 0; i < 9; i++)
- result.m[i] = m[i] * f;
- return result;
- }
-
- float m[9];
-};
-
-void packScanlineSBGGR8(void *output, const void *input, unsigned int width)
-{
- const uint8_t *in = static_cast<const uint8_t *>(input);
- uint8_t *out = static_cast<uint8_t *>(output);
-
- std::copy(in, in + width, out);
-}
-
-void packScanlineSBGGR10P(void *output, const void *input, unsigned int width)
-{
- const uint8_t *in = static_cast<const uint8_t *>(input);
- uint8_t *out = static_cast<uint8_t *>(output);
-
- /* \todo Can this be made more efficient? */
- for (unsigned int x = 0; x < width; x += 4) {
- *out++ = in[0];
- *out++ = (in[4] & 0x03) << 6 | in[1] >> 2;
- *out++ = (in[1] & 0x03) << 6 | (in[4] & 0x0c) << 2 | in[2] >> 4;
- *out++ = (in[2] & 0x0f) << 4 | (in[4] & 0x30) >> 2 | in[3] >> 6;
- *out++ = (in[3] & 0x3f) << 2 | (in[4] & 0xc0) >> 6;
- in += 5;
- }
-}
-
-void packScanlineSBGGR12P(void *output, const void *input, unsigned int width)
-{
- const uint8_t *in = static_cast<const uint8_t *>(input);
- uint8_t *out = static_cast<uint8_t *>(output);
-
- /* \todo Can this be made more efficient? */
- for (unsigned int i = 0; i < width; i += 2) {
- *out++ = in[0];
- *out++ = (in[2] & 0x0f) << 4 | in[1] >> 4;
- *out++ = (in[1] & 0x0f) << 4 | in[2] >> 4;
- in += 3;
- }
-}
-
-void thumbScanlineSBGGRxxP(const FormatInfo &info, void *output,
- const void *input, unsigned int width,
- unsigned int stride)
-{
- const uint8_t *in = static_cast<const uint8_t *>(input);
- uint8_t *out = static_cast<uint8_t *>(output);
-
- /* Number of bytes corresponding to 16 pixels. */
- unsigned int skip = info.bitsPerSample * 16 / 8;
-
- for (unsigned int x = 0; x < width; x++) {
- uint8_t value = (in[0] + in[1] + in[stride] + in[stride + 1]) >> 2;
- *out++ = value;
- *out++ = value;
- *out++ = value;
- in += skip;
- }
-}
-
-void packScanlineIPU3(void *output, const void *input, unsigned int width)
-{
- const uint8_t *in = static_cast<const uint8_t *>(input);
- uint16_t *out = static_cast<uint16_t *>(output);
-
- /*
- * Upscale the 10-bit format to 16-bit as it's not trivial to pack it
- * as 10-bit without gaps.
- *
- * \todo Improve packing to keep the 10-bit sample size.
- */
- unsigned int x = 0;
- while (true) {
- for (unsigned int i = 0; i < 6; i++) {
- *out++ = (in[1] & 0x03) << 14 | (in[0] & 0xff) << 6;
- if (++x >= width)
- return;
-
- *out++ = (in[2] & 0x0f) << 12 | (in[1] & 0xfc) << 4;
- if (++x >= width)
- return;
-
- *out++ = (in[3] & 0x3f) << 10 | (in[2] & 0xf0) << 2;
- if (++x >= width)
- return;
-
- *out++ = (in[4] & 0xff) << 8 | (in[3] & 0xc0) << 0;
- if (++x >= width)
- return;
-
- in += 5;
- }
-
- *out++ = (in[1] & 0x03) << 14 | (in[0] & 0xff) << 6;
- if (++x >= width)
- return;
-
- in += 2;
- }
-}
-
-void thumbScanlineIPU3([[maybe_unused]] const FormatInfo &info, void *output,
- const void *input, unsigned int width,
- unsigned int stride)
-{
- uint8_t *out = static_cast<uint8_t *>(output);
-
- for (unsigned int x = 0; x < width; x++) {
- unsigned int pixel = x * 16;
- unsigned int block = pixel / 25;
- unsigned int pixelInBlock = pixel - block * 25;
-
- /*
- * If the pixel is the last in the block cheat a little and
- * move one pixel backward to avoid reading between two blocks
- * and having to deal with the padding bits.
- */
- if (pixelInBlock == 24)
- pixelInBlock--;
-
- const uint8_t *in = static_cast<const uint8_t *>(input)
- + block * 32 + (pixelInBlock / 4) * 5;
-
- uint16_t val1, val2, val3, val4;
- switch (pixelInBlock % 4) {
- case 0:
- val1 = (in[1] & 0x03) << 14 | (in[0] & 0xff) << 6;
- val2 = (in[2] & 0x0f) << 12 | (in[1] & 0xfc) << 4;
- val3 = (in[stride + 1] & 0x03) << 14 | (in[stride + 0] & 0xff) << 6;
- val4 = (in[stride + 2] & 0x0f) << 12 | (in[stride + 1] & 0xfc) << 4;
- break;
- case 1:
- val1 = (in[2] & 0x0f) << 12 | (in[1] & 0xfc) << 4;
- val2 = (in[3] & 0x3f) << 10 | (in[2] & 0xf0) << 2;
- val3 = (in[stride + 2] & 0x0f) << 12 | (in[stride + 1] & 0xfc) << 4;
- val4 = (in[stride + 3] & 0x3f) << 10 | (in[stride + 2] & 0xf0) << 2;
- break;
- case 2:
- val1 = (in[3] & 0x3f) << 10 | (in[2] & 0xf0) << 2;
- val2 = (in[4] & 0xff) << 8 | (in[3] & 0xc0) << 0;
- val3 = (in[stride + 3] & 0x3f) << 10 | (in[stride + 2] & 0xf0) << 2;
- val4 = (in[stride + 4] & 0xff) << 8 | (in[stride + 3] & 0xc0) << 0;
- break;
- case 3:
- val1 = (in[4] & 0xff) << 8 | (in[3] & 0xc0) << 0;
- val2 = (in[6] & 0x03) << 14 | (in[5] & 0xff) << 6;
- val3 = (in[stride + 4] & 0xff) << 8 | (in[stride + 3] & 0xc0) << 0;
- val4 = (in[stride + 6] & 0x03) << 14 | (in[stride + 5] & 0xff) << 6;
- break;
- }
-
- uint8_t value = (val1 + val2 + val3 + val4) >> 10;
- *out++ = value;
- *out++ = value;
- *out++ = value;
- }
-}
-
-static const std::map<PixelFormat, FormatInfo> formatInfo = {
- { formats::SBGGR8, {
- .bitsPerSample = 8,
- .pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed },
- .packScanline = packScanlineSBGGR8,
- .thumbScanline = thumbScanlineSBGGRxxP,
- } },
- { formats::SGBRG8, {
- .bitsPerSample = 8,
- .pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen },
- .packScanline = packScanlineSBGGR8,
- .thumbScanline = thumbScanlineSBGGRxxP,
- } },
- { formats::SGRBG8, {
- .bitsPerSample = 8,
- .pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen },
- .packScanline = packScanlineSBGGR8,
- .thumbScanline = thumbScanlineSBGGRxxP,
- } },
- { formats::SRGGB8, {
- .bitsPerSample = 8,
- .pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue },
- .packScanline = packScanlineSBGGR8,
- .thumbScanline = thumbScanlineSBGGRxxP,
- } },
- { formats::SBGGR10_CSI2P, {
- .bitsPerSample = 10,
- .pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed },
- .packScanline = packScanlineSBGGR10P,
- .thumbScanline = thumbScanlineSBGGRxxP,
- } },
- { formats::SGBRG10_CSI2P, {
- .bitsPerSample = 10,
- .pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen },
- .packScanline = packScanlineSBGGR10P,
- .thumbScanline = thumbScanlineSBGGRxxP,
- } },
- { formats::SGRBG10_CSI2P, {
- .bitsPerSample = 10,
- .pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen },
- .packScanline = packScanlineSBGGR10P,
- .thumbScanline = thumbScanlineSBGGRxxP,
- } },
- { formats::SRGGB10_CSI2P, {
- .bitsPerSample = 10,
- .pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue },
- .packScanline = packScanlineSBGGR10P,
- .thumbScanline = thumbScanlineSBGGRxxP,
- } },
- { formats::SBGGR12_CSI2P, {
- .bitsPerSample = 12,
- .pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed },
- .packScanline = packScanlineSBGGR12P,
- .thumbScanline = thumbScanlineSBGGRxxP,
- } },
- { formats::SGBRG12_CSI2P, {
- .bitsPerSample = 12,
- .pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen },
- .packScanline = packScanlineSBGGR12P,
- .thumbScanline = thumbScanlineSBGGRxxP,
- } },
- { formats::SGRBG12_CSI2P, {
- .bitsPerSample = 12,
- .pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen },
- .packScanline = packScanlineSBGGR12P,
- .thumbScanline = thumbScanlineSBGGRxxP,
- } },
- { formats::SRGGB12_CSI2P, {
- .bitsPerSample = 12,
- .pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue },
- .packScanline = packScanlineSBGGR12P,
- .thumbScanline = thumbScanlineSBGGRxxP,
- } },
- { formats::SBGGR10_IPU3, {
- .bitsPerSample = 16,
- .pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed },
- .packScanline = packScanlineIPU3,
- .thumbScanline = thumbScanlineIPU3,
- } },
- { formats::SGBRG10_IPU3, {
- .bitsPerSample = 16,
- .pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen },
- .packScanline = packScanlineIPU3,
- .thumbScanline = thumbScanlineIPU3,
- } },
- { formats::SGRBG10_IPU3, {
- .bitsPerSample = 16,
- .pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen },
- .packScanline = packScanlineIPU3,
- .thumbScanline = thumbScanlineIPU3,
- } },
- { formats::SRGGB10_IPU3, {
- .bitsPerSample = 16,
- .pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue },
- .packScanline = packScanlineIPU3,
- .thumbScanline = thumbScanlineIPU3,
- } },
-};
-
-int DNGWriter::write(const char *filename, const Camera *camera,
- const StreamConfiguration &config,
- const ControlList &metadata,
- [[maybe_unused]] const FrameBuffer *buffer,
- const void *data)
-{
- const ControlList &cameraProperties = camera->properties();
-
- const auto it = formatInfo.find(config.pixelFormat);
- if (it == formatInfo.cend()) {
- std::cerr << "Unsupported pixel format" << std::endl;
- return -EINVAL;
- }
- const FormatInfo *info = &it->second;
-
- TIFF *tif = TIFFOpen(filename, "w");
- if (!tif) {
- std::cerr << "Failed to open tiff file" << std::endl;
- return -EINVAL;
- }
-
- /*
- * Scanline buffer, has to be large enough to store both a RAW scanline
- * or a thumbnail scanline. The latter will always be much smaller than
- * the former as we downscale by 16 in both directions.
- */
- uint8_t scanline[(config.size.width * info->bitsPerSample + 7) / 8];
-
- toff_t rawIFDOffset = 0;
- toff_t exifIFDOffset = 0;
-
- /*
- * Start with a thumbnail in IFD 0 for compatibility with TIFF baseline
- * readers, as required by the TIFF/EP specification. Tags that apply to
- * the whole file are stored here.
- */
- const uint8_t version[] = { 1, 2, 0, 0 };
-
- TIFFSetField(tif, TIFFTAG_DNGVERSION, version);
- TIFFSetField(tif, TIFFTAG_DNGBACKWARDVERSION, version);
- TIFFSetField(tif, TIFFTAG_FILLORDER, FILLORDER_MSB2LSB);
- TIFFSetField(tif, TIFFTAG_MAKE, "libcamera");
-
- const auto &model = cameraProperties.get(properties::Model);
- if (model) {
- TIFFSetField(tif, TIFFTAG_MODEL, model->c_str());
- /* \todo set TIFFTAG_UNIQUECAMERAMODEL. */
- }
-
- TIFFSetField(tif, TIFFTAG_SOFTWARE, "qcam");
- TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
-
- /*
- * Thumbnail-specific tags. The thumbnail is stored as an RGB image
- * with 1/16 of the raw image resolution. Greyscale would save space,
- * but doesn't seem well supported by RawTherapee.
- */
- TIFFSetField(tif, TIFFTAG_SUBFILETYPE, FILETYPE_REDUCEDIMAGE);
- TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, config.size.width / 16);
- TIFFSetField(tif, TIFFTAG_IMAGELENGTH, config.size.height / 16);
- TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
- TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
- TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
- TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3);
- TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
- TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT);
-
- /*
- * Fill in some reasonable colour information in the DNG. We supply
- * the "neutral" colour values which determine the white balance, and the
- * "ColorMatrix1" which converts XYZ to (un-white-balanced) camera RGB.
- * Note that this is not a "proper" colour calibration for the DNG,
- * nonetheless, many tools should be able to render the colours better.
- */
- float neutral[3] = { 1, 1, 1 };
- Matrix3d wbGain = Matrix3d::identity();
- /* From http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html */
- const Matrix3d rgb2xyz(0.4124564, 0.3575761, 0.1804375,
- 0.2126729, 0.7151522, 0.0721750,
- 0.0193339, 0.1191920, 0.9503041);
- Matrix3d ccm = Matrix3d::identity();
- /*
- * Pick a reasonable number eps to protect against singularities. It
- * should be comfortably larger than the point at which we run into
- * numerical trouble, yet smaller than any plausible gain that we might
- * apply to a colour, either explicitly or as part of the colour matrix.
- */
- const double eps = 1e-2;
-
- const auto &colourGains = metadata.get(controls::ColourGains);
- if (colourGains) {
- if ((*colourGains)[0] > eps && (*colourGains)[1] > eps) {
- wbGain = Matrix3d::diag((*colourGains)[0], 1, (*colourGains)[1]);
- neutral[0] = 1.0 / (*colourGains)[0]; /* red */
- neutral[2] = 1.0 / (*colourGains)[1]; /* blue */
- }
- }
-
- const auto &ccmControl = metadata.get(controls::ColourCorrectionMatrix);
- if (ccmControl) {
- Matrix3d ccmSupplied(*ccmControl);
- if (ccmSupplied.determinant() > eps)
- ccm = ccmSupplied;
- }
-
- /*
- * rgb2xyz is known to be invertible, and we've ensured above that both
- * the ccm and wbGain matrices are non-singular, so the product of all
- * three is guaranteed to be invertible too.
- */
- Matrix3d colorMatrix1 = (rgb2xyz * ccm * wbGain).inverse();
-
- TIFFSetField(tif, TIFFTAG_COLORMATRIX1, 9, colorMatrix1.m);
- TIFFSetField(tif, TIFFTAG_ASSHOTNEUTRAL, 3, neutral);
-
- /*
- * Reserve space for the SubIFD and ExifIFD tags, pointing to the IFD
- * for the raw image and EXIF data respectively. The real offsets will
- * be set later.
- */
- TIFFSetField(tif, TIFFTAG_SUBIFD, 1, &rawIFDOffset);
- TIFFSetField(tif, TIFFTAG_EXIFIFD, exifIFDOffset);
-
- /* Write the thumbnail. */
- const uint8_t *row = static_cast<const uint8_t *>(data);
- for (unsigned int y = 0; y < config.size.height / 16; y++) {
- info->thumbScanline(*info, &scanline, row,
- config.size.width / 16, config.stride);
-
- if (TIFFWriteScanline(tif, &scanline, y, 0) != 1) {
- std::cerr << "Failed to write thumbnail scanline"
- << std::endl;
- TIFFClose(tif);
- return -EINVAL;
- }
-
- row += config.stride * 16;
- }
-
- TIFFWriteDirectory(tif);
-
- /* Create a new IFD for the RAW image. */
- const uint16_t cfaRepeatPatternDim[] = { 2, 2 };
- const uint8_t cfaPlaneColor[] = {
- CFAPatternRed,
- CFAPatternGreen,
- CFAPatternBlue
- };
-
- TIFFSetField(tif, TIFFTAG_SUBFILETYPE, 0);
- TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, config.size.width);
- TIFFSetField(tif, TIFFTAG_IMAGELENGTH, config.size.height);
- TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, info->bitsPerSample);
- TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
- TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CFA);
- TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1);
- TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
- TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT);
- TIFFSetField(tif, TIFFTAG_CFAREPEATPATTERNDIM, cfaRepeatPatternDim);
- if (TIFFLIB_VERSION < 20201219)
- TIFFSetField(tif, TIFFTAG_CFAPATTERN, info->pattern);
- else
- TIFFSetField(tif, TIFFTAG_CFAPATTERN, 4, info->pattern);
- TIFFSetField(tif, TIFFTAG_CFAPLANECOLOR, 3, cfaPlaneColor);
- TIFFSetField(tif, TIFFTAG_CFALAYOUT, 1);
-
- const uint16_t blackLevelRepeatDim[] = { 2, 2 };
- float blackLevel[] = { 0.0f, 0.0f, 0.0f, 0.0f };
- uint32_t whiteLevel = (1 << info->bitsPerSample) - 1;
-
- const auto &blackLevels = metadata.get(controls::SensorBlackLevels);
- if (blackLevels) {
- Span<const int32_t, 4> levels = *blackLevels;
-
- /*
- * The black levels control is specified in R, Gr, Gb, B order.
- * Map it to the TIFF tag that is specified in CFA pattern
- * order.
- */
- unsigned int green = (info->pattern[0] == CFAPatternRed ||
- info->pattern[1] == CFAPatternRed)
- ? 0 : 1;
-
- for (unsigned int i = 0; i < 4; ++i) {
- unsigned int level;
-
- switch (info->pattern[i]) {
- case CFAPatternRed:
- level = levels[0];
- break;
- case CFAPatternGreen:
- level = levels[green + 1];
- green = (green + 1) % 2;
- break;
- case CFAPatternBlue:
- default:
- level = levels[3];
- break;
- }
-
- /* Map the 16-bit value to the bits per sample range. */
- blackLevel[i] = level >> (16 - info->bitsPerSample);
- }
- }
-
- TIFFSetField(tif, TIFFTAG_BLACKLEVELREPEATDIM, &blackLevelRepeatDim);
- TIFFSetField(tif, TIFFTAG_BLACKLEVEL, 4, &blackLevel);
- TIFFSetField(tif, TIFFTAG_WHITELEVEL, 1, &whiteLevel);
-
- /* Write RAW content. */
- row = static_cast<const uint8_t *>(data);
- for (unsigned int y = 0; y < config.size.height; y++) {
- info->packScanline(&scanline, row, config.size.width);
-
- if (TIFFWriteScanline(tif, &scanline, y, 0) != 1) {
- std::cerr << "Failed to write RAW scanline"
- << std::endl;
- TIFFClose(tif);
- return -EINVAL;
- }
-
- row += config.stride;
- }
-
- /* Checkpoint the IFD to retrieve its offset, and write it out. */
- TIFFCheckpointDirectory(tif);
- rawIFDOffset = TIFFCurrentDirOffset(tif);
- TIFFWriteDirectory(tif);
-
- /* Create a new IFD for the EXIF data and fill it. */
- TIFFCreateEXIFDirectory(tif);
-
- /* Store creation time. */
- time_t rawtime;
- struct tm *timeinfo;
- char strTime[20];
-
- time(&rawtime);
- timeinfo = localtime(&rawtime);
- strftime(strTime, 20, "%Y:%m:%d %H:%M:%S", timeinfo);
-
- /*
- * \todo Handle timezone information by setting OffsetTimeOriginal and
- * OffsetTimeDigitized once libtiff catches up to the specification and
- * has EXIFTAG_ defines to handle them.
- */
- TIFFSetField(tif, EXIFTAG_DATETIMEORIGINAL, strTime);
- TIFFSetField(tif, EXIFTAG_DATETIMEDIGITIZED, strTime);
-
- const auto &analogGain = metadata.get(controls::AnalogueGain);
- if (analogGain) {
- uint16_t iso = std::min(std::max(*analogGain * 100, 0.0f), 65535.0f);
- TIFFSetField(tif, EXIFTAG_ISOSPEEDRATINGS, 1, &iso);
- }
-
- const auto &exposureTime = metadata.get(controls::ExposureTime);
- if (exposureTime)
- TIFFSetField(tif, EXIFTAG_EXPOSURETIME, *exposureTime / 1e6);
-
- TIFFWriteCustomDirectory(tif, &exifIFDOffset);
-
- /* Update the IFD offsets and close the file. */
- TIFFSetDirectory(tif, 0);
- TIFFSetField(tif, TIFFTAG_SUBIFD, 1, &rawIFDOffset);
- TIFFSetField(tif, TIFFTAG_EXIFIFD, exifIFDOffset);
- TIFFWriteDirectory(tif);
-
- TIFFClose(tif);
-
- return 0;
-}
diff --git a/src/cam/dng_writer.h b/src/cam/dng_writer.h
deleted file mode 100644
index 38f38f62..00000000
--- a/src/cam/dng_writer.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2020, Raspberry Pi Ltd
- *
- * dng_writer.h - DNG writer
- */
-
-#pragma once
-
-#ifdef HAVE_TIFF
-#define HAVE_DNG
-
-#include <libcamera/camera.h>
-#include <libcamera/controls.h>
-#include <libcamera/framebuffer.h>
-#include <libcamera/stream.h>
-
-class DNGWriter
-{
-public:
- static int write(const char *filename, const libcamera::Camera *camera,
- const libcamera::StreamConfiguration &config,
- const libcamera::ControlList &metadata,
- const libcamera::FrameBuffer *buffer, const void *data);
-};
-
-#endif /* HAVE_TIFF */
diff --git a/src/cam/drm.cpp b/src/cam/drm.cpp
deleted file mode 100644
index 2e4d7985..00000000
--- a/src/cam/drm.cpp
+++ /dev/null
@@ -1,717 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2021, Ideas on Board Oy
- *
- * drm.cpp - DRM/KMS Helpers
- */
-
-#include "drm.h"
-
-#include <algorithm>
-#include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <iostream>
-#include <set>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include <libcamera/framebuffer.h>
-#include <libcamera/geometry.h>
-#include <libcamera/pixel_format.h>
-
-#include <libdrm/drm_mode.h>
-
-#include "event_loop.h"
-
-namespace DRM {
-
-Object::Object(Device *dev, uint32_t id, Type type)
- : id_(id), dev_(dev), type_(type)
-{
- /* Retrieve properties from the objects that support them. */
- if (type != TypeConnector && type != TypeCrtc &&
- type != TypeEncoder && type != TypePlane)
- return;
-
- /*
- * We can't distinguish between failures due to the object having no
- * property and failures due to other conditions. Assume we use the API
- * correctly and consider the object has no property.
- */
- drmModeObjectProperties *properties = drmModeObjectGetProperties(dev->fd(), id, type);
- if (!properties)
- return;
-
- properties_.reserve(properties->count_props);
- for (uint32_t i = 0; i < properties->count_props; ++i)
- properties_.emplace_back(properties->props[i],
- properties->prop_values[i]);
-
- drmModeFreeObjectProperties(properties);
-}
-
-Object::~Object()
-{
-}
-
-const Property *Object::property(const std::string &name) const
-{
- for (const PropertyValue &pv : properties_) {
- const Property *property = static_cast<const Property *>(dev_->object(pv.id()));
- if (property && property->name() == name)
- return property;
- }
-
- return nullptr;
-}
-
-const PropertyValue *Object::propertyValue(const std::string &name) const
-{
- for (const PropertyValue &pv : properties_) {
- const Property *property = static_cast<const Property *>(dev_->object(pv.id()));
- if (property && property->name() == name)
- return &pv;
- }
-
- return nullptr;
-}
-
-Property::Property(Device *dev, drmModePropertyRes *property)
- : Object(dev, property->prop_id, TypeProperty),
- name_(property->name), flags_(property->flags),
- values_(property->values, property->values + property->count_values),
- blobs_(property->blob_ids, property->blob_ids + property->count_blobs)
-{
- if (drm_property_type_is(property, DRM_MODE_PROP_RANGE))
- type_ = TypeRange;
- else if (drm_property_type_is(property, DRM_MODE_PROP_ENUM))
- type_ = TypeEnum;
- else if (drm_property_type_is(property, DRM_MODE_PROP_BLOB))
- type_ = TypeBlob;
- else if (drm_property_type_is(property, DRM_MODE_PROP_BITMASK))
- type_ = TypeBitmask;
- else if (drm_property_type_is(property, DRM_MODE_PROP_OBJECT))
- type_ = TypeObject;
- else if (drm_property_type_is(property, DRM_MODE_PROP_SIGNED_RANGE))
- type_ = TypeSignedRange;
- else
- type_ = TypeUnknown;
-
- for (int i = 0; i < property->count_enums; ++i)
- enums_[property->enums[i].value] = property->enums[i].name;
-}
-
-Blob::Blob(Device *dev, const libcamera::Span<const uint8_t> &data)
- : Object(dev, 0, Object::TypeBlob)
-{
- drmModeCreatePropertyBlob(dev->fd(), data.data(), data.size(), &id_);
-}
-
-Blob::~Blob()
-{
- if (isValid())
- drmModeDestroyPropertyBlob(device()->fd(), id());
-}
-
-Mode::Mode(const drmModeModeInfo &mode)
- : drmModeModeInfo(mode)
-{
-}
-
-std::unique_ptr<Blob> Mode::toBlob(Device *dev) const
-{
- libcamera::Span<const uint8_t> data{ reinterpret_cast<const uint8_t *>(this),
- sizeof(*this) };
- return std::make_unique<Blob>(dev, data);
-}
-
-Crtc::Crtc(Device *dev, const drmModeCrtc *crtc, unsigned int index)
- : Object(dev, crtc->crtc_id, Object::TypeCrtc), index_(index)
-{
-}
-
-Encoder::Encoder(Device *dev, const drmModeEncoder *encoder)
- : Object(dev, encoder->encoder_id, Object::TypeEncoder),
- type_(encoder->encoder_type)
-{
- const std::list<Crtc> &crtcs = dev->crtcs();
- possibleCrtcs_.reserve(crtcs.size());
-
- for (const Crtc &crtc : crtcs) {
- if (encoder->possible_crtcs & (1 << crtc.index()))
- possibleCrtcs_.push_back(&crtc);
- }
-
- possibleCrtcs_.shrink_to_fit();
-}
-
-namespace {
-
-const std::map<uint32_t, const char *> connectorTypeNames{
- { DRM_MODE_CONNECTOR_Unknown, "Unknown" },
- { DRM_MODE_CONNECTOR_VGA, "VGA" },
- { DRM_MODE_CONNECTOR_DVII, "DVI-I" },
- { DRM_MODE_CONNECTOR_DVID, "DVI-D" },
- { DRM_MODE_CONNECTOR_DVIA, "DVI-A" },
- { DRM_MODE_CONNECTOR_Composite, "Composite" },
- { DRM_MODE_CONNECTOR_SVIDEO, "S-Video" },
- { DRM_MODE_CONNECTOR_LVDS, "LVDS" },
- { DRM_MODE_CONNECTOR_Component, "Component" },
- { DRM_MODE_CONNECTOR_9PinDIN, "9-Pin-DIN" },
- { DRM_MODE_CONNECTOR_DisplayPort, "DP" },
- { DRM_MODE_CONNECTOR_HDMIA, "HDMI-A" },
- { DRM_MODE_CONNECTOR_HDMIB, "HDMI-B" },
- { DRM_MODE_CONNECTOR_TV, "TV" },
- { DRM_MODE_CONNECTOR_eDP, "eDP" },
- { DRM_MODE_CONNECTOR_VIRTUAL, "Virtual" },
- { DRM_MODE_CONNECTOR_DSI, "DSI" },
- { DRM_MODE_CONNECTOR_DPI, "DPI" },
-};
-
-} /* namespace */
-
-Connector::Connector(Device *dev, const drmModeConnector *connector)
- : Object(dev, connector->connector_id, Object::TypeConnector),
- type_(connector->connector_type)
-{
- auto typeName = connectorTypeNames.find(connector->connector_type);
- if (typeName == connectorTypeNames.end()) {
- std::cerr
- << "Invalid connector type "
- << connector->connector_type << std::endl;
- typeName = connectorTypeNames.find(DRM_MODE_CONNECTOR_Unknown);
- }
-
- name_ = std::string(typeName->second) + "-"
- + std::to_string(connector->connector_type_id);
-
- switch (connector->connection) {
- case DRM_MODE_CONNECTED:
- status_ = Status::Connected;
- break;
-
- case DRM_MODE_DISCONNECTED:
- status_ = Status::Disconnected;
- break;
-
- case DRM_MODE_UNKNOWNCONNECTION:
- default:
- status_ = Status::Unknown;
- break;
- }
-
- const std::list<Encoder> &encoders = dev->encoders();
-
- encoders_.reserve(connector->count_encoders);
-
- for (int i = 0; i < connector->count_encoders; ++i) {
- uint32_t encoderId = connector->encoders[i];
- auto encoder = std::find_if(encoders.begin(), encoders.end(),
- [=](const Encoder &e) {
- return e.id() == encoderId;
- });
- if (encoder == encoders.end()) {
- std::cerr
- << "Encoder " << encoderId << " not found"
- << std::endl;
- continue;
- }
-
- encoders_.push_back(&*encoder);
- }
-
- encoders_.shrink_to_fit();
-
- modes_ = { connector->modes, connector->modes + connector->count_modes };
-}
-
-Plane::Plane(Device *dev, const drmModePlane *plane)
- : Object(dev, plane->plane_id, Object::TypePlane),
- possibleCrtcsMask_(plane->possible_crtcs)
-{
- formats_ = { plane->formats, plane->formats + plane->count_formats };
-
- const std::list<Crtc> &crtcs = dev->crtcs();
- possibleCrtcs_.reserve(crtcs.size());
-
- for (const Crtc &crtc : crtcs) {
- if (plane->possible_crtcs & (1 << crtc.index()))
- possibleCrtcs_.push_back(&crtc);
- }
-
- possibleCrtcs_.shrink_to_fit();
-}
-
-bool Plane::supportsFormat(const libcamera::PixelFormat &format) const
-{
- return std::find(formats_.begin(), formats_.end(), format.fourcc())
- != formats_.end();
-}
-
-int Plane::setup()
-{
- const PropertyValue *pv = propertyValue("type");
- if (!pv)
- return -EINVAL;
-
- switch (pv->value()) {
- case DRM_PLANE_TYPE_OVERLAY:
- type_ = TypeOverlay;
- break;
-
- case DRM_PLANE_TYPE_PRIMARY:
- type_ = TypePrimary;
- break;
-
- case DRM_PLANE_TYPE_CURSOR:
- type_ = TypeCursor;
- break;
-
- default:
- return -EINVAL;
- }
-
- return 0;
-}
-
-FrameBuffer::FrameBuffer(Device *dev)
- : Object(dev, 0, Object::TypeFb)
-{
-}
-
-FrameBuffer::~FrameBuffer()
-{
- for (const auto &plane : planes_) {
- struct drm_gem_close gem_close = {
- .handle = plane.second.handle,
- .pad = 0,
- };
- int ret;
-
- do {
- ret = ioctl(device()->fd(), DRM_IOCTL_GEM_CLOSE, &gem_close);
- } while (ret == -1 && (errno == EINTR || errno == EAGAIN));
-
- if (ret == -1) {
- ret = -errno;
- std::cerr
- << "Failed to close GEM object: "
- << strerror(-ret) << std::endl;
- }
- }
-
- drmModeRmFB(device()->fd(), id());
-}
-
-AtomicRequest::AtomicRequest(Device *dev)
- : dev_(dev), valid_(true)
-{
- request_ = drmModeAtomicAlloc();
- if (!request_)
- valid_ = false;
-}
-
-AtomicRequest::~AtomicRequest()
-{
- if (request_)
- drmModeAtomicFree(request_);
-}
-
-int AtomicRequest::addProperty(const Object *object, const std::string &property,
- uint64_t value)
-{
- if (!valid_)
- return -EINVAL;
-
- const Property *prop = object->property(property);
- if (!prop) {
- valid_ = false;
- return -EINVAL;
- }
-
- return addProperty(object->id(), prop->id(), value);
-}
-
-int AtomicRequest::addProperty(const Object *object, const std::string &property,
- std::unique_ptr<Blob> blob)
-{
- if (!valid_)
- return -EINVAL;
-
- const Property *prop = object->property(property);
- if (!prop) {
- valid_ = false;
- return -EINVAL;
- }
-
- int ret = addProperty(object->id(), prop->id(), blob->id());
- if (ret < 0)
- return ret;
-
- blobs_.emplace_back(std::move(blob));
-
- return 0;
-}
-
-int AtomicRequest::addProperty(uint32_t object, uint32_t property, uint64_t value)
-{
- int ret = drmModeAtomicAddProperty(request_, object, property, value);
- if (ret < 0) {
- valid_ = false;
- return ret;
- }
-
- return 0;
-}
-
-int AtomicRequest::commit(unsigned int flags)
-{
- if (!valid_)
- return -EINVAL;
-
- uint32_t drmFlags = 0;
- if (flags & FlagAllowModeset)
- drmFlags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
- if (flags & FlagAsync)
- drmFlags |= DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK;
- if (flags & FlagTestOnly)
- drmFlags |= DRM_MODE_ATOMIC_TEST_ONLY;
-
- return drmModeAtomicCommit(dev_->fd(), request_, drmFlags, this);
-}
-
-Device::Device()
- : fd_(-1)
-{
-}
-
-Device::~Device()
-{
- if (fd_ != -1)
- drmClose(fd_);
-}
-
-int Device::init()
-{
- int ret = openCard();
- if (ret < 0) {
- std::cerr << "Failed to open any DRM/KMS device: "
- << strerror(-ret) << std::endl;
- return ret;
- }
-
- /*
- * Enable the atomic APIs. This also automatically enables the
- * universal planes API.
- */
- ret = drmSetClientCap(fd_, DRM_CLIENT_CAP_ATOMIC, 1);
- if (ret < 0) {
- ret = -errno;
- std::cerr
- << "Failed to enable atomic capability: "
- << strerror(-ret) << std::endl;
- return ret;
- }
-
- /* List all the resources. */
- ret = getResources();
- if (ret < 0)
- return ret;
-
- EventLoop::instance()->addFdEvent(fd_, EventLoop::Read,
- std::bind(&Device::drmEvent, this));
-
- return 0;
-}
-
-int Device::openCard()
-{
- const std::string dirName = "/dev/dri/";
- bool found = false;
- int ret;
-
- /*
- * Open the first DRM/KMS device beginning with /dev/dri/card. The
- * libdrm drmOpen*() functions require either a module name or a bus ID,
- * which we don't have, so bypass them. The automatic module loading and
- * device node creation from drmOpen() is of no practical use as any
- * modern system will handle that through udev or an equivalent
- * component.
- */
- DIR *folder = opendir(dirName.c_str());
- if (!folder) {
- ret = -errno;
- std::cerr << "Failed to open " << dirName
- << " directory: " << strerror(-ret) << std::endl;
- return ret;
- }
-
- for (struct dirent *res; (res = readdir(folder));) {
- uint64_t cap;
-
- if (strncmp(res->d_name, "card", 4))
- continue;
-
- const std::string devName = dirName + res->d_name;
- fd_ = open(devName.c_str(), O_RDWR | O_CLOEXEC);
- if (fd_ < 0) {
- ret = -errno;
- std::cerr << "Failed to open DRM/KMS device " << devName << ": "
- << strerror(-ret) << std::endl;
- continue;
- }
-
- /*
- * Skip devices that don't support the modeset API, to avoid
- * selecting a DRM device corresponding to a GPU. There is no
- * modeset capability, but the kernel returns an error for most
- * caps if mode setting isn't support by the driver. The
- * DRM_CAP_DUMB_BUFFER capability is one of those, other would
- * do as well. The capability value itself isn't relevant.
- */
- ret = drmGetCap(fd_, DRM_CAP_DUMB_BUFFER, &cap);
- if (ret < 0) {
- drmClose(fd_);
- fd_ = -1;
- continue;
- }
-
- found = true;
- break;
- }
-
- closedir(folder);
-
- return found ? 0 : -ENOENT;
-}
-
-int Device::getResources()
-{
- int ret;
-
- std::unique_ptr<drmModeRes, decltype(&drmModeFreeResources)> resources{
- drmModeGetResources(fd_),
- &drmModeFreeResources
- };
- if (!resources) {
- ret = -errno;
- std::cerr
- << "Failed to get DRM/KMS resources: "
- << strerror(-ret) << std::endl;
- return ret;
- }
-
- for (int i = 0; i < resources->count_crtcs; ++i) {
- drmModeCrtc *crtc = drmModeGetCrtc(fd_, resources->crtcs[i]);
- if (!crtc) {
- ret = -errno;
- std::cerr
- << "Failed to get CRTC: " << strerror(-ret)
- << std::endl;
- return ret;
- }
-
- crtcs_.emplace_back(this, crtc, i);
- drmModeFreeCrtc(crtc);
-
- Crtc &obj = crtcs_.back();
- objects_[obj.id()] = &obj;
- }
-
- for (int i = 0; i < resources->count_encoders; ++i) {
- drmModeEncoder *encoder =
- drmModeGetEncoder(fd_, resources->encoders[i]);
- if (!encoder) {
- ret = -errno;
- std::cerr
- << "Failed to get encoder: " << strerror(-ret)
- << std::endl;
- return ret;
- }
-
- encoders_.emplace_back(this, encoder);
- drmModeFreeEncoder(encoder);
-
- Encoder &obj = encoders_.back();
- objects_[obj.id()] = &obj;
- }
-
- for (int i = 0; i < resources->count_connectors; ++i) {
- drmModeConnector *connector =
- drmModeGetConnector(fd_, resources->connectors[i]);
- if (!connector) {
- ret = -errno;
- std::cerr
- << "Failed to get connector: " << strerror(-ret)
- << std::endl;
- return ret;
- }
-
- connectors_.emplace_back(this, connector);
- drmModeFreeConnector(connector);
-
- Connector &obj = connectors_.back();
- objects_[obj.id()] = &obj;
- }
-
- std::unique_ptr<drmModePlaneRes, decltype(&drmModeFreePlaneResources)> planes{
- drmModeGetPlaneResources(fd_),
- &drmModeFreePlaneResources
- };
- if (!planes) {
- ret = -errno;
- std::cerr
- << "Failed to get DRM/KMS planes: "
- << strerror(-ret) << std::endl;
- return ret;
- }
-
- for (uint32_t i = 0; i < planes->count_planes; ++i) {
- drmModePlane *plane =
- drmModeGetPlane(fd_, planes->planes[i]);
- if (!plane) {
- ret = -errno;
- std::cerr
- << "Failed to get plane: " << strerror(-ret)
- << std::endl;
- return ret;
- }
-
- planes_.emplace_back(this, plane);
- drmModeFreePlane(plane);
-
- Plane &obj = planes_.back();
- objects_[obj.id()] = &obj;
- }
-
- /* Set the possible planes for each CRTC. */
- for (Crtc &crtc : crtcs_) {
- for (const Plane &plane : planes_) {
- if (plane.possibleCrtcsMask_ & (1 << crtc.index()))
- crtc.planes_.push_back(&plane);
- }
- }
-
- /* Collect all property IDs and create Property instances. */
- std::set<uint32_t> properties;
- for (const auto &object : objects_) {
- for (const PropertyValue &value : object.second->properties())
- properties.insert(value.id());
- }
-
- for (uint32_t id : properties) {
- drmModePropertyRes *property = drmModeGetProperty(fd_, id);
- if (!property) {
- ret = -errno;
- std::cerr
- << "Failed to get property: " << strerror(-ret)
- << std::endl;
- continue;
- }
-
- properties_.emplace_back(this, property);
- drmModeFreeProperty(property);
-
- Property &obj = properties_.back();
- objects_[obj.id()] = &obj;
- }
-
- /* Finally, perform all delayed setup of mode objects. */
- for (auto &object : objects_) {
- ret = object.second->setup();
- if (ret < 0) {
- std::cerr
- << "Failed to setup object " << object.second->id()
- << ": " << strerror(-ret) << std::endl;
- return ret;
- }
- }
-
- return 0;
-}
-
-const Object *Device::object(uint32_t id)
-{
- const auto iter = objects_.find(id);
- if (iter == objects_.end())
- return nullptr;
-
- return iter->second;
-}
-
-std::unique_ptr<FrameBuffer> Device::createFrameBuffer(
- const libcamera::FrameBuffer &buffer,
- const libcamera::PixelFormat &format,
- const libcamera::Size &size,
- const std::array<uint32_t, 4> &strides)
-{
- std::unique_ptr<FrameBuffer> fb{ new FrameBuffer(this) };
-
- uint32_t handles[4] = {};
- uint32_t offsets[4] = {};
- int ret;
-
- const std::vector<libcamera::FrameBuffer::Plane> &planes = buffer.planes();
-
- unsigned int i = 0;
- for (const libcamera::FrameBuffer::Plane &plane : planes) {
- int fd = plane.fd.get();
- uint32_t handle;
-
- auto iter = fb->planes_.find(fd);
- if (iter == fb->planes_.end()) {
- ret = drmPrimeFDToHandle(fd_, plane.fd.get(), &handle);
- if (ret < 0) {
- ret = -errno;
- std::cerr
- << "Unable to import framebuffer dmabuf: "
- << strerror(-ret) << std::endl;
- return nullptr;
- }
-
- fb->planes_[fd] = { handle };
- } else {
- handle = iter->second.handle;
- }
-
- handles[i] = handle;
- offsets[i] = plane.offset;
- ++i;
- }
-
- ret = drmModeAddFB2(fd_, size.width, size.height, format.fourcc(), handles,
- strides.data(), offsets, &fb->id_, 0);
- if (ret < 0) {
- ret = -errno;
- std::cerr
- << "Failed to add framebuffer: "
- << strerror(-ret) << std::endl;
- return nullptr;
- }
-
- return fb;
-}
-
-void Device::drmEvent()
-{
- drmEventContext ctx{};
- ctx.version = DRM_EVENT_CONTEXT_VERSION;
- ctx.page_flip_handler = &Device::pageFlipComplete;
-
- drmHandleEvent(fd_, &ctx);
-}
-
-void Device::pageFlipComplete([[maybe_unused]] int fd,
- [[maybe_unused]] unsigned int sequence,
- [[maybe_unused]] unsigned int tv_sec,
- [[maybe_unused]] unsigned int tv_usec,
- void *user_data)
-{
- AtomicRequest *request = static_cast<AtomicRequest *>(user_data);
- request->device()->requestComplete.emit(request);
-}
-
-} /* namespace DRM */
diff --git a/src/cam/drm.h b/src/cam/drm.h
deleted file mode 100644
index ebaea04d..00000000
--- a/src/cam/drm.h
+++ /dev/null
@@ -1,334 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2021, Ideas on Board Oy
- *
- * drm.h - DRM/KMS Helpers
- */
-
-#pragma once
-
-#include <array>
-#include <list>
-#include <map>
-#include <memory>
-#include <stdint.h>
-#include <string>
-#include <vector>
-
-#include <libcamera/base/signal.h>
-#include <libcamera/base/span.h>
-
-#include <libdrm/drm.h>
-#include <xf86drm.h>
-#include <xf86drmMode.h>
-
-namespace libcamera {
-class FrameBuffer;
-class PixelFormat;
-class Size;
-} /* namespace libcamera */
-
-namespace DRM {
-
-class Device;
-class Plane;
-class Property;
-class PropertyValue;
-
-class Object
-{
-public:
- enum Type {
- TypeCrtc = DRM_MODE_OBJECT_CRTC,
- TypeConnector = DRM_MODE_OBJECT_CONNECTOR,
- TypeEncoder = DRM_MODE_OBJECT_ENCODER,
- TypeMode = DRM_MODE_OBJECT_MODE,
- TypeProperty = DRM_MODE_OBJECT_PROPERTY,
- TypeFb = DRM_MODE_OBJECT_FB,
- TypeBlob = DRM_MODE_OBJECT_BLOB,
- TypePlane = DRM_MODE_OBJECT_PLANE,
- TypeAny = DRM_MODE_OBJECT_ANY,
- };
-
- Object(Device *dev, uint32_t id, Type type);
- virtual ~Object();
-
- Device *device() const { return dev_; }
- uint32_t id() const { return id_; }
- Type type() const { return type_; }
-
- const Property *property(const std::string &name) const;
- const PropertyValue *propertyValue(const std::string &name) const;
- const std::vector<PropertyValue> &properties() const { return properties_; }
-
-protected:
- virtual int setup()
- {
- return 0;
- }
-
- uint32_t id_;
-
-private:
- friend Device;
-
- Device *dev_;
- Type type_;
- std::vector<PropertyValue> properties_;
-};
-
-class Property : public Object
-{
-public:
- enum Type {
- TypeUnknown = 0,
- TypeRange,
- TypeEnum,
- TypeBlob,
- TypeBitmask,
- TypeObject,
- TypeSignedRange,
- };
-
- Property(Device *dev, drmModePropertyRes *property);
-
- Type type() const { return type_; }
- const std::string &name() const { return name_; }
-
- bool isImmutable() const { return flags_ & DRM_MODE_PROP_IMMUTABLE; }
-
- const std::vector<uint64_t> values() const { return values_; }
- const std::map<uint32_t, std::string> &enums() const { return enums_; }
- const std::vector<uint32_t> blobs() const { return blobs_; }
-
-private:
- Type type_;
- std::string name_;
- uint32_t flags_;
- std::vector<uint64_t> values_;
- std::map<uint32_t, std::string> enums_;
- std::vector<uint32_t> blobs_;
-};
-
-class PropertyValue
-{
-public:
- PropertyValue(uint32_t id, uint64_t value)
- : id_(id), value_(value)
- {
- }
-
- uint32_t id() const { return id_; }
- uint32_t value() const { return value_; }
-
-private:
- uint32_t id_;
- uint64_t value_;
-};
-
-class Blob : public Object
-{
-public:
- Blob(Device *dev, const libcamera::Span<const uint8_t> &data);
- ~Blob();
-
- bool isValid() const { return id() != 0; }
-};
-
-class Mode : public drmModeModeInfo
-{
-public:
- Mode(const drmModeModeInfo &mode);
-
- std::unique_ptr<Blob> toBlob(Device *dev) const;
-};
-
-class Crtc : public Object
-{
-public:
- Crtc(Device *dev, const drmModeCrtc *crtc, unsigned int index);
-
- unsigned int index() const { return index_; }
- const std::vector<const Plane *> &planes() const { return planes_; }
-
-private:
- friend Device;
-
- unsigned int index_;
- std::vector<const Plane *> planes_;
-};
-
-class Encoder : public Object
-{
-public:
- Encoder(Device *dev, const drmModeEncoder *encoder);
-
- uint32_t type() const { return type_; }
-
- const std::vector<const Crtc *> &possibleCrtcs() const { return possibleCrtcs_; }
-
-private:
- uint32_t type_;
- std::vector<const Crtc *> possibleCrtcs_;
-};
-
-class Connector : public Object
-{
-public:
- enum Status {
- Connected,
- Disconnected,
- Unknown,
- };
-
- Connector(Device *dev, const drmModeConnector *connector);
-
- uint32_t type() const { return type_; }
- const std::string &name() const { return name_; }
-
- Status status() const { return status_; }
-
- const std::vector<const Encoder *> &encoders() const { return encoders_; }
- const std::vector<Mode> &modes() const { return modes_; }
-
-private:
- uint32_t type_;
- std::string name_;
- Status status_;
- std::vector<const Encoder *> encoders_;
- std::vector<Mode> modes_;
-};
-
-class Plane : public Object
-{
-public:
- enum Type {
- TypeOverlay,
- TypePrimary,
- TypeCursor,
- };
-
- Plane(Device *dev, const drmModePlane *plane);
-
- Type type() const { return type_; }
- const std::vector<uint32_t> &formats() const { return formats_; }
- const std::vector<const Crtc *> &possibleCrtcs() const { return possibleCrtcs_; }
-
- bool supportsFormat(const libcamera::PixelFormat &format) const;
-
-protected:
- int setup() override;
-
-private:
- friend class Device;
-
- Type type_;
- std::vector<uint32_t> formats_;
- std::vector<const Crtc *> possibleCrtcs_;
- uint32_t possibleCrtcsMask_;
-};
-
-class FrameBuffer : public Object
-{
-public:
- struct Plane {
- uint32_t handle;
- };
-
- ~FrameBuffer();
-
-private:
- friend class Device;
-
- FrameBuffer(Device *dev);
-
- std::map<int, Plane> planes_;
-};
-
-class AtomicRequest
-{
-public:
- enum Flags {
- FlagAllowModeset = (1 << 0),
- FlagAsync = (1 << 1),
- FlagTestOnly = (1 << 2),
- };
-
- AtomicRequest(Device *dev);
- ~AtomicRequest();
-
- Device *device() const { return dev_; }
- bool isValid() const { return valid_; }
-
- int addProperty(const Object *object, const std::string &property,
- uint64_t value);
- int addProperty(const Object *object, const std::string &property,
- std::unique_ptr<Blob> blob);
- int commit(unsigned int flags = 0);
-
-private:
- AtomicRequest(const AtomicRequest &) = delete;
- AtomicRequest(const AtomicRequest &&) = delete;
- AtomicRequest &operator=(const AtomicRequest &) = delete;
- AtomicRequest &operator=(const AtomicRequest &&) = delete;
-
- int addProperty(uint32_t object, uint32_t property, uint64_t value);
-
- Device *dev_;
- bool valid_;
- drmModeAtomicReq *request_;
- std::list<std::unique_ptr<Blob>> blobs_;
-};
-
-class Device
-{
-public:
- Device();
- ~Device();
-
- int init();
-
- int fd() const { return fd_; }
-
- const std::list<Crtc> &crtcs() const { return crtcs_; }
- const std::list<Encoder> &encoders() const { return encoders_; }
- const std::list<Connector> &connectors() const { return connectors_; }
- const std::list<Plane> &planes() const { return planes_; }
- const std::list<Property> &properties() const { return properties_; }
-
- const Object *object(uint32_t id);
-
- std::unique_ptr<FrameBuffer> createFrameBuffer(
- const libcamera::FrameBuffer &buffer,
- const libcamera::PixelFormat &format,
- const libcamera::Size &size,
- const std::array<uint32_t, 4> &strides);
-
- libcamera::Signal<AtomicRequest *> requestComplete;
-
-private:
- Device(const Device &) = delete;
- Device(const Device &&) = delete;
- Device &operator=(const Device &) = delete;
- Device &operator=(const Device &&) = delete;
-
- int openCard();
- int getResources();
-
- void drmEvent();
- static void pageFlipComplete(int fd, unsigned int sequence,
- unsigned int tv_sec, unsigned int tv_usec,
- void *user_data);
-
- int fd_;
-
- std::list<Crtc> crtcs_;
- std::list<Encoder> encoders_;
- std::list<Connector> connectors_;
- std::list<Plane> planes_;
- std::list<Property> properties_;
-
- std::map<uint32_t, Object *> objects_;
-};
-
-} /* namespace DRM */
diff --git a/src/cam/event_loop.cpp b/src/cam/event_loop.cpp
deleted file mode 100644
index cb83845c..00000000
--- a/src/cam/event_loop.cpp
+++ /dev/null
@@ -1,150 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2019, Google Inc.
- *
- * event_loop.cpp - cam - Event loop
- */
-
-#include "event_loop.h"
-
-#include <assert.h>
-#include <event2/event.h>
-#include <event2/thread.h>
-#include <iostream>
-
-EventLoop *EventLoop::instance_ = nullptr;
-
-EventLoop::EventLoop()
-{
- assert(!instance_);
-
- evthread_use_pthreads();
- base_ = event_base_new();
- instance_ = this;
-}
-
-EventLoop::~EventLoop()
-{
- instance_ = nullptr;
-
- events_.clear();
- event_base_free(base_);
- libevent_global_shutdown();
-}
-
-EventLoop *EventLoop::instance()
-{
- return instance_;
-}
-
-int EventLoop::exec()
-{
- exitCode_ = -1;
- event_base_loop(base_, EVLOOP_NO_EXIT_ON_EMPTY);
- return exitCode_;
-}
-
-void EventLoop::exit(int code)
-{
- exitCode_ = code;
- event_base_loopbreak(base_);
-}
-
-void EventLoop::callLater(const std::function<void()> &func)
-{
- {
- std::unique_lock<std::mutex> locker(lock_);
- calls_.push_back(func);
- }
-
- event_base_once(base_, -1, EV_TIMEOUT, dispatchCallback, this, nullptr);
-}
-
-void EventLoop::addFdEvent(int fd, EventType type,
- const std::function<void()> &callback)
-{
- std::unique_ptr<Event> event = std::make_unique<Event>(callback);
- short events = (type & Read ? EV_READ : 0)
- | (type & Write ? EV_WRITE : 0)
- | EV_PERSIST;
-
- event->event_ = event_new(base_, fd, events, &EventLoop::Event::dispatch,
- event.get());
- if (!event->event_) {
- std::cerr << "Failed to create event for fd " << fd << std::endl;
- return;
- }
-
- int ret = event_add(event->event_, nullptr);
- if (ret < 0) {
- std::cerr << "Failed to add event for fd " << fd << std::endl;
- return;
- }
-
- events_.push_back(std::move(event));
-}
-
-void EventLoop::addTimerEvent(const std::chrono::microseconds period,
- const std::function<void()> &callback)
-{
- std::unique_ptr<Event> event = std::make_unique<Event>(callback);
- event->event_ = event_new(base_, -1, EV_PERSIST, &EventLoop::Event::dispatch,
- event.get());
- if (!event->event_) {
- std::cerr << "Failed to create timer event" << std::endl;
- return;
- }
-
- struct timeval tv;
- tv.tv_sec = period.count() / 1000000ULL;
- tv.tv_usec = period.count() % 1000000ULL;
-
- int ret = event_add(event->event_, &tv);
- if (ret < 0) {
- std::cerr << "Failed to add timer event" << std::endl;
- return;
- }
-
- events_.push_back(std::move(event));
-}
-
-void EventLoop::dispatchCallback([[maybe_unused]] evutil_socket_t fd,
- [[maybe_unused]] short flags, void *param)
-{
- EventLoop *loop = static_cast<EventLoop *>(param);
- loop->dispatchCall();
-}
-
-void EventLoop::dispatchCall()
-{
- std::function<void()> call;
-
- {
- std::unique_lock<std::mutex> locker(lock_);
- if (calls_.empty())
- return;
-
- call = calls_.front();
- calls_.pop_front();
- }
-
- call();
-}
-
-EventLoop::Event::Event(const std::function<void()> &callback)
- : callback_(callback), event_(nullptr)
-{
-}
-
-EventLoop::Event::~Event()
-{
- event_del(event_);
- event_free(event_);
-}
-
-void EventLoop::Event::dispatch([[maybe_unused]] int fd,
- [[maybe_unused]] short events, void *arg)
-{
- Event *event = static_cast<Event *>(arg);
- event->callback_();
-}
diff --git a/src/cam/event_loop.h b/src/cam/event_loop.h
deleted file mode 100644
index ef79e8e5..00000000
--- a/src/cam/event_loop.h
+++ /dev/null
@@ -1,68 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2019, Google Inc.
- *
- * event_loop.h - cam - Event loop
- */
-
-#pragma once
-
-#include <chrono>
-#include <functional>
-#include <list>
-#include <memory>
-#include <mutex>
-
-#include <event2/util.h>
-
-struct event_base;
-
-class EventLoop
-{
-public:
- enum EventType {
- Read = 1,
- Write = 2,
- };
-
- EventLoop();
- ~EventLoop();
-
- static EventLoop *instance();
-
- int exec();
- void exit(int code = 0);
-
- void callLater(const std::function<void()> &func);
-
- void addFdEvent(int fd, EventType type,
- const std::function<void()> &handler);
-
- using duration = std::chrono::steady_clock::duration;
- void addTimerEvent(const std::chrono::microseconds period,
- const std::function<void()> &handler);
-
-private:
- struct Event {
- Event(const std::function<void()> &callback);
- ~Event();
-
- static void dispatch(int fd, short events, void *arg);
-
- std::function<void()> callback_;
- struct event *event_;
- };
-
- static EventLoop *instance_;
-
- struct event_base *base_;
- int exitCode_;
-
- std::list<std::function<void()>> calls_;
- std::list<std::unique_ptr<Event>> events_;
- std::mutex lock_;
-
- static void dispatchCallback(evutil_socket_t fd, short flags,
- void *param);
- void dispatchCall();
-};
diff --git a/src/cam/file_sink.cpp b/src/cam/file_sink.cpp
deleted file mode 100644
index 9d60c04e..00000000
--- a/src/cam/file_sink.cpp
+++ /dev/null
@@ -1,137 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2019, Google Inc.
- *
- * file_sink.cpp - File Sink
- */
-
-#include <assert.h>
-#include <fcntl.h>
-#include <iomanip>
-#include <iostream>
-#include <sstream>
-#include <string.h>
-#include <unistd.h>
-
-#include <libcamera/camera.h>
-
-#include "dng_writer.h"
-#include "file_sink.h"
-#include "image.h"
-
-using namespace libcamera;
-
-FileSink::FileSink(const libcamera::Camera *camera,
- const std::map<const libcamera::Stream *, std::string> &streamNames,
- const std::string &pattern)
- : camera_(camera), streamNames_(streamNames), pattern_(pattern)
-{
-}
-
-FileSink::~FileSink()
-{
-}
-
-int FileSink::configure(const libcamera::CameraConfiguration &config)
-{
- int ret = FrameSink::configure(config);
- if (ret < 0)
- return ret;
-
- return 0;
-}
-
-void FileSink::mapBuffer(FrameBuffer *buffer)
-{
- std::unique_ptr<Image> image =
- Image::fromFrameBuffer(buffer, Image::MapMode::ReadOnly);
- assert(image != nullptr);
-
- mappedBuffers_[buffer] = std::move(image);
-}
-
-bool FileSink::processRequest(Request *request)
-{
- for (auto [stream, buffer] : request->buffers())
- writeBuffer(stream, buffer, request->metadata());
-
- return true;
-}
-
-void FileSink::writeBuffer(const Stream *stream, FrameBuffer *buffer,
- [[maybe_unused]] const ControlList &metadata)
-{
- std::string filename;
- size_t pos;
- int fd, ret = 0;
-
- if (!pattern_.empty())
- filename = pattern_;
-
-#ifdef HAVE_TIFF
- bool dng = filename.find(".dng", filename.size() - 4) != std::string::npos;
-#endif /* HAVE_TIFF */
-
- if (filename.empty() || filename.back() == '/')
- filename += "frame-#.bin";
-
- pos = filename.find_first_of('#');
- if (pos != std::string::npos) {
- std::stringstream ss;
- ss << streamNames_[stream] << "-" << std::setw(6)
- << std::setfill('0') << buffer->metadata().sequence;
- filename.replace(pos, 1, ss.str());
- }
-
- Image *image = mappedBuffers_[buffer].get();
-
-#ifdef HAVE_TIFF
- if (dng) {
- ret = DNGWriter::write(filename.c_str(), camera_,
- stream->configuration(), metadata,
- buffer, image->data(0).data());
- if (ret < 0)
- std::cerr << "failed to write DNG file `" << filename
- << "'" << std::endl;
-
- return;
- }
-#endif /* HAVE_TIFF */
-
- fd = open(filename.c_str(), O_CREAT | O_WRONLY |
- (pos == std::string::npos ? O_APPEND : O_TRUNC),
- S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
- if (fd == -1) {
- ret = -errno;
- std::cerr << "failed to open file " << filename << ": "
- << strerror(-ret) << std::endl;
- return;
- }
-
- for (unsigned int i = 0; i < buffer->planes().size(); ++i) {
- const FrameMetadata::Plane &meta = buffer->metadata().planes()[i];
-
- Span<uint8_t> data = image->data(i);
- unsigned int length = std::min<unsigned int>(meta.bytesused, data.size());
-
- if (meta.bytesused > data.size())
- std::cerr << "payload size " << meta.bytesused
- << " larger than plane size " << data.size()
- << std::endl;
-
- ret = ::write(fd, data.data(), length);
- if (ret < 0) {
- ret = -errno;
- std::cerr << "write error: " << strerror(-ret)
- << std::endl;
- break;
- } else if (ret != (int)length) {
- std::cerr << "write error: only " << ret
- << " bytes written instead of "
- << length << std::endl;
- break;
- }
- }
-
- close(fd);
-}
diff --git a/src/cam/file_sink.h b/src/cam/file_sink.h
deleted file mode 100644
index 9ce8b619..00000000
--- a/src/cam/file_sink.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2019, Google Inc.
- *
- * file_sink.h - File Sink
- */
-
-#pragma once
-
-#include <map>
-#include <memory>
-#include <string>
-
-#include <libcamera/stream.h>
-
-#include "frame_sink.h"
-
-class Image;
-
-class FileSink : public FrameSink
-{
-public:
- FileSink(const libcamera::Camera *camera,
- const std::map<const libcamera::Stream *, std::string> &streamNames,
- const std::string &pattern = "");
- ~FileSink();
-
- int configure(const libcamera::CameraConfiguration &config) override;
-
- void mapBuffer(libcamera::FrameBuffer *buffer) override;
-
- bool processRequest(libcamera::Request *request) override;
-
-private:
- void writeBuffer(const libcamera::Stream *stream,
- libcamera::FrameBuffer *buffer,
- const libcamera::ControlList &metadata);
-
- const libcamera::Camera *camera_;
- std::map<const libcamera::Stream *, std::string> streamNames_;
- std::string pattern_;
- std::map<libcamera::FrameBuffer *, std::unique_ptr<Image>> mappedBuffers_;
-};
diff --git a/src/cam/frame_sink.cpp b/src/cam/frame_sink.cpp
deleted file mode 100644
index af21d575..00000000
--- a/src/cam/frame_sink.cpp
+++ /dev/null
@@ -1,67 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2021, Ideas on Board Oy
- *
- * frame_sink.cpp - Base Frame Sink Class
- */
-
-#include "frame_sink.h"
-
-/**
- * \class FrameSink
- * \brief Abstract class to model a consumer of frames
- *
- * The FrameSink class models the consumer that processes frames after a request
- * completes. It receives requests through processRequest(), and processes them
- * synchronously or asynchronously. This allows frame sinks to hold onto frames
- * for an extended period of time, for instance to display them until a new
- * frame arrives.
- *
- * A frame sink processes whole requests, and is solely responsible for deciding
- * how to handle different frame buffers in case multiple streams are captured.
- */
-
-FrameSink::~FrameSink()
-{
-}
-
-int FrameSink::configure([[maybe_unused]] const libcamera::CameraConfiguration &config)
-{
- return 0;
-}
-
-void FrameSink::mapBuffer([[maybe_unused]] libcamera::FrameBuffer *buffer)
-{
-}
-
-int FrameSink::start()
-{
- return 0;
-}
-
-int FrameSink::stop()
-{
- return 0;
-}
-
-/**
- * \fn FrameSink::processRequest()
- * \param[in] request The request
- *
- * This function is called to instruct the sink to process a request. The sink
- * may process the request synchronously or queue it for asynchronous
- * processing.
- *
- * When the request is processed synchronously, this function shall return true.
- * The \a request shall not be accessed by the FrameSink after the function
- * returns.
- *
- * When the request is processed asynchronously, the FrameSink temporarily takes
- * ownership of the \a request. The function shall return false, and the
- * FrameSink shall emit the requestProcessed signal when the request processing
- * completes. If the stop() function is called before the request processing
- * completes, it shall release the request synchronously.
- *
- * \return True if the request has been processed synchronously, false if
- * processing has been queued
- */
diff --git a/src/cam/frame_sink.h b/src/cam/frame_sink.h
deleted file mode 100644
index ca4347cb..00000000
--- a/src/cam/frame_sink.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2021, Ideas on Board Oy
- *
- * frame_sink.h - Base Frame Sink Class
- */
-
-#pragma once
-
-#include <libcamera/base/signal.h>
-
-namespace libcamera {
-class CameraConfiguration;
-class FrameBuffer;
-class Request;
-} /* namespace libcamera */
-
-class FrameSink
-{
-public:
- virtual ~FrameSink();
-
- virtual int configure(const libcamera::CameraConfiguration &config);
-
- virtual void mapBuffer(libcamera::FrameBuffer *buffer);
-
- virtual int start();
- virtual int stop();
-
- virtual bool processRequest(libcamera::Request *request) = 0;
- libcamera::Signal<libcamera::Request *> requestProcessed;
-};
diff --git a/src/cam/image.cpp b/src/cam/image.cpp
deleted file mode 100644
index fe2cc6da..00000000
--- a/src/cam/image.cpp
+++ /dev/null
@@ -1,109 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2021, Ideas on Board Oy
- *
- * image.cpp - Multi-planar image with access to pixel data
- */
-
-#include "image.h"
-
-#include <assert.h>
-#include <errno.h>
-#include <iostream>
-#include <map>
-#include <string.h>
-#include <sys/mman.h>
-#include <unistd.h>
-
-using namespace libcamera;
-
-std::unique_ptr<Image> Image::fromFrameBuffer(const FrameBuffer *buffer, MapMode mode)
-{
- std::unique_ptr<Image> image{ new Image() };
-
- assert(!buffer->planes().empty());
-
- int mmapFlags = 0;
-
- if (mode & MapMode::ReadOnly)
- mmapFlags |= PROT_READ;
-
- if (mode & MapMode::WriteOnly)
- mmapFlags |= PROT_WRITE;
-
- struct MappedBufferInfo {
- uint8_t *address = nullptr;
- size_t mapLength = 0;
- size_t dmabufLength = 0;
- };
- std::map<int, MappedBufferInfo> mappedBuffers;
-
- for (const FrameBuffer::Plane &plane : buffer->planes()) {
- const int fd = plane.fd.get();
- if (mappedBuffers.find(fd) == mappedBuffers.end()) {
- const size_t length = lseek(fd, 0, SEEK_END);
- mappedBuffers[fd] = MappedBufferInfo{ nullptr, 0, length };
- }
-
- const size_t length = mappedBuffers[fd].dmabufLength;
-
- if (plane.offset > length ||
- plane.offset + plane.length > length) {
- std::cerr << "plane is out of buffer: buffer length="
- << length << ", plane offset=" << plane.offset
- << ", plane length=" << plane.length
- << std::endl;
- return nullptr;
- }
- size_t &mapLength = mappedBuffers[fd].mapLength;
- mapLength = std::max(mapLength,
- static_cast<size_t>(plane.offset + plane.length));
- }
-
- for (const FrameBuffer::Plane &plane : buffer->planes()) {
- const int fd = plane.fd.get();
- auto &info = mappedBuffers[fd];
- if (!info.address) {
- void *address = mmap(nullptr, info.mapLength, mmapFlags,
- MAP_SHARED, fd, 0);
- if (address == MAP_FAILED) {
- int error = -errno;
- std::cerr << "Failed to mmap plane: "
- << strerror(-error) << std::endl;
- return nullptr;
- }
-
- info.address = static_cast<uint8_t *>(address);
- image->maps_.emplace_back(info.address, info.mapLength);
- }
-
- image->planes_.emplace_back(info.address + plane.offset, plane.length);
- }
-
- return image;
-}
-
-Image::Image() = default;
-
-Image::~Image()
-{
- for (Span<uint8_t> &map : maps_)
- munmap(map.data(), map.size());
-}
-
-unsigned int Image::numPlanes() const
-{
- return planes_.size();
-}
-
-Span<uint8_t> Image::data(unsigned int plane)
-{
- assert(plane <= planes_.size());
- return planes_[plane];
-}
-
-Span<const uint8_t> Image::data(unsigned int plane) const
-{
- assert(plane <= planes_.size());
- return planes_[plane];
-}
diff --git a/src/cam/image.h b/src/cam/image.h
deleted file mode 100644
index 7953b177..00000000
--- a/src/cam/image.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2021, Ideas on Board Oy
- *
- * image.h - Multi-planar image with access to pixel data
- */
-
-#pragma once
-
-#include <memory>
-#include <stdint.h>
-#include <vector>
-
-#include <libcamera/base/class.h>
-#include <libcamera/base/flags.h>
-#include <libcamera/base/span.h>
-
-#include <libcamera/framebuffer.h>
-
-class Image
-{
-public:
- enum class MapMode {
- ReadOnly = 1 << 0,
- WriteOnly = 1 << 1,
- ReadWrite = ReadOnly | WriteOnly,
- };
-
- static std::unique_ptr<Image> fromFrameBuffer(const libcamera::FrameBuffer *buffer,
- MapMode mode);
-
- ~Image();
-
- unsigned int numPlanes() const;
-
- libcamera::Span<uint8_t> data(unsigned int plane);
- libcamera::Span<const uint8_t> data(unsigned int plane) const;
-
-private:
- LIBCAMERA_DISABLE_COPY(Image)
-
- Image();
-
- std::vector<libcamera::Span<uint8_t>> maps_;
- std::vector<libcamera::Span<uint8_t>> planes_;
-};
-
-namespace libcamera {
-LIBCAMERA_FLAGS_ENABLE_OPERATORS(Image::MapMode)
-}
diff --git a/src/cam/kms_sink.cpp b/src/cam/kms_sink.cpp
deleted file mode 100644
index 754b061e..00000000
--- a/src/cam/kms_sink.cpp
+++ /dev/null
@@ -1,538 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2021, Ideas on Board Oy
- *
- * kms_sink.cpp - KMS Sink
- */
-
-#include "kms_sink.h"
-
-#include <array>
-#include <algorithm>
-#include <assert.h>
-#include <iostream>
-#include <limits.h>
-#include <memory>
-#include <stdint.h>
-#include <string.h>
-
-#include <libcamera/camera.h>
-#include <libcamera/formats.h>
-#include <libcamera/framebuffer.h>
-#include <libcamera/stream.h>
-
-#include "drm.h"
-
-KMSSink::KMSSink(const std::string &connectorName)
- : connector_(nullptr), crtc_(nullptr), plane_(nullptr), mode_(nullptr)
-{
- int ret = dev_.init();
- if (ret < 0)
- return;
-
- /*
- * Find the requested connector. If no specific connector is requested,
- * pick the first connected connector or, if no connector is connected,
- * the first connector with unknown status.
- */
- for (const DRM::Connector &conn : dev_.connectors()) {
- if (!connectorName.empty()) {
- if (conn.name() != connectorName)
- continue;
-
- connector_ = &conn;
- break;
- }
-
- if (conn.status() == DRM::Connector::Connected) {
- connector_ = &conn;
- break;
- }
-
- if (!connector_ && conn.status() == DRM::Connector::Unknown)
- connector_ = &conn;
- }
-
- if (!connector_) {
- if (!connectorName.empty())
- std::cerr
- << "Connector " << connectorName << " not found"
- << std::endl;
- else
- std::cerr << "No connected connector found" << std::endl;
- return;
- }
-
- dev_.requestComplete.connect(this, &KMSSink::requestComplete);
-}
-
-void KMSSink::mapBuffer(libcamera::FrameBuffer *buffer)
-{
- std::array<uint32_t, 4> strides = {};
-
- /* \todo Should libcamera report per-plane strides ? */
- unsigned int uvStrideMultiplier;
-
- switch (format_) {
- case libcamera::formats::NV24:
- case libcamera::formats::NV42:
- uvStrideMultiplier = 4;
- break;
- case libcamera::formats::YUV420:
- case libcamera::formats::YVU420:
- case libcamera::formats::YUV422:
- uvStrideMultiplier = 1;
- break;
- default:
- uvStrideMultiplier = 2;
- break;
- }
-
- strides[0] = stride_;
- for (unsigned int i = 1; i < buffer->planes().size(); ++i)
- strides[i] = stride_ * uvStrideMultiplier / 2;
-
- std::unique_ptr<DRM::FrameBuffer> drmBuffer =
- dev_.createFrameBuffer(*buffer, format_, size_, strides);
- if (!drmBuffer)
- return;
-
- buffers_.emplace(std::piecewise_construct,
- std::forward_as_tuple(buffer),
- std::forward_as_tuple(std::move(drmBuffer)));
-}
-
-int KMSSink::configure(const libcamera::CameraConfiguration &config)
-{
- if (!connector_)
- return -EINVAL;
-
- crtc_ = nullptr;
- plane_ = nullptr;
- mode_ = nullptr;
-
- const libcamera::StreamConfiguration &cfg = config.at(0);
-
- /* Find the best mode for the stream size. */
- const std::vector<DRM::Mode> &modes = connector_->modes();
-
- unsigned int cfgArea = cfg.size.width * cfg.size.height;
- unsigned int bestDistance = UINT_MAX;
-
- for (const DRM::Mode &mode : modes) {
- unsigned int modeArea = mode.hdisplay * mode.vdisplay;
- unsigned int distance = modeArea > cfgArea ? modeArea - cfgArea
- : cfgArea - modeArea;
-
- if (distance < bestDistance) {
- mode_ = &mode;
- bestDistance = distance;
-
- /*
- * If the sizes match exactly, there will be no better
- * match.
- */
- if (distance == 0)
- break;
- }
- }
-
- if (!mode_) {
- std::cerr << "No modes\n";
- return -EINVAL;
- }
-
- int ret = configurePipeline(cfg.pixelFormat);
- if (ret < 0)
- return ret;
-
- size_ = cfg.size;
- stride_ = cfg.stride;
-
- /* Configure color space. */
- colorEncoding_ = std::nullopt;
- colorRange_ = std::nullopt;
-
- if (cfg.colorSpace->ycbcrEncoding == libcamera::ColorSpace::YcbcrEncoding::None)
- return 0;
-
- /*
- * The encoding and range enums are defined in the kernel but not
- * exposed in public headers.
- */
- enum drm_color_encoding {
- DRM_COLOR_YCBCR_BT601,
- DRM_COLOR_YCBCR_BT709,
- DRM_COLOR_YCBCR_BT2020,
- };
-
- enum drm_color_range {
- DRM_COLOR_YCBCR_LIMITED_RANGE,
- DRM_COLOR_YCBCR_FULL_RANGE,
- };
-
- const DRM::Property *colorEncoding = plane_->property("COLOR_ENCODING");
- const DRM::Property *colorRange = plane_->property("COLOR_RANGE");
-
- if (colorEncoding) {
- drm_color_encoding encoding;
-
- switch (cfg.colorSpace->ycbcrEncoding) {
- case libcamera::ColorSpace::YcbcrEncoding::Rec601:
- default:
- encoding = DRM_COLOR_YCBCR_BT601;
- break;
- case libcamera::ColorSpace::YcbcrEncoding::Rec709:
- encoding = DRM_COLOR_YCBCR_BT709;
- break;
- case libcamera::ColorSpace::YcbcrEncoding::Rec2020:
- encoding = DRM_COLOR_YCBCR_BT2020;
- break;
- }
-
- for (const auto &[id, name] : colorEncoding->enums()) {
- if (id == encoding) {
- colorEncoding_ = encoding;
- break;
- }
- }
- }
-
- if (colorRange) {
- drm_color_range range;
-
- switch (cfg.colorSpace->range) {
- case libcamera::ColorSpace::Range::Limited:
- default:
- range = DRM_COLOR_YCBCR_LIMITED_RANGE;
- break;
- case libcamera::ColorSpace::Range::Full:
- range = DRM_COLOR_YCBCR_FULL_RANGE;
- break;
- }
-
- for (const auto &[id, name] : colorRange->enums()) {
- if (id == range) {
- colorRange_ = range;
- break;
- }
- }
- }
-
- if (!colorEncoding_ || !colorRange_)
- std::cerr << "Color space " << cfg.colorSpace->toString()
- << " not supported by the display device."
- << " Colors may be wrong." << std::endl;
-
- return 0;
-}
-
-int KMSSink::selectPipeline(const libcamera::PixelFormat &format)
-{
- /*
- * If the requested format has an alpha channel, also consider the X
- * variant.
- */
- libcamera::PixelFormat xFormat;
-
- switch (format) {
- case libcamera::formats::ABGR8888:
- xFormat = libcamera::formats::XBGR8888;
- break;
- case libcamera::formats::ARGB8888:
- xFormat = libcamera::formats::XRGB8888;
- break;
- case libcamera::formats::BGRA8888:
- xFormat = libcamera::formats::BGRX8888;
- break;
- case libcamera::formats::RGBA8888:
- xFormat = libcamera::formats::RGBX8888;
- break;
- }
-
- /*
- * Find a CRTC and plane suitable for the request format and the
- * connector at the end of the pipeline. Restrict the search to primary
- * planes for now.
- */
- for (const DRM::Encoder *encoder : connector_->encoders()) {
- for (const DRM::Crtc *crtc : encoder->possibleCrtcs()) {
- for (const DRM::Plane *plane : crtc->planes()) {
- if (plane->type() != DRM::Plane::TypePrimary)
- continue;
-
- if (plane->supportsFormat(format)) {
- crtc_ = crtc;
- plane_ = plane;
- format_ = format;
- return 0;
- }
-
- if (plane->supportsFormat(xFormat)) {
- crtc_ = crtc;
- plane_ = plane;
- format_ = xFormat;
- return 0;
- }
- }
- }
- }
-
- return -EPIPE;
-}
-
-int KMSSink::configurePipeline(const libcamera::PixelFormat &format)
-{
- const int ret = selectPipeline(format);
- if (ret) {
- std::cerr
- << "Unable to find display pipeline for format "
- << format << std::endl;
-
- return ret;
- }
-
- std::cout
- << "Using KMS plane " << plane_->id() << ", CRTC " << crtc_->id()
- << ", connector " << connector_->name()
- << " (" << connector_->id() << "), mode " << mode_->hdisplay
- << "x" << mode_->vdisplay << "@" << mode_->vrefresh << std::endl;
-
- return 0;
-}
-
-int KMSSink::start()
-{
- std::unique_ptr<DRM::AtomicRequest> request;
-
- int ret = FrameSink::start();
- if (ret < 0)
- return ret;
-
- /* Disable all CRTCs and planes to start from a known valid state. */
- request = std::make_unique<DRM::AtomicRequest>(&dev_);
-
- for (const DRM::Crtc &crtc : dev_.crtcs())
- request->addProperty(&crtc, "ACTIVE", 0);
-
- for (const DRM::Plane &plane : dev_.planes()) {
- request->addProperty(&plane, "CRTC_ID", 0);
- request->addProperty(&plane, "FB_ID", 0);
- }
-
- ret = request->commit(DRM::AtomicRequest::FlagAllowModeset);
- if (ret < 0) {
- std::cerr
- << "Failed to disable CRTCs and planes: "
- << strerror(-ret) << std::endl;
- return ret;
- }
-
- return 0;
-}
-
-int KMSSink::stop()
-{
- /* Display pipeline. */
- DRM::AtomicRequest request(&dev_);
-
- request.addProperty(connector_, "CRTC_ID", 0);
- request.addProperty(crtc_, "ACTIVE", 0);
- request.addProperty(crtc_, "MODE_ID", 0);
- request.addProperty(plane_, "CRTC_ID", 0);
- request.addProperty(plane_, "FB_ID", 0);
-
- int ret = request.commit(DRM::AtomicRequest::FlagAllowModeset);
- if (ret < 0) {
- std::cerr
- << "Failed to stop display pipeline: "
- << strerror(-ret) << std::endl;
- return ret;
- }
-
- /* Free all buffers. */
- pending_.reset();
- queued_.reset();
- active_.reset();
- buffers_.clear();
-
- return FrameSink::stop();
-}
-
-bool KMSSink::testModeSet(DRM::FrameBuffer *drmBuffer,
- const libcamera::Rectangle &src,
- const libcamera::Rectangle &dst)
-{
- DRM::AtomicRequest drmRequest{ &dev_ };
-
- drmRequest.addProperty(connector_, "CRTC_ID", crtc_->id());
-
- drmRequest.addProperty(crtc_, "ACTIVE", 1);
- drmRequest.addProperty(crtc_, "MODE_ID", mode_->toBlob(&dev_));
-
- drmRequest.addProperty(plane_, "CRTC_ID", crtc_->id());
- drmRequest.addProperty(plane_, "FB_ID", drmBuffer->id());
- drmRequest.addProperty(plane_, "SRC_X", src.x << 16);
- drmRequest.addProperty(plane_, "SRC_Y", src.y << 16);
- drmRequest.addProperty(plane_, "SRC_W", src.width << 16);
- drmRequest.addProperty(plane_, "SRC_H", src.height << 16);
- drmRequest.addProperty(plane_, "CRTC_X", dst.x);
- drmRequest.addProperty(plane_, "CRTC_Y", dst.y);
- drmRequest.addProperty(plane_, "CRTC_W", dst.width);
- drmRequest.addProperty(plane_, "CRTC_H", dst.height);
-
- return !drmRequest.commit(DRM::AtomicRequest::FlagAllowModeset |
- DRM::AtomicRequest::FlagTestOnly);
-}
-
-bool KMSSink::setupComposition(DRM::FrameBuffer *drmBuffer)
-{
- /*
- * Test composition options, from most to least desirable, to select the
- * best one.
- */
- const libcamera::Rectangle framebuffer{ size_ };
- const libcamera::Rectangle display{ 0, 0, mode_->hdisplay, mode_->vdisplay };
-
- /* 1. Scale the frame buffer to full screen, preserving aspect ratio. */
- libcamera::Rectangle src = framebuffer;
- libcamera::Rectangle dst = display.size().boundedToAspectRatio(framebuffer.size())
- .centeredTo(display.center());
-
- if (testModeSet(drmBuffer, src, dst)) {
- std::cout << "KMS: full-screen scaled output, square pixels"
- << std::endl;
- src_ = src;
- dst_ = dst;
- return true;
- }
-
- /*
- * 2. Scale the frame buffer to full screen, without preserving aspect
- * ratio.
- */
- src = framebuffer;
- dst = display;
-
- if (testModeSet(drmBuffer, src, dst)) {
- std::cout << "KMS: full-screen scaled output, non-square pixels"
- << std::endl;
- src_ = src;
- dst_ = dst;
- return true;
- }
-
- /* 3. Center the frame buffer on the display. */
- src = display.size().centeredTo(framebuffer.center()).boundedTo(framebuffer);
- dst = framebuffer.size().centeredTo(display.center()).boundedTo(display);
-
- if (testModeSet(drmBuffer, src, dst)) {
- std::cout << "KMS: centered output" << std::endl;
- src_ = src;
- dst_ = dst;
- return true;
- }
-
- /* 4. Align the frame buffer on the top-left of the display. */
- src = framebuffer.boundedTo(display);
- dst = display.boundedTo(framebuffer);
-
- if (testModeSet(drmBuffer, src, dst)) {
- std::cout << "KMS: top-left aligned output" << std::endl;
- src_ = src;
- dst_ = dst;
- return true;
- }
-
- return false;
-}
-
-bool KMSSink::processRequest(libcamera::Request *camRequest)
-{
- /*
- * Perform a very crude rate adaptation by simply dropping the request
- * if the display queue is full.
- */
- if (pending_)
- return true;
-
- libcamera::FrameBuffer *buffer = camRequest->buffers().begin()->second;
- auto iter = buffers_.find(buffer);
- if (iter == buffers_.end())
- return true;
-
- DRM::FrameBuffer *drmBuffer = iter->second.get();
-
- unsigned int flags = DRM::AtomicRequest::FlagAsync;
- std::unique_ptr<DRM::AtomicRequest> drmRequest =
- std::make_unique<DRM::AtomicRequest>(&dev_);
- drmRequest->addProperty(plane_, "FB_ID", drmBuffer->id());
-
- if (!active_ && !queued_) {
- /* Enable the display pipeline on the first frame. */
- if (!setupComposition(drmBuffer)) {
- std::cerr << "Failed to setup composition" << std::endl;
- return true;
- }
-
- drmRequest->addProperty(connector_, "CRTC_ID", crtc_->id());
-
- drmRequest->addProperty(crtc_, "ACTIVE", 1);
- drmRequest->addProperty(crtc_, "MODE_ID", mode_->toBlob(&dev_));
-
- drmRequest->addProperty(plane_, "CRTC_ID", crtc_->id());
- drmRequest->addProperty(plane_, "SRC_X", src_.x << 16);
- drmRequest->addProperty(plane_, "SRC_Y", src_.y << 16);
- drmRequest->addProperty(plane_, "SRC_W", src_.width << 16);
- drmRequest->addProperty(plane_, "SRC_H", src_.height << 16);
- drmRequest->addProperty(plane_, "CRTC_X", dst_.x);
- drmRequest->addProperty(plane_, "CRTC_Y", dst_.y);
- drmRequest->addProperty(plane_, "CRTC_W", dst_.width);
- drmRequest->addProperty(plane_, "CRTC_H", dst_.height);
-
- if (colorEncoding_)
- drmRequest->addProperty(plane_, "COLOR_ENCODING", *colorEncoding_);
- if (colorRange_)
- drmRequest->addProperty(plane_, "COLOR_RANGE", *colorRange_);
-
- flags |= DRM::AtomicRequest::FlagAllowModeset;
- }
-
- pending_ = std::make_unique<Request>(std::move(drmRequest), camRequest);
-
- std::lock_guard<std::mutex> lock(lock_);
-
- if (!queued_) {
- int ret = pending_->drmRequest_->commit(flags);
- if (ret < 0) {
- std::cerr
- << "Failed to commit atomic request: "
- << strerror(-ret) << std::endl;
- /* \todo Implement error handling */
- }
-
- queued_ = std::move(pending_);
- }
-
- return false;
-}
-
-void KMSSink::requestComplete(DRM::AtomicRequest *request)
-{
- std::lock_guard<std::mutex> lock(lock_);
-
- assert(queued_ && queued_->drmRequest_.get() == request);
-
- /* Complete the active request, if any. */
- if (active_)
- requestProcessed.emit(active_->camRequest_);
-
- /* The queued request becomes active. */
- active_ = std::move(queued_);
-
- /* Queue the pending request, if any. */
- if (pending_) {
- pending_->drmRequest_->commit(DRM::AtomicRequest::FlagAsync);
- queued_ = std::move(pending_);
- }
-}
diff --git a/src/cam/kms_sink.h b/src/cam/kms_sink.h
deleted file mode 100644
index e2c618a1..00000000
--- a/src/cam/kms_sink.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2021, Ideas on Board Oy
- *
- * kms_sink.h - KMS Sink
- */
-
-#pragma once
-
-#include <list>
-#include <memory>
-#include <mutex>
-#include <optional>
-#include <string>
-#include <utility>
-
-#include <libcamera/base/signal.h>
-
-#include <libcamera/geometry.h>
-#include <libcamera/pixel_format.h>
-
-#include "drm.h"
-#include "frame_sink.h"
-
-class KMSSink : public FrameSink
-{
-public:
- KMSSink(const std::string &connectorName);
-
- void mapBuffer(libcamera::FrameBuffer *buffer) override;
-
- int configure(const libcamera::CameraConfiguration &config) override;
- int start() override;
- int stop() override;
-
- bool processRequest(libcamera::Request *request) override;
-
-private:
- class Request
- {
- public:
- Request(std::unique_ptr<DRM::AtomicRequest> drmRequest,
- libcamera::Request *camRequest)
- : drmRequest_(std::move(drmRequest)), camRequest_(camRequest)
- {
- }
-
- std::unique_ptr<DRM::AtomicRequest> drmRequest_;
- libcamera::Request *camRequest_;
- };
-
- int selectPipeline(const libcamera::PixelFormat &format);
- int configurePipeline(const libcamera::PixelFormat &format);
- bool testModeSet(DRM::FrameBuffer *drmBuffer,
- const libcamera::Rectangle &src,
- const libcamera::Rectangle &dst);
- bool setupComposition(DRM::FrameBuffer *drmBuffer);
-
- void requestComplete(DRM::AtomicRequest *request);
-
- DRM::Device dev_;
-
- const DRM::Connector *connector_;
- const DRM::Crtc *crtc_;
- const DRM::Plane *plane_;
- const DRM::Mode *mode_;
-
- libcamera::PixelFormat format_;
- libcamera::Size size_;
- unsigned int stride_;
- std::optional<unsigned int> colorEncoding_;
- std::optional<unsigned int> colorRange_;
-
- libcamera::Rectangle src_;
- libcamera::Rectangle dst_;
-
- std::map<libcamera::FrameBuffer *, std::unique_ptr<DRM::FrameBuffer>> buffers_;
-
- std::mutex lock_;
- std::unique_ptr<Request> pending_;
- std::unique_ptr<Request> queued_;
- std::unique_ptr<Request> active_;
-};
diff --git a/src/cam/main.cpp b/src/cam/main.cpp
deleted file mode 100644
index d70130e2..00000000
--- a/src/cam/main.cpp
+++ /dev/null
@@ -1,362 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2019, Google Inc.
- *
- * main.cpp - cam - The libcamera swiss army knife
- */
-
-#include <atomic>
-#include <iomanip>
-#include <iostream>
-#include <signal.h>
-#include <string.h>
-
-#include <libcamera/libcamera.h>
-#include <libcamera/property_ids.h>
-
-#include "camera_session.h"
-#include "event_loop.h"
-#include "main.h"
-#include "options.h"
-#include "stream_options.h"
-
-using namespace libcamera;
-
-class CamApp
-{
-public:
- CamApp();
-
- static CamApp *instance();
-
- int init(int argc, char **argv);
- void cleanup();
-
- int exec();
- void quit();
-
-private:
- void cameraAdded(std::shared_ptr<Camera> cam);
- void cameraRemoved(std::shared_ptr<Camera> cam);
- void captureDone();
- int parseOptions(int argc, char *argv[]);
- int run();
-
- static std::string cameraName(const Camera *camera);
-
- static CamApp *app_;
- OptionsParser::Options options_;
-
- std::unique_ptr<CameraManager> cm_;
-
- std::atomic_uint loopUsers_;
- EventLoop loop_;
-};
-
-CamApp *CamApp::app_ = nullptr;
-
-CamApp::CamApp()
- : loopUsers_(0)
-{
- CamApp::app_ = this;
-}
-
-CamApp *CamApp::instance()
-{
- return CamApp::app_;
-}
-
-int CamApp::init(int argc, char **argv)
-{
- int ret;
-
- ret = parseOptions(argc, argv);
- if (ret < 0)
- return ret;
-
- cm_ = std::make_unique<CameraManager>();
-
- ret = cm_->start();
- if (ret) {
- std::cout << "Failed to start camera manager: "
- << strerror(-ret) << std::endl;
- return ret;
- }
-
- return 0;
-}
-
-void CamApp::cleanup()
-{
- cm_->stop();
-}
-
-int CamApp::exec()
-{
- int ret;
-
- ret = run();
- cleanup();
-
- return ret;
-}
-
-void CamApp::quit()
-{
- loop_.exit();
-}
-
-int CamApp::parseOptions(int argc, char *argv[])
-{
- StreamKeyValueParser streamKeyValue;
-
- OptionsParser parser;
- parser.addOption(OptCamera, OptionString,
- "Specify which camera to operate on, by id or by index", "camera",
- ArgumentRequired, "camera", true);
- parser.addOption(OptHelp, OptionNone, "Display this help message",
- "help");
- parser.addOption(OptInfo, OptionNone,
- "Display information about stream(s)", "info");
- parser.addOption(OptList, OptionNone, "List all cameras", "list");
- parser.addOption(OptListControls, OptionNone, "List cameras controls",
- "list-controls");
- parser.addOption(OptListProperties, OptionNone, "List cameras properties",
- "list-properties");
- parser.addOption(OptMonitor, OptionNone,
- "Monitor for hotplug and unplug camera events",
- "monitor");
-
- /* Sub-options of OptCamera: */
- parser.addOption(OptCapture, OptionInteger,
- "Capture until interrupted by user or until <count> frames captured",
- "capture", ArgumentOptional, "count", false,
- OptCamera);
-#ifdef HAVE_KMS
- parser.addOption(OptDisplay, OptionString,
- "Display viewfinder through DRM/KMS on specified connector",
- "display", ArgumentOptional, "connector", false,
- OptCamera);
-#endif
- parser.addOption(OptFile, OptionString,
- "Write captured frames to disk\n"
- "If the file name ends with a '/', it sets the directory in which\n"
- "to write files, using the default file name. Otherwise it sets the\n"
- "full file path and name. The first '#' character in the file name\n"
- "is expanded to the camera index, stream name and frame sequence number.\n"
-#ifdef HAVE_TIFF
- "If the file name ends with '.dng', then the frame will be written to\n"
- "the output file(s) in DNG format.\n"
-#endif
- "The default file name is 'frame-#.bin'.",
- "file", ArgumentOptional, "filename", false,
- OptCamera);
-#ifdef HAVE_SDL
- parser.addOption(OptSDL, OptionNone, "Display viewfinder through SDL",
- "sdl", ArgumentNone, "", false, OptCamera);
-#endif
- parser.addOption(OptStream, &streamKeyValue,
- "Set configuration of a camera stream", "stream", true,
- OptCamera);
- parser.addOption(OptStrictFormats, OptionNone,
- "Do not allow requested stream format(s) to be adjusted",
- "strict-formats", ArgumentNone, nullptr, false,
- OptCamera);
- parser.addOption(OptMetadata, OptionNone,
- "Print the metadata for completed requests",
- "metadata", ArgumentNone, nullptr, false,
- OptCamera);
- parser.addOption(OptCaptureScript, OptionString,
- "Load a capture session configuration script from a file",
- "script", ArgumentRequired, "script", false,
- OptCamera);
-
- options_ = parser.parse(argc, argv);
- if (!options_.valid())
- return -EINVAL;
-
- if (options_.empty() || options_.isSet(OptHelp)) {
- parser.usage();
- return options_.empty() ? -EINVAL : -EINTR;
- }
-
- return 0;
-}
-
-void CamApp::cameraAdded(std::shared_ptr<Camera> cam)
-{
- std::cout << "Camera Added: " << cam->id() << std::endl;
-}
-
-void CamApp::cameraRemoved(std::shared_ptr<Camera> cam)
-{
- std::cout << "Camera Removed: " << cam->id() << std::endl;
-}
-
-void CamApp::captureDone()
-{
- if (--loopUsers_ == 0)
- EventLoop::instance()->exit(0);
-}
-
-int CamApp::run()
-{
- int ret;
-
- /* 1. List all cameras. */
- if (options_.isSet(OptList)) {
- std::cout << "Available cameras:" << std::endl;
-
- unsigned int index = 1;
- for (const std::shared_ptr<Camera> &cam : cm_->cameras()) {
- std::cout << index << ": " << cameraName(cam.get()) << std::endl;
- index++;
- }
- }
-
- /* 2. Create the camera sessions. */
- std::vector<std::unique_ptr<CameraSession>> sessions;
-
- if (options_.isSet(OptCamera)) {
- unsigned int index = 0;
-
- for (const OptionValue &camera : options_[OptCamera].toArray()) {
- std::unique_ptr<CameraSession> session =
- std::make_unique<CameraSession>(cm_.get(),
- camera.toString(),
- index,
- camera.children());
- if (!session->isValid()) {
- std::cout << "Failed to create camera session" << std::endl;
- return -EINVAL;
- }
-
- std::cout << "Using camera " << session->camera()->id()
- << " as cam" << index << std::endl;
-
- session->captureDone.connect(this, &CamApp::captureDone);
-
- sessions.push_back(std::move(session));
- index++;
- }
- }
-
- /* 3. Print camera information. */
- if (options_.isSet(OptListControls) ||
- options_.isSet(OptListProperties) ||
- options_.isSet(OptInfo)) {
- for (const auto &session : sessions) {
- if (options_.isSet(OptListControls))
- session->listControls();
- if (options_.isSet(OptListProperties))
- session->listProperties();
- if (options_.isSet(OptInfo))
- session->infoConfiguration();
- }
- }
-
- /* 4. Start capture. */
- for (const auto &session : sessions) {
- if (!session->options().isSet(OptCapture))
- continue;
-
- ret = session->start();
- if (ret) {
- std::cout << "Failed to start camera session" << std::endl;
- return ret;
- }
-
- loopUsers_++;
- }
-
- /* 5. Enable hotplug monitoring. */
- if (options_.isSet(OptMonitor)) {
- std::cout << "Monitoring new hotplug and unplug events" << std::endl;
- std::cout << "Press Ctrl-C to interrupt" << std::endl;
-
- cm_->cameraAdded.connect(this, &CamApp::cameraAdded);
- cm_->cameraRemoved.connect(this, &CamApp::cameraRemoved);
-
- loopUsers_++;
- }
-
- if (loopUsers_)
- loop_.exec();
-
- /* 6. Stop capture. */
- for (const auto &session : sessions) {
- if (!session->options().isSet(OptCapture))
- continue;
-
- session->stop();
- }
-
- return 0;
-}
-
-std::string CamApp::cameraName(const Camera *camera)
-{
- const ControlList &props = camera->properties();
- bool addModel = true;
- std::string name;
-
- /*
- * Construct the name from the camera location, model and ID. The model
- * is only used if the location isn't present or is set to External.
- */
- const auto &location = props.get(properties::Location);
- if (location) {
- switch (*location) {
- case properties::CameraLocationFront:
- addModel = false;
- name = "Internal front camera ";
- break;
- case properties::CameraLocationBack:
- addModel = false;
- name = "Internal back camera ";
- break;
- case properties::CameraLocationExternal:
- name = "External camera ";
- break;
- }
- }
-
- if (addModel) {
- /*
- * If the camera location is not availble use the camera model
- * to build the camera name.
- */
- const auto &model = props.get(properties::Model);
- if (model)
- name = "'" + *model + "' ";
- }
-
- name += "(" + camera->id() + ")";
-
- return name;
-}
-
-void signalHandler([[maybe_unused]] int signal)
-{
- std::cout << "Exiting" << std::endl;
- CamApp::instance()->quit();
-}
-
-int main(int argc, char **argv)
-{
- CamApp app;
- int ret;
-
- ret = app.init(argc, argv);
- if (ret)
- return ret == -EINTR ? 0 : EXIT_FAILURE;
-
- struct sigaction sa = {};
- sa.sa_handler = &signalHandler;
- sigaction(SIGINT, &sa, nullptr);
-
- if (app.exec())
- return EXIT_FAILURE;
-
- return 0;
-}
diff --git a/src/cam/main.h b/src/cam/main.h
deleted file mode 100644
index 526aecec..00000000
--- a/src/cam/main.h
+++ /dev/null
@@ -1,26 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2019, Google Inc.
- *
- * main.h - Cam application
- */
-
-#pragma once
-
-enum {
- OptCamera = 'c',
- OptCapture = 'C',
- OptDisplay = 'D',
- OptFile = 'F',
- OptHelp = 'h',
- OptInfo = 'I',
- OptList = 'l',
- OptListProperties = 'p',
- OptMonitor = 'm',
- OptSDL = 'S',
- OptStream = 's',
- OptListControls = 256,
- OptStrictFormats = 257,
- OptMetadata = 258,
- OptCaptureScript = 259,
-};
diff --git a/src/cam/meson.build b/src/cam/meson.build
deleted file mode 100644
index 06dbea06..00000000
--- a/src/cam/meson.build
+++ /dev/null
@@ -1,74 +0,0 @@
-# SPDX-License-Identifier: CC0-1.0
-
-libevent = dependency('libevent_pthreads', required : get_option('cam'))
-
-if not libevent.found()
- cam_enabled = false
- subdir_done()
-endif
-
-cam_enabled = true
-
-cam_sources = files([
- 'camera_session.cpp',
- 'capture_script.cpp',
- 'event_loop.cpp',
- 'file_sink.cpp',
- 'frame_sink.cpp',
- 'image.cpp',
- 'main.cpp',
- 'options.cpp',
- 'stream_options.cpp',
-])
-
-cam_cpp_args = []
-
-libdrm = dependency('libdrm', required : false)
-libjpeg = dependency('libjpeg', required : false)
-libsdl2 = dependency('SDL2', required : false)
-libtiff = dependency('libtiff-4', required : false)
-
-if libdrm.found()
- cam_cpp_args += [ '-DHAVE_KMS' ]
- cam_sources += files([
- 'drm.cpp',
- 'kms_sink.cpp'
- ])
-endif
-
-if libsdl2.found()
- cam_cpp_args += ['-DHAVE_SDL']
- cam_sources += files([
- 'sdl_sink.cpp',
- 'sdl_texture.cpp',
- 'sdl_texture_yuv.cpp',
- ])
-
- if libjpeg.found()
- cam_cpp_args += ['-DHAVE_LIBJPEG']
- cam_sources += files([
- 'sdl_texture_mjpg.cpp'
- ])
- endif
-endif
-
-if libtiff.found()
- cam_cpp_args += ['-DHAVE_TIFF']
- cam_sources += files([
- 'dng_writer.cpp',
- ])
-endif
-
-cam = executable('cam', cam_sources,
- dependencies : [
- libatomic,
- libcamera_public,
- libdrm,
- libevent,
- libjpeg,
- libsdl2,
- libtiff,
- libyaml,
- ],
- cpp_args : cam_cpp_args,
- install : true)
diff --git a/src/cam/options.cpp b/src/cam/options.cpp
deleted file mode 100644
index 4f7e8691..00000000
--- a/src/cam/options.cpp
+++ /dev/null
@@ -1,1141 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2019, Google Inc.
- *
- * options.cpp - cam - Options parsing
- */
-
-#include <assert.h>
-#include <getopt.h>
-#include <iomanip>
-#include <iostream>
-#include <string.h>
-
-#include "options.h"
-
-/**
- * \enum OptionArgument
- * \brief Indicate if an option takes an argument
- *
- * \var OptionArgument::ArgumentNone
- * \brief The option doesn't accept any argument
- *
- * \var OptionArgument::ArgumentRequired
- * \brief The option requires an argument
- *
- * \var OptionArgument::ArgumentOptional
- * \brief The option accepts an optional argument
- */
-
-/**
- * \enum OptionType
- * \brief The type of argument for an option
- *
- * \var OptionType::OptionNone
- * \brief No argument type, used for options that take no argument
- *
- * \var OptionType::OptionInteger
- * \brief Integer argument type, with an optional base prefix (`0` for base 8,
- * `0x` for base 16, none for base 10)
- *
- * \var OptionType::OptionString
- * \brief String argument
- *
- * \var OptionType::OptionKeyValue
- * \brief key=value list argument
- */
-
-/* -----------------------------------------------------------------------------
- * Option
- */
-
-/**
- * \struct Option
- * \brief Store metadata about an option
- *
- * \var Option::opt
- * \brief The option identifier
- *
- * \var Option::type
- * \brief The type of the option argument
- *
- * \var Option::name
- * \brief The option name
- *
- * \var Option::argument
- * \brief Whether the option accepts an optional argument, a mandatory
- * argument, or no argument at all
- *
- * \var Option::argumentName
- * \brief The argument name used in the help text
- *
- * \var Option::help
- * \brief The help text (may be a multi-line string)
- *
- * \var Option::keyValueParser
- * \brief For options of type OptionType::OptionKeyValue, the key-value parser
- * to parse the argument
- *
- * \var Option::isArray
- * \brief Whether the option can appear once or multiple times
- *
- * \var Option::parent
- * \brief The parent option
- *
- * \var Option::children
- * \brief List of child options, storing all options whose parent is this option
- *
- * \fn Option::hasShortOption()
- * \brief Tell if the option has a short option specifier (e.g. `-f`)
- * \return True if the option has a short option specifier, false otherwise
- *
- * \fn Option::hasLongOption()
- * \brief Tell if the option has a long option specifier (e.g. `--foo`)
- * \return True if the option has a long option specifier, false otherwise
- */
-struct Option {
- int opt;
- OptionType type;
- const char *name;
- OptionArgument argument;
- const char *argumentName;
- const char *help;
- KeyValueParser *keyValueParser;
- bool isArray;
- Option *parent;
- std::list<Option> children;
-
- bool hasShortOption() const { return isalnum(opt); }
- bool hasLongOption() const { return name != nullptr; }
- const char *typeName() const;
- std::string optionName() const;
-};
-
-/**
- * \brief Retrieve a string describing the option type
- * \return A string describing the option type
- */
-const char *Option::typeName() const
-{
- switch (type) {
- case OptionNone:
- return "none";
-
- case OptionInteger:
- return "integer";
-
- case OptionString:
- return "string";
-
- case OptionKeyValue:
- return "key=value";
- }
-
- return "unknown";
-}
-
-/**
- * \brief Retrieve a string describing the option name, with leading dashes
- * \return A string describing the option name, as a long option identifier
- * (double dash) if the option has a name, or a short option identifier (single
- * dash) otherwise
- */
-std::string Option::optionName() const
-{
- if (name)
- return "--" + std::string(name);
- else
- return "-" + std::string(1, opt);
-}
-
-/* -----------------------------------------------------------------------------
- * OptionBase<T>
- */
-
-/**
- * \class template<typename T> OptionBase
- * \brief Container to store the values of parsed options
- * \tparam T The type through which options are identified
- *
- * The OptionsBase class is generated by a parser (either OptionsParser or
- * KeyValueParser) when parsing options. It stores values for all the options
- * found, and exposes accessor functions to retrieve them. The options are
- * accessed through an identifier to type \a T, which is an int referencing an
- * Option::opt for OptionsParser, or a std::string referencing an Option::name
- * for KeyValueParser.
- */
-
-/**
- * \fn OptionsBase::OptionsBase()
- * \brief Construct an OptionsBase instance
- *
- * The constructed instance is initially invalid, and will be populated by the
- * options parser.
- */
-
-/**
- * \brief Tell if the stored options list is empty
- * \return True if the container is empty, false otherwise
- */
-template<typename T>
-bool OptionsBase<T>::empty() const
-{
- return values_.empty();
-}
-
-/**
- * \brief Tell if the options parsing completed successfully
- * \return True if the container is returned after successfully parsing
- * options, false if it is returned after an error was detected during parsing
- */
-template<typename T>
-bool OptionsBase<T>::valid() const
-{
- return valid_;
-}
-
-/**
- * \brief Tell if the option \a opt is specified
- * \param[in] opt The option to search for
- * \return True if the \a opt option is set, false otherwise
- */
-template<typename T>
-bool OptionsBase<T>::isSet(const T &opt) const
-{
- return values_.find(opt) != values_.end();
-}
-
-/**
- * \brief Retrieve the value of option \a opt
- * \param[in] opt The option to retrieve
- * \return The value of option \a opt if found, an empty OptionValue otherwise
- */
-template<typename T>
-const OptionValue &OptionsBase<T>::operator[](const T &opt) const
-{
- static const OptionValue empty;
-
- auto it = values_.find(opt);
- if (it != values_.end())
- return it->second;
- return empty;
-}
-
-/**
- * \brief Mark the container as invalid
- *
- * This function can be used in a key-value parser's override of the
- * KeyValueParser::parse() function to mark the returned options as invalid if
- * a validation error occurs.
- */
-template<typename T>
-void OptionsBase<T>::invalidate()
-{
- valid_ = false;
-}
-
-template<typename T>
-bool OptionsBase<T>::parseValue(const T &opt, const Option &option,
- const char *arg)
-{
- OptionValue value;
-
- switch (option.type) {
- case OptionNone:
- break;
-
- case OptionInteger:
- unsigned int integer;
-
- if (arg) {
- char *endptr;
- integer = strtoul(arg, &endptr, 0);
- if (*endptr != '\0')
- return false;
- } else {
- integer = 0;
- }
-
- value = OptionValue(integer);
- break;
-
- case OptionString:
- value = OptionValue(arg ? arg : "");
- break;
-
- case OptionKeyValue:
- KeyValueParser *kvParser = option.keyValueParser;
- KeyValueParser::Options keyValues = kvParser->parse(arg);
- if (!keyValues.valid())
- return false;
-
- value = OptionValue(keyValues);
- break;
- }
-
- if (option.isArray)
- values_[opt].addValue(value);
- else
- values_[opt] = value;
-
- return true;
-}
-
-template class OptionsBase<int>;
-template class OptionsBase<std::string>;
-
-/* -----------------------------------------------------------------------------
- * KeyValueParser
- */
-
-/**
- * \class KeyValueParser
- * \brief A specialized parser for list of key-value pairs
- *
- * The KeyValueParser is an options parser for comma-separated lists of
- * `key=value` pairs. The supported keys are added to the parser with
- * addOption(). A given key can only appear once in the parsed list.
- *
- * Instances of this class can be passed to the OptionsParser::addOption()
- * function to create options that take key-value pairs as an option argument.
- * Specialized versions of the key-value parser can be created by inheriting
- * from this class, to pre-build the options list in the constructor, and to add
- * custom validation by overriding the parse() function.
- */
-
-/**
- * \class KeyValueParser::Options
- * \brief An option list generated by the key-value parser
- *
- * This is a specialization of OptionsBase with the option reference type set to
- * std::string.
- */
-
-KeyValueParser::KeyValueParser() = default;
-KeyValueParser::~KeyValueParser() = default;
-
-/**
- * \brief Add a supported option to the parser
- * \param[in] name The option name, corresponding to the key name in the
- * key=value pair. The name shall be unique.
- * \param[in] type The type of the value in the key=value pair
- * \param[in] help The help text
- * \param[in] argument Whether the value is optional, mandatory or not allowed.
- * Shall be ArgumentNone if \a type is OptionNone.
- *
- * \sa OptionsParser
- *
- * \return True if the option was added successfully, false if an error
- * occurred.
- */
-bool KeyValueParser::addOption(const char *name, OptionType type,
- const char *help, OptionArgument argument)
-{
- if (!name)
- return false;
- if (!help || help[0] == '\0')
- return false;
- if (argument != ArgumentNone && type == OptionNone)
- return false;
-
- /* Reject duplicate options. */
- if (optionsMap_.find(name) != optionsMap_.end())
- return false;
-
- optionsMap_[name] = Option({ 0, type, name, argument, nullptr,
- help, nullptr, false, nullptr, {} });
- return true;
-}
-
-/**
- * \brief Parse a string containing a list of key-value pairs
- * \param[in] arguments The key-value pairs string to parse
- *
- * If a parsing error occurs, the parsing stops and the function returns an
- * invalid container. The container is populated with the options successfully
- * parsed so far.
- *
- * \return A valid container with the list of parsed options on success, or an
- * invalid container otherwise
- */
-KeyValueParser::Options KeyValueParser::parse(const char *arguments)
-{
- Options options;
-
- for (const char *pair = arguments; *arguments != '\0'; pair = arguments) {
- const char *comma = strchrnul(arguments, ',');
- size_t len = comma - pair;
-
- /* Skip over the comma. */
- arguments = *comma == ',' ? comma + 1 : comma;
-
- /* Skip to the next pair if the pair is empty. */
- if (!len)
- continue;
-
- std::string key;
- std::string value;
-
- const char *separator = static_cast<const char *>(memchr(pair, '=', len));
- if (!separator) {
- key = std::string(pair, len);
- value = "";
- } else {
- key = std::string(pair, separator - pair);
- value = std::string(separator + 1, comma - separator - 1);
- }
-
- /* The key is mandatory, the value might be optional. */
- if (key.empty())
- continue;
-
- if (optionsMap_.find(key) == optionsMap_.end()) {
- std::cerr << "Invalid option " << key << std::endl;
- return options;
- }
-
- OptionArgument arg = optionsMap_[key].argument;
- if (value.empty() && arg == ArgumentRequired) {
- std::cerr << "Option " << key << " requires an argument"
- << std::endl;
- return options;
- } else if (!value.empty() && arg == ArgumentNone) {
- std::cerr << "Option " << key << " takes no argument"
- << std::endl;
- return options;
- }
-
- const Option &option = optionsMap_[key];
- if (!options.parseValue(key, option, value.c_str())) {
- std::cerr << "Failed to parse '" << value << "' as "
- << option.typeName() << " for option " << key
- << std::endl;
- return options;
- }
- }
-
- options.valid_ = true;
- return options;
-}
-
-unsigned int KeyValueParser::maxOptionLength() const
-{
- unsigned int maxLength = 0;
-
- for (auto const &iter : optionsMap_) {
- const Option &option = iter.second;
- unsigned int length = 10 + strlen(option.name);
- if (option.argument != ArgumentNone)
- length += 1 + strlen(option.typeName());
- if (option.argument == ArgumentOptional)
- length += 2;
-
- if (length > maxLength)
- maxLength = length;
- }
-
- return maxLength;
-}
-
-void KeyValueParser::usage(int indent)
-{
- for (auto const &iter : optionsMap_) {
- const Option &option = iter.second;
- std::string argument = std::string(" ") + option.name;
-
- if (option.argument != ArgumentNone) {
- if (option.argument == ArgumentOptional)
- argument += "[=";
- else
- argument += "=";
- argument += option.typeName();
- if (option.argument == ArgumentOptional)
- argument += "]";
- }
-
- std::cerr << std::setw(indent) << argument;
-
- for (const char *help = option.help, *end = help; end;) {
- end = strchr(help, '\n');
- if (end) {
- std::cerr << std::string(help, end - help + 1);
- std::cerr << std::setw(indent) << " ";
- help = end + 1;
- } else {
- std::cerr << help << std::endl;
- }
- }
- }
-}
-
-/* -----------------------------------------------------------------------------
- * OptionValue
- */
-
-/**
- * \class OptionValue
- * \brief Container to store the value of an option
- *
- * The OptionValue class is a variant-type container to store the value of an
- * option. It supports empty values, integers, strings, key-value lists, as well
- * as arrays of those types. For array values, all array elements shall have the
- * same type.
- *
- * OptionValue instances are organized in a tree-based structure that matches
- * the parent-child relationship of the options added to the parser. Children
- * are retrieved with the children() function, and are stored as an
- * OptionsBase<int>.
- */
-
-/**
- * \enum OptionValue::ValueType
- * \brief The option value type
- *
- * \var OptionValue::ValueType::ValueNone
- * \brief Empty value
- *
- * \var OptionValue::ValueType::ValueInteger
- * \brief Integer value (int)
- *
- * \var OptionValue::ValueType::ValueString
- * \brief String value (std::string)
- *
- * \var OptionValue::ValueType::ValueKeyValue
- * \brief Key-value list value (KeyValueParser::Options)
- *
- * \var OptionValue::ValueType::ValueArray
- * \brief Array value
- */
-
-/**
- * \brief Construct an empty OptionValue instance
- *
- * The value type is set to ValueType::ValueNone.
- */
-OptionValue::OptionValue()
- : type_(ValueNone), integer_(0)
-{
-}
-
-/**
- * \brief Construct an integer OptionValue instance
- * \param[in] value The integer value
- *
- * The value type is set to ValueType::ValueInteger.
- */
-OptionValue::OptionValue(int value)
- : type_(ValueInteger), integer_(value)
-{
-}
-
-/**
- * \brief Construct a string OptionValue instance
- * \param[in] value The string value
- *
- * The value type is set to ValueType::ValueString.
- */
-OptionValue::OptionValue(const char *value)
- : type_(ValueString), integer_(0), string_(value)
-{
-}
-
-/**
- * \brief Construct a string OptionValue instance
- * \param[in] value The string value
- *
- * The value type is set to ValueType::ValueString.
- */
-OptionValue::OptionValue(const std::string &value)
- : type_(ValueString), integer_(0), string_(value)
-{
-}
-
-/**
- * \brief Construct a key-value OptionValue instance
- * \param[in] value The key-value list
- *
- * The value type is set to ValueType::ValueKeyValue.
- */
-OptionValue::OptionValue(const KeyValueParser::Options &value)
- : type_(ValueKeyValue), integer_(0), keyValues_(value)
-{
-}
-
-/**
- * \brief Add an entry to an array value
- * \param[in] value The entry value
- *
- * This function can only be called if the OptionValue type is
- * ValueType::ValueNone or ValueType::ValueArray. Upon return, the type will be
- * set to ValueType::ValueArray.
- */
-void OptionValue::addValue(const OptionValue &value)
-{
- assert(type_ == ValueNone || type_ == ValueArray);
-
- type_ = ValueArray;
- array_.push_back(value);
-}
-
-/**
- * \fn OptionValue::type()
- * \brief Retrieve the value type
- * \return The value type
- */
-
-/**
- * \fn OptionValue::empty()
- * \brief Check if the value is empty
- * \return True if the value is empty (type set to ValueType::ValueNone), or
- * false otherwise
- */
-
-/**
- * \brief Cast the value to an int
- * \return The option value as an int, or 0 if the value type isn't
- * ValueType::ValueInteger
- */
-OptionValue::operator int() const
-{
- return toInteger();
-}
-
-/**
- * \brief Cast the value to a std::string
- * \return The option value as an std::string, or an empty string if the value
- * type isn't ValueType::ValueString
- */
-OptionValue::operator std::string() const
-{
- return toString();
-}
-
-/**
- * \brief Retrieve the value as an int
- * \return The option value as an int, or 0 if the value type isn't
- * ValueType::ValueInteger
- */
-int OptionValue::toInteger() const
-{
- if (type_ != ValueInteger)
- return 0;
-
- return integer_;
-}
-
-/**
- * \brief Retrieve the value as a std::string
- * \return The option value as a std::string, or an empty string if the value
- * type isn't ValueType::ValueString
- */
-std::string OptionValue::toString() const
-{
- if (type_ != ValueString)
- return std::string();
-
- return string_;
-}
-
-/**
- * \brief Retrieve the value as a key-value list
- *
- * The behaviour is undefined if the value type isn't ValueType::ValueKeyValue.
- *
- * \return The option value as a KeyValueParser::Options
- */
-const KeyValueParser::Options &OptionValue::toKeyValues() const
-{
- assert(type_ == ValueKeyValue);
- return keyValues_;
-}
-
-/**
- * \brief Retrieve the value as an array
- *
- * The behaviour is undefined if the value type isn't ValueType::ValueArray.
- *
- * \return The option value as a std::vector of OptionValue
- */
-const std::vector<OptionValue> &OptionValue::toArray() const
-{
- assert(type_ == ValueArray);
- return array_;
-}
-
-/**
- * \brief Retrieve the list of child values
- * \return The list of child values
- */
-const OptionsParser::Options &OptionValue::children() const
-{
- return children_;
-}
-
-/* -----------------------------------------------------------------------------
- * OptionsParser
- */
-
-/**
- * \class OptionsParser
- * \brief A command line options parser
- *
- * The OptionsParser class is an easy to use options parser for POSIX-style
- * command line options. Supports short (e.g. `-f`) and long (e.g. `--foo`)
- * options, optional and mandatory arguments, automatic parsing arguments for
- * integer types and comma-separated list of key=value pairs, and multi-value
- * arguments. It handles help text generation automatically.
- *
- * An OptionsParser instance is initialized by adding supported options with
- * addOption(). Options are specified by an identifier and a name. If the
- * identifier is an alphanumeric character, it will be used by the parser as a
- * short option identifier (e.g. `-f`). The name, if specified, will be used as
- * a long option identifier (e.g. `--foo`). It should not include the double
- * dashes. The name is optional if the option identifier is an alphanumeric
- * character and mandatory otherwise.
- *
- * An option has a mandatory help text, which is used to print the full options
- * list with the usage() function. The help text may be a multi-line string.
- * Correct indentation of the help text is handled automatically.
- *
- * Options accept arguments when created with OptionArgument::ArgumentRequired
- * or OptionArgument::ArgumentOptional. If the argument is required, it can be
- * specified as a positional argument after the option (e.g. `-f bar`,
- * `--foo bar`), collated with the short option (e.g. `-fbar`) or separated from
- * the long option by an equal sign (e.g. `--foo=bar`'). When the argument is
- * optional, it must be collated with the short option or separated from the
- * long option by an equal sign.
- *
- * If an option has a required or optional argument, an argument name must be
- * set when adding the option. The argument name is used in the help text as a
- * place holder for an argument value. For instance, a `--write` option that
- * takes a file name as an argument could set the argument name to `filename`,
- * and the help text would display `--write filename`. This is only used to
- * clarify the help text and has no effect on option parsing.
- *
- * The option type tells the parser how to process the argument. Arguments for
- * string options (OptionType::OptionString) are stored as-is without any
- * processing. Arguments for integer options (OptionType::OptionInteger) are
- * converted to an integer value, using an optional base prefix (`0` for base 8,
- * `0x` for base 16, none for base 10). Arguments for key-value options are
- * parsed by a KeyValueParser given to addOption().
- *
- * By default, a given option can appear once only in the parsed command line.
- * If the option is created as an array option, the parser will accept multiple
- * instances of the option. The order in which identical options are specified
- * is preserved in the values of an array option.
- *
- * After preparing the parser, it can be used any number of times to parse
- * command line options with the parse() function. The function returns an
- * Options instance that stores the values for the parsed options. The
- * Options::isSet() function can be used to test if an option has been found,
- * and is the only way to access options that take no argument (specified by
- * OptionType::OptionNone and OptionArgument::ArgumentNone). For options that
- * accept an argument, the option value can be access by Options::operator[]()
- * using the option identifier as the key. The order in which different options
- * are specified on the command line isn't preserved.
- *
- * Options can be created with parent-child relationships to organize them as a
- * tree instead of a flat list. When parsing a command line, the child options
- * are considered related to the parent option that precedes them. This is
- * useful when the parent is an array option. The Options values list generated
- * by the parser then turns into a tree, which each parent value storing the
- * values of child options that follow that instance of the parent option.
- * For instance, with a `capture` option specified as a child of a `camera`
- * array option, parsing the command line
- *
- * `--camera 1 --capture=10 --camera 2 --capture=20`
- *
- * will return an Options instance containing a single OptionValue instance of
- * array type, for the `camera` option. The OptionValue will contain two
- * entries, with the first entry containing the integer value 1 and the second
- * entry the integer value 2. Each of those entries will in turn store an
- * Options instance that contains the respective children. The first entry will
- * store in its children a `capture` option of value 10, and the second entry a
- * `capture` option of value 20.
- *
- * The command line
- *
- * `--capture=10 --camera 1`
- *
- * would result in a parsing error, as the `capture` option has no preceding
- * `camera` option on the command line.
- */
-
-/**
- * \class OptionsParser::Options
- * \brief An option list generated by the options parser
- *
- * This is a specialization of OptionsBase with the option reference type set to
- * int.
- */
-
-OptionsParser::OptionsParser() = default;
-OptionsParser::~OptionsParser() = default;
-
-/**
- * \brief Add an option to the parser
- * \param[in] opt The option identifier
- * \param[in] type The type of the option argument
- * \param[in] help The help text (may be a multi-line string)
- * \param[in] name The option name
- * \param[in] argument Whether the option accepts an optional argument, a
- * mandatory argument, or no argument at all
- * \param[in] argumentName The argument name used in the help text
- * \param[in] array Whether the option can appear once or multiple times
- * \param[in] parent The identifier of the parent option (optional)
- *
- * \return True if the option was added successfully, false if an error
- * occurred.
- */
-bool OptionsParser::addOption(int opt, OptionType type, const char *help,
- const char *name, OptionArgument argument,
- const char *argumentName, bool array, int parent)
-{
- /*
- * Options must have at least a short or long name, and a text message.
- * If an argument is accepted, it must be described by argumentName.
- */
- if (!isalnum(opt) && !name)
- return false;
- if (!help || help[0] == '\0')
- return false;
- if (argument != ArgumentNone && !argumentName)
- return false;
-
- /* Reject duplicate options. */
- if (optionsMap_.find(opt) != optionsMap_.end())
- return false;
-
- /*
- * If a parent is specified, create the option as a child of its parent.
- * Otherwise, create it in the parser's options list.
- */
- Option *option;
-
- if (parent) {
- auto iter = optionsMap_.find(parent);
- if (iter == optionsMap_.end())
- return false;
-
- Option *parentOpt = iter->second;
- parentOpt->children.push_back({
- opt, type, name, argument, argumentName, help, nullptr,
- array, parentOpt, {}
- });
- option = &parentOpt->children.back();
- } else {
- options_.push_back({ opt, type, name, argument, argumentName,
- help, nullptr, array, nullptr, {} });
- option = &options_.back();
- }
-
- optionsMap_[opt] = option;
-
- return true;
-}
-
-/**
- * \brief Add a key-value pair option to the parser
- * \param[in] opt The option identifier
- * \param[in] parser The KeyValueParser for the option value
- * \param[in] help The help text (may be a multi-line string)
- * \param[in] name The option name
- * \param[in] array Whether the option can appear once or multiple times
- *
- * \sa Option
- *
- * \return True if the option was added successfully, false if an error
- * occurred.
- */
-bool OptionsParser::addOption(int opt, KeyValueParser *parser, const char *help,
- const char *name, bool array, int parent)
-{
- if (!addOption(opt, OptionKeyValue, help, name, ArgumentRequired,
- "key=value[,key=value,...]", array, parent))
- return false;
-
- optionsMap_[opt]->keyValueParser = parser;
- return true;
-}
-
-/**
- * \brief Parse command line arguments
- * \param[in] argc The number of arguments in the \a argv array
- * \param[in] argv The array of arguments
- *
- * If a parsing error occurs, the parsing stops, the function prints an error
- * message that identifies the invalid argument, prints usage information with
- * usage(), and returns an invalid container. The container is populated with
- * the options successfully parsed so far.
- *
- * \return A valid container with the list of parsed options on success, or an
- * invalid container otherwise
- */
-OptionsParser::Options OptionsParser::parse(int argc, char **argv)
-{
- OptionsParser::Options options;
-
- /*
- * Allocate short and long options arrays large enough to contain all
- * options.
- */
- char shortOptions[optionsMap_.size() * 3 + 2];
- struct option longOptions[optionsMap_.size() + 1];
- unsigned int ids = 0;
- unsigned int idl = 0;
-
- shortOptions[ids++] = ':';
-
- for (const auto [opt, option] : optionsMap_) {
- if (option->hasShortOption()) {
- shortOptions[ids++] = opt;
- if (option->argument != ArgumentNone)
- shortOptions[ids++] = ':';
- if (option->argument == ArgumentOptional)
- shortOptions[ids++] = ':';
- }
-
- if (option->hasLongOption()) {
- longOptions[idl].name = option->name;
-
- switch (option->argument) {
- case ArgumentNone:
- longOptions[idl].has_arg = no_argument;
- break;
- case ArgumentRequired:
- longOptions[idl].has_arg = required_argument;
- break;
- case ArgumentOptional:
- longOptions[idl].has_arg = optional_argument;
- break;
- }
-
- longOptions[idl].flag = 0;
- longOptions[idl].val = option->opt;
- idl++;
- }
- }
-
- shortOptions[ids] = '\0';
- memset(&longOptions[idl], 0, sizeof(longOptions[idl]));
-
- opterr = 0;
-
- while (true) {
- int c = getopt_long(argc, argv, shortOptions, longOptions, nullptr);
-
- if (c == -1)
- break;
-
- if (c == '?' || c == ':') {
- if (c == '?')
- std::cerr << "Invalid option ";
- else
- std::cerr << "Missing argument for option ";
- std::cerr << argv[optind - 1] << std::endl;
-
- usage();
- return options;
- }
-
- const Option &option = *optionsMap_[c];
- if (!parseValue(option, optarg, &options)) {
- usage();
- return options;
- }
- }
-
- if (optind < argc) {
- std::cerr << "Invalid non-option argument '" << argv[optind]
- << "'" << std::endl;
- usage();
- return options;
- }
-
- options.valid_ = true;
- return options;
-}
-
-/**
- * \brief Print usage text to std::cerr
- *
- * The usage text list all the supported option with their arguments. It is
- * generated automatically from the options added to the parser. Caller of this
- * function may print additional usage information for the application before
- * the list of options.
- */
-void OptionsParser::usage()
-{
- unsigned int indent = 0;
-
- for (const auto &opt : optionsMap_) {
- const Option *option = opt.second;
- unsigned int length = 14;
- if (option->hasLongOption())
- length += 2 + strlen(option->name);
- if (option->argument != ArgumentNone)
- length += 1 + strlen(option->argumentName);
- if (option->argument == ArgumentOptional)
- length += 2;
- if (option->isArray)
- length += 4;
-
- if (length > indent)
- indent = length;
-
- if (option->keyValueParser) {
- length = option->keyValueParser->maxOptionLength();
- if (length > indent)
- indent = length;
- }
- }
-
- indent = (indent + 7) / 8 * 8;
-
- std::cerr << "Options:" << std::endl;
-
- std::ios_base::fmtflags f(std::cerr.flags());
- std::cerr << std::left;
-
- usageOptions(options_, indent);
-
- std::cerr.flags(f);
-}
-
-void OptionsParser::usageOptions(const std::list<Option> &options,
- unsigned int indent)
-{
- std::vector<const Option *> parentOptions;
-
- for (const Option &option : options) {
- std::string argument;
- if (option.hasShortOption())
- argument = std::string(" -")
- + static_cast<char>(option.opt);
- else
- argument = " ";
-
- if (option.hasLongOption()) {
- if (option.hasShortOption())
- argument += ", ";
- else
- argument += " ";
- argument += std::string("--") + option.name;
- }
-
- if (option.argument != ArgumentNone) {
- if (option.argument == ArgumentOptional)
- argument += "[=";
- else
- argument += " ";
- argument += option.argumentName;
- if (option.argument == ArgumentOptional)
- argument += "]";
- }
-
- if (option.isArray)
- argument += " ...";
-
- std::cerr << std::setw(indent) << argument;
-
- for (const char *help = option.help, *end = help; end; ) {
- end = strchr(help, '\n');
- if (end) {
- std::cerr << std::string(help, end - help + 1);
- std::cerr << std::setw(indent) << " ";
- help = end + 1;
- } else {
- std::cerr << help << std::endl;
- }
- }
-
- if (option.keyValueParser)
- option.keyValueParser->usage(indent);
-
- if (!option.children.empty())
- parentOptions.push_back(&option);
- }
-
- if (parentOptions.empty())
- return;
-
- for (const Option *option : parentOptions) {
- std::cerr << std::endl << "Options valid in the context of "
- << option->optionName() << ":" << std::endl;
- usageOptions(option->children, indent);
- }
-}
-
-std::tuple<OptionsParser::Options *, const Option *>
-OptionsParser::childOption(const Option *parent, Options *options)
-{
- /*
- * The parent argument points to the parent of the leaf node Option,
- * and the options argument to the root node of the Options tree. Use
- * recursive calls to traverse the Option tree up to the root node while
- * traversing the Options tree down to the leaf node:
- */
-
- /*
- * - If we have no parent, we've reached the root node of the Option
- * tree, the options argument is what we need.
- */
- if (!parent)
- return { options, nullptr };
-
- /*
- * - If the parent has a parent, use recursion to move one level up the
- * Option tree. This returns the Options corresponding to parent, or
- * nullptr if a suitable Options child isn't found.
- */
- if (parent->parent) {
- const Option *error;
- std::tie(options, error) = childOption(parent->parent, options);
-
- /* Propagate the error all the way back up the call stack. */
- if (!error)
- return { options, error };
- }
-
- /*
- * - The parent has no parent, we're now one level down the root.
- * Return the Options child corresponding to the parent. The child may
- * not exist if options are specified in an incorrect order.
- */
- if (!options->isSet(parent->opt))
- return { nullptr, parent };
-
- /*
- * If the child value is of array type, children are not stored in the
- * value .children() list, but in the .children() of the value's array
- * elements. Use the last array element in that case, as a child option
- * relates to the last instance of its parent option.
- */
- const OptionValue *value = &(*options)[parent->opt];
- if (value->type() == OptionValue::ValueArray)
- value = &value->toArray().back();
-
- return { const_cast<Options *>(&value->children()), nullptr };
-}
-
-bool OptionsParser::parseValue(const Option &option, const char *arg,
- Options *options)
-{
- const Option *error;
-
- std::tie(options, error) = childOption(option.parent, options);
- if (error) {
- std::cerr << "Option " << option.optionName() << " requires a "
- << error->optionName() << " context" << std::endl;
- return false;
- }
-
- if (!options->parseValue(option.opt, option, arg)) {
- std::cerr << "Can't parse " << option.typeName()
- << " argument for option " << option.optionName()
- << std::endl;
- return false;
- }
-
- return true;
-}
diff --git a/src/cam/options.h b/src/cam/options.h
deleted file mode 100644
index 4ddd4987..00000000
--- a/src/cam/options.h
+++ /dev/null
@@ -1,157 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2019, Google Inc.
- *
- * options.h - cam - Options parsing
- */
-
-#pragma once
-
-#include <ctype.h>
-#include <list>
-#include <map>
-#include <tuple>
-#include <vector>
-
-class KeyValueParser;
-class OptionValue;
-struct Option;
-
-enum OptionArgument {
- ArgumentNone,
- ArgumentRequired,
- ArgumentOptional,
-};
-
-enum OptionType {
- OptionNone,
- OptionInteger,
- OptionString,
- OptionKeyValue,
-};
-
-template<typename T>
-class OptionsBase
-{
-public:
- OptionsBase() : valid_(false) {}
-
- bool empty() const;
- bool valid() const;
- bool isSet(const T &opt) const;
- const OptionValue &operator[](const T &opt) const;
-
- void invalidate();
-
-private:
- friend class KeyValueParser;
- friend class OptionsParser;
-
- bool parseValue(const T &opt, const Option &option, const char *value);
-
- std::map<T, OptionValue> values_;
- bool valid_;
-};
-
-class KeyValueParser
-{
-public:
- class Options : public OptionsBase<std::string>
- {
- };
-
- KeyValueParser();
- virtual ~KeyValueParser();
-
- bool addOption(const char *name, OptionType type, const char *help,
- OptionArgument argument = ArgumentNone);
-
- virtual Options parse(const char *arguments);
-
-private:
- KeyValueParser(const KeyValueParser &) = delete;
- KeyValueParser &operator=(const KeyValueParser &) = delete;
-
- friend class OptionsParser;
- unsigned int maxOptionLength() const;
- void usage(int indent);
-
- std::map<std::string, Option> optionsMap_;
-};
-
-class OptionsParser
-{
-public:
- class Options : public OptionsBase<int>
- {
- };
-
- OptionsParser();
- ~OptionsParser();
-
- bool addOption(int opt, OptionType type, const char *help,
- const char *name = nullptr,
- OptionArgument argument = ArgumentNone,
- const char *argumentName = nullptr, bool array = false,
- int parent = 0);
- bool addOption(int opt, KeyValueParser *parser, const char *help,
- const char *name = nullptr, bool array = false,
- int parent = 0);
-
- Options parse(int argc, char *argv[]);
- void usage();
-
-private:
- OptionsParser(const OptionsParser &) = delete;
- OptionsParser &operator=(const OptionsParser &) = delete;
-
- void usageOptions(const std::list<Option> &options, unsigned int indent);
-
- std::tuple<OptionsParser::Options *, const Option *>
- childOption(const Option *parent, Options *options);
- bool parseValue(const Option &option, const char *arg, Options *options);
-
- std::list<Option> options_;
- std::map<unsigned int, Option *> optionsMap_;
-};
-
-class OptionValue
-{
-public:
- enum ValueType {
- ValueNone,
- ValueInteger,
- ValueString,
- ValueKeyValue,
- ValueArray,
- };
-
- OptionValue();
- OptionValue(int value);
- OptionValue(const char *value);
- OptionValue(const std::string &value);
- OptionValue(const KeyValueParser::Options &value);
-
- void addValue(const OptionValue &value);
-
- ValueType type() const { return type_; }
- bool empty() const { return type_ == ValueType::ValueNone; }
-
- operator int() const;
- operator std::string() const;
-
- int toInteger() const;
- std::string toString() const;
- const KeyValueParser::Options &toKeyValues() const;
- const std::vector<OptionValue> &toArray() const;
-
- const OptionsParser::Options &children() const;
-
-private:
- ValueType type_;
- int integer_;
- std::string string_;
- KeyValueParser::Options keyValues_;
- std::vector<OptionValue> array_;
- OptionsParser::Options children_;
-};
diff --git a/src/cam/sdl_sink.cpp b/src/cam/sdl_sink.cpp
deleted file mode 100644
index ee177227..00000000
--- a/src/cam/sdl_sink.cpp
+++ /dev/null
@@ -1,214 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2022, Ideas on Board Oy
- *
- * sdl_sink.h - SDL Sink
- */
-
-#include "sdl_sink.h"
-
-#include <assert.h>
-#include <fcntl.h>
-#include <iomanip>
-#include <iostream>
-#include <signal.h>
-#include <sstream>
-#include <string.h>
-#include <unistd.h>
-
-#include <libcamera/camera.h>
-#include <libcamera/formats.h>
-
-#include "event_loop.h"
-#include "image.h"
-#ifdef HAVE_LIBJPEG
-#include "sdl_texture_mjpg.h"
-#endif
-#include "sdl_texture_yuv.h"
-
-using namespace libcamera;
-
-using namespace std::chrono_literals;
-
-SDLSink::SDLSink()
- : window_(nullptr), renderer_(nullptr), rect_({}),
- init_(false)
-{
-}
-
-SDLSink::~SDLSink()
-{
- stop();
-}
-
-int SDLSink::configure(const libcamera::CameraConfiguration &config)
-{
- int ret = FrameSink::configure(config);
- if (ret < 0)
- return ret;
-
- if (config.size() > 1) {
- std::cerr
- << "SDL sink only supports one camera stream at present, streaming first camera stream"
- << std::endl;
- } else if (config.empty()) {
- std::cerr << "Require at least one camera stream to process"
- << std::endl;
- return -EINVAL;
- }
-
- const libcamera::StreamConfiguration &cfg = config.at(0);
- rect_.w = cfg.size.width;
- rect_.h = cfg.size.height;
-
- switch (cfg.pixelFormat) {
-#ifdef HAVE_LIBJPEG
- case libcamera::formats::MJPEG:
- texture_ = std::make_unique<SDLTextureMJPG>(rect_);
- break;
-#endif
-#if SDL_VERSION_ATLEAST(2, 0, 16)
- case libcamera::formats::NV12:
- texture_ = std::make_unique<SDLTextureNV12>(rect_, cfg.stride);
- break;
-#endif
- case libcamera::formats::YUYV:
- texture_ = std::make_unique<SDLTextureYUYV>(rect_, cfg.stride);
- break;
- default:
- std::cerr << "Unsupported pixel format "
- << cfg.pixelFormat.toString() << std::endl;
- return -EINVAL;
- };
-
- return 0;
-}
-
-int SDLSink::start()
-{
- int ret = SDL_Init(SDL_INIT_VIDEO);
- if (ret) {
- std::cerr << "Failed to initialize SDL: " << SDL_GetError()
- << std::endl;
- return ret;
- }
-
- init_ = true;
- window_ = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED,
- SDL_WINDOWPOS_UNDEFINED, rect_.w,
- rect_.h,
- SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
- if (!window_) {
- std::cerr << "Failed to create SDL window: " << SDL_GetError()
- << std::endl;
- return -EINVAL;
- }
-
- renderer_ = SDL_CreateRenderer(window_, -1, 0);
- if (!renderer_) {
- std::cerr << "Failed to create SDL renderer: " << SDL_GetError()
- << std::endl;
- return -EINVAL;
- }
-
- /*
- * Set for scaling purposes, not critical, don't return in case of
- * error.
- */
- ret = SDL_RenderSetLogicalSize(renderer_, rect_.w, rect_.h);
- if (ret)
- std::cerr << "Failed to set SDL render logical size: "
- << SDL_GetError() << std::endl;
-
- ret = texture_->create(renderer_);
- if (ret) {
- return ret;
- }
-
- /* \todo Make the event cancellable to support stop/start cycles. */
- EventLoop::instance()->addTimerEvent(
- 10ms, std::bind(&SDLSink::processSDLEvents, this));
-
- return 0;
-}
-
-int SDLSink::stop()
-{
- texture_.reset();
-
- if (renderer_) {
- SDL_DestroyRenderer(renderer_);
- renderer_ = nullptr;
- }
-
- if (window_) {
- SDL_DestroyWindow(window_);
- window_ = nullptr;
- }
-
- if (init_) {
- SDL_Quit();
- init_ = false;
- }
-
- return FrameSink::stop();
-}
-
-void SDLSink::mapBuffer(FrameBuffer *buffer)
-{
- std::unique_ptr<Image> image =
- Image::fromFrameBuffer(buffer, Image::MapMode::ReadOnly);
- assert(image != nullptr);
-
- mappedBuffers_[buffer] = std::move(image);
-}
-
-bool SDLSink::processRequest(Request *request)
-{
- for (auto [stream, buffer] : request->buffers()) {
- renderBuffer(buffer);
- break; /* to be expanded to launch SDL window per buffer */
- }
-
- return true;
-}
-
-/*
- * Process SDL events, required for things like window resize and quit button
- */
-void SDLSink::processSDLEvents()
-{
- for (SDL_Event e; SDL_PollEvent(&e);) {
- if (e.type == SDL_QUIT) {
- /* Click close icon then quit */
- EventLoop::instance()->exit(0);
- }
- }
-}
-
-void SDLSink::renderBuffer(FrameBuffer *buffer)
-{
- Image *image = mappedBuffers_[buffer].get();
-
- std::vector<Span<const uint8_t>> planes;
- unsigned int i = 0;
-
- planes.reserve(buffer->metadata().planes().size());
-
- for (const FrameMetadata::Plane &meta : buffer->metadata().planes()) {
- Span<uint8_t> data = image->data(i);
- if (meta.bytesused > data.size())
- std::cerr << "payload size " << meta.bytesused
- << " larger than plane size " << data.size()
- << std::endl;
-
- planes.push_back(data);
- i++;
- }
-
- texture_->update(planes);
-
- SDL_RenderClear(renderer_);
- SDL_RenderCopy(renderer_, texture_->get(), nullptr, nullptr);
- SDL_RenderPresent(renderer_);
-}
diff --git a/src/cam/sdl_sink.h b/src/cam/sdl_sink.h
deleted file mode 100644
index 6c19c663..00000000
--- a/src/cam/sdl_sink.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2022, Ideas on Board Oy
- *
- * sdl_sink.h - SDL Sink
- */
-
-#pragma once
-
-#include <map>
-#include <memory>
-
-#include <libcamera/stream.h>
-
-#include <SDL2/SDL.h>
-
-#include "frame_sink.h"
-
-class Image;
-class SDLTexture;
-
-class SDLSink : public FrameSink
-{
-public:
- SDLSink();
- ~SDLSink();
-
- int configure(const libcamera::CameraConfiguration &config) override;
- int start() override;
- int stop() override;
- void mapBuffer(libcamera::FrameBuffer *buffer) override;
-
- bool processRequest(libcamera::Request *request) override;
-
-private:
- void renderBuffer(libcamera::FrameBuffer *buffer);
- void processSDLEvents();
-
- std::map<libcamera::FrameBuffer *, std::unique_ptr<Image>>
- mappedBuffers_;
-
- std::unique_ptr<SDLTexture> texture_;
-
- SDL_Window *window_;
- SDL_Renderer *renderer_;
- SDL_Rect rect_;
- bool init_;
-};
diff --git a/src/cam/sdl_texture.cpp b/src/cam/sdl_texture.cpp
deleted file mode 100644
index e9040bc5..00000000
--- a/src/cam/sdl_texture.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2022, Ideas on Board Oy
- *
- * sdl_texture.cpp - SDL Texture
- */
-
-#include "sdl_texture.h"
-
-#include <iostream>
-
-SDLTexture::SDLTexture(const SDL_Rect &rect, uint32_t pixelFormat,
- const int stride)
- : ptr_(nullptr), rect_(rect), pixelFormat_(pixelFormat), stride_(stride)
-{
-}
-
-SDLTexture::~SDLTexture()
-{
- if (ptr_)
- SDL_DestroyTexture(ptr_);
-}
-
-int SDLTexture::create(SDL_Renderer *renderer)
-{
- ptr_ = SDL_CreateTexture(renderer, pixelFormat_,
- SDL_TEXTUREACCESS_STREAMING, rect_.w,
- rect_.h);
- if (!ptr_) {
- std::cerr << "Failed to create SDL texture: " << SDL_GetError()
- << std::endl;
- return -ENOMEM;
- }
-
- return 0;
-}
diff --git a/src/cam/sdl_texture.h b/src/cam/sdl_texture.h
deleted file mode 100644
index 6ccd85ea..00000000
--- a/src/cam/sdl_texture.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2022, Ideas on Board Oy
- *
- * sdl_texture.h - SDL Texture
- */
-
-#pragma once
-
-#include <vector>
-
-#include <SDL2/SDL.h>
-
-#include "image.h"
-
-class SDLTexture
-{
-public:
- SDLTexture(const SDL_Rect &rect, uint32_t pixelFormat, const int stride);
- virtual ~SDLTexture();
- int create(SDL_Renderer *renderer);
- virtual void update(const std::vector<libcamera::Span<const uint8_t>> &data) = 0;
- SDL_Texture *get() const { return ptr_; }
-
-protected:
- SDL_Texture *ptr_;
- const SDL_Rect rect_;
- const uint32_t pixelFormat_;
- const int stride_;
-};
diff --git a/src/cam/sdl_texture_mjpg.cpp b/src/cam/sdl_texture_mjpg.cpp
deleted file mode 100644
index da958e03..00000000
--- a/src/cam/sdl_texture_mjpg.cpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2022, Ideas on Board Oy
- *
- * sdl_texture_mjpg.cpp - SDL Texture MJPG
- */
-
-#include "sdl_texture_mjpg.h"
-
-#include <iostream>
-#include <setjmp.h>
-#include <stdio.h>
-
-#include <jpeglib.h>
-
-using namespace libcamera;
-
-struct JpegErrorManager : public jpeg_error_mgr {
- JpegErrorManager()
- {
- jpeg_std_error(this);
- error_exit = errorExit;
- output_message = outputMessage;
- }
-
- static void errorExit(j_common_ptr cinfo)
- {
- JpegErrorManager *self =
- static_cast<JpegErrorManager *>(cinfo->err);
- longjmp(self->escape_, 1);
- }
-
- static void outputMessage([[maybe_unused]] j_common_ptr cinfo)
- {
- }
-
- jmp_buf escape_;
-};
-
-SDLTextureMJPG::SDLTextureMJPG(const SDL_Rect &rect)
- : SDLTexture(rect, SDL_PIXELFORMAT_RGB24, rect.w * 3),
- rgb_(std::make_unique<unsigned char[]>(stride_ * rect.h))
-{
-}
-
-int SDLTextureMJPG::decompress(Span<const uint8_t> data)
-{
- struct jpeg_decompress_struct cinfo;
-
- JpegErrorManager errorManager;
- if (setjmp(errorManager.escape_)) {
- /* libjpeg found an error */
- jpeg_destroy_decompress(&cinfo);
- std::cerr << "JPEG decompression error" << std::endl;
- return -EINVAL;
- }
-
- cinfo.err = &errorManager;
- jpeg_create_decompress(&cinfo);
-
- jpeg_mem_src(&cinfo, data.data(), data.size());
-
- jpeg_read_header(&cinfo, TRUE);
-
- jpeg_start_decompress(&cinfo);
-
- for (int i = 0; cinfo.output_scanline < cinfo.output_height; ++i) {
- JSAMPROW rowptr = rgb_.get() + i * stride_;
- jpeg_read_scanlines(&cinfo, &rowptr, 1);
- }
-
- jpeg_finish_decompress(&cinfo);
-
- jpeg_destroy_decompress(&cinfo);
-
- return 0;
-}
-
-void SDLTextureMJPG::update(const std::vector<libcamera::Span<const uint8_t>> &data)
-{
- decompress(data[0]);
- SDL_UpdateTexture(ptr_, nullptr, rgb_.get(), stride_);
-}
diff --git a/src/cam/sdl_texture_mjpg.h b/src/cam/sdl_texture_mjpg.h
deleted file mode 100644
index 814ca79a..00000000
--- a/src/cam/sdl_texture_mjpg.h
+++ /dev/null
@@ -1,23 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2022, Ideas on Board Oy
- *
- * sdl_texture_mjpg.h - SDL Texture MJPG
- */
-
-#pragma once
-
-#include "sdl_texture.h"
-
-class SDLTextureMJPG : public SDLTexture
-{
-public:
- SDLTextureMJPG(const SDL_Rect &rect);
-
- void update(const std::vector<libcamera::Span<const uint8_t>> &data) override;
-
-private:
- int decompress(libcamera::Span<const uint8_t> data);
-
- std::unique_ptr<unsigned char[]> rgb_;
-};
diff --git a/src/cam/sdl_texture_yuv.cpp b/src/cam/sdl_texture_yuv.cpp
deleted file mode 100644
index b29c3b93..00000000
--- a/src/cam/sdl_texture_yuv.cpp
+++ /dev/null
@@ -1,33 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2022, Ideas on Board Oy
- *
- * sdl_texture_yuv.cpp - SDL YUV Textures
- */
-
-#include "sdl_texture_yuv.h"
-
-using namespace libcamera;
-
-#if SDL_VERSION_ATLEAST(2, 0, 16)
-SDLTextureNV12::SDLTextureNV12(const SDL_Rect &rect, unsigned int stride)
- : SDLTexture(rect, SDL_PIXELFORMAT_NV12, stride)
-{
-}
-
-void SDLTextureNV12::update(const std::vector<libcamera::Span<const uint8_t>> &data)
-{
- SDL_UpdateNVTexture(ptr_, &rect_, data[0].data(), stride_,
- data[1].data(), stride_);
-}
-#endif
-
-SDLTextureYUYV::SDLTextureYUYV(const SDL_Rect &rect, unsigned int stride)
- : SDLTexture(rect, SDL_PIXELFORMAT_YUY2, stride)
-{
-}
-
-void SDLTextureYUYV::update(const std::vector<libcamera::Span<const uint8_t>> &data)
-{
- SDL_UpdateTexture(ptr_, &rect_, data[0].data(), stride_);
-}
diff --git a/src/cam/sdl_texture_yuv.h b/src/cam/sdl_texture_yuv.h
deleted file mode 100644
index 310e4e50..00000000
--- a/src/cam/sdl_texture_yuv.h
+++ /dev/null
@@ -1,26 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2022, Ideas on Board Oy
- *
- * sdl_texture_yuv.h - SDL YUV Textures
- */
-
-#pragma once
-
-#include "sdl_texture.h"
-
-#if SDL_VERSION_ATLEAST(2, 0, 16)
-class SDLTextureNV12 : public SDLTexture
-{
-public:
- SDLTextureNV12(const SDL_Rect &rect, unsigned int stride);
- void update(const std::vector<libcamera::Span<const uint8_t>> &data) override;
-};
-#endif
-
-class SDLTextureYUYV : public SDLTexture
-{
-public:
- SDLTextureYUYV(const SDL_Rect &rect, unsigned int stride);
- void update(const std::vector<libcamera::Span<const uint8_t>> &data) override;
-};
diff --git a/src/cam/stream_options.cpp b/src/cam/stream_options.cpp
deleted file mode 100644
index 3a5625f5..00000000
--- a/src/cam/stream_options.cpp
+++ /dev/null
@@ -1,134 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2020, Raspberry Pi Ltd
- *
- * stream_options.cpp - Helper to parse options for streams
- */
-#include "stream_options.h"
-
-#include <iostream>
-
-#include <libcamera/color_space.h>
-
-using namespace libcamera;
-
-StreamKeyValueParser::StreamKeyValueParser()
-{
- addOption("role", OptionString,
- "Role for the stream (viewfinder, video, still, raw)",
- ArgumentRequired);
- addOption("width", OptionInteger, "Width in pixels",
- ArgumentRequired);
- addOption("height", OptionInteger, "Height in pixels",
- ArgumentRequired);
- addOption("pixelformat", OptionString, "Pixel format name",
- ArgumentRequired);
- addOption("colorspace", OptionString, "Color space",
- ArgumentRequired);
-}
-
-KeyValueParser::Options StreamKeyValueParser::parse(const char *arguments)
-{
- KeyValueParser::Options options = KeyValueParser::parse(arguments);
- StreamRole role;
-
- if (options.valid() && options.isSet("role") &&
- !parseRole(&role, options)) {
- std::cerr << "Unknown stream role "
- << options["role"].toString() << std::endl;
- options.invalidate();
- }
-
- return options;
-}
-
-StreamRoles StreamKeyValueParser::roles(const OptionValue &values)
-{
- /* If no configuration values to examine default to viewfinder. */
- if (values.empty())
- return { StreamRole::Viewfinder };
-
- const std::vector<OptionValue> &streamParameters = values.toArray();
-
- StreamRoles roles;
- for (auto const &value : streamParameters) {
- StreamRole role;
-
- /* If role is invalid or not set default to viewfinder. */
- if (!parseRole(&role, value.toKeyValues()))
- role = StreamRole::Viewfinder;
-
- roles.push_back(role);
- }
-
- return roles;
-}
-
-int StreamKeyValueParser::updateConfiguration(CameraConfiguration *config,
- const OptionValue &values)
-{
- if (!config) {
- std::cerr << "No configuration provided" << std::endl;
- return -EINVAL;
- }
-
- /* If no configuration values nothing to do. */
- if (values.empty())
- return 0;
-
- const std::vector<OptionValue> &streamParameters = values.toArray();
-
- if (config->size() != streamParameters.size()) {
- std::cerr
- << "Number of streams in configuration "
- << config->size()
- << " does not match number of streams parsed "
- << streamParameters.size()
- << std::endl;
- return -EINVAL;
- }
-
- unsigned int i = 0;
- for (auto const &value : streamParameters) {
- KeyValueParser::Options opts = value.toKeyValues();
- StreamConfiguration &cfg = config->at(i++);
-
- if (opts.isSet("width") && opts.isSet("height")) {
- cfg.size.width = opts["width"];
- cfg.size.height = opts["height"];
- }
-
- if (opts.isSet("pixelformat"))
- cfg.pixelFormat = PixelFormat::fromString(opts["pixelformat"].toString());
-
- if (opts.isSet("colorspace"))
- cfg.colorSpace = ColorSpace::fromString(opts["colorspace"].toString());
- }
-
- return 0;
-}
-
-bool StreamKeyValueParser::parseRole(StreamRole *role,
- const KeyValueParser::Options &options)
-{
- if (!options.isSet("role"))
- return false;
-
- std::string name = options["role"].toString();
-
- if (name == "viewfinder") {
- *role = StreamRole::Viewfinder;
- return true;
- } else if (name == "video") {
- *role = StreamRole::VideoRecording;
- return true;
- } else if (name == "still") {
- *role = StreamRole::StillCapture;
- return true;
- } else if (name == "raw") {
- *role = StreamRole::Raw;
- return true;
- }
-
- return false;
-}
diff --git a/src/cam/stream_options.h b/src/cam/stream_options.h
deleted file mode 100644
index 35e4e7c0..00000000
--- a/src/cam/stream_options.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2020, Raspberry Pi Ltd
- *
- * stream_options.h - Helper to parse options for streams
- */
-
-#pragma once
-
-#include <libcamera/camera.h>
-
-#include "options.h"
-
-class StreamKeyValueParser : public KeyValueParser
-{
-public:
- StreamKeyValueParser();
-
- KeyValueParser::Options parse(const char *arguments) override;
-
- static libcamera::StreamRoles roles(const OptionValue &values);
- static int updateConfiguration(libcamera::CameraConfiguration *config,
- const OptionValue &values);
-
-private:
- static bool parseRole(libcamera::StreamRole *role,
- const KeyValueParser::Options &options);
-};