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/main.cpp | 362 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 362 insertions(+) create mode 100644 src/apps/cam/main.cpp (limited to 'src/apps/cam/main.cpp') diff --git a/src/apps/cam/main.cpp b/src/apps/cam/main.cpp new file mode 100644 index 00000000..d70130e2 --- /dev/null +++ b/src/apps/cam/main.cpp @@ -0,0 +1,362 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * main.cpp - cam - The libcamera swiss army knife + */ + +#include +#include +#include +#include +#include + +#include +#include + +#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 cam); + void cameraRemoved(std::shared_ptr 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 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(); + + 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 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 cam) +{ + std::cout << "Camera Added: " << cam->id() << std::endl; +} + +void CamApp::cameraRemoved(std::shared_ptr 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 &cam : cm_->cameras()) { + std::cout << index << ": " << cameraName(cam.get()) << std::endl; + index++; + } + } + + /* 2. Create the camera sessions. */ + std::vector> sessions; + + if (options_.isSet(OptCamera)) { + unsigned int index = 0; + + for (const OptionValue &camera : options_[OptCamera].toArray()) { + std::unique_ptr session = + std::make_unique(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; +} -- cgit v1.2.1