diff options
author | Laurent Pinchart <laurent.pinchart@ideasonboard.com> | 2022-10-20 00:44:55 +0300 |
---|---|---|
committer | Laurent Pinchart <laurent.pinchart@ideasonboard.com> | 2022-10-20 13:36:25 +0300 |
commit | 84ad104499d9efc0253dae1a60ee070ed375ad95 (patch) | |
tree | d10fd53eb79cebb28fa3f72b18b46dddb6382b83 /src/cam | |
parent | daf3f4b59f4ea0ecb42c6a39fe909f071d3a2842 (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')
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); -}; |