From 84ad104499d9efc0253dae1a60ee070ed375ad95 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Thu, 20 Oct 2022 00:44:55 +0300 Subject: 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 Reviewed-by: Paul Elder Reviewed-by: Kieran Bingham --- src/apps/cam/camera_session.cpp | 450 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 450 insertions(+) create mode 100644 src/apps/cam/camera_session.cpp (limited to 'src/apps/cam/camera_session.cpp') diff --git a/src/apps/cam/camera_session.cpp b/src/apps/cam/camera_session.cpp new file mode 100644 index 00000000..6b409c98 --- /dev/null +++ b/src/apps/cam/camera_session.cpp @@ -0,0 +1,450 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * camera_session.cpp - Camera capture session + */ + +#include +#include +#include +#include + +#include +#include + +#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 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(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(options_[OptDisplay].toString()); +#endif + +#ifdef HAVE_SDL + if (options_.isSet(OptSDL)) + sink_ = std::make_unique(); +#endif + + if (options_.isSet(OptFile)) { + if (!options_[OptFile].toString().empty()) + sink_ = std::make_unique(camera_.get(), streamNames_, + options_[OptFile]); + else + sink_ = std::make_unique(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(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 = 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> &buffers = + allocator_->buffers(stream); + const std::unique_ptr &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 : 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); +} -- cgit v1.2.1