| /* 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" |
| "The default file name is 'frame-#.bin'.", |
| "file", ArgumentOptional, "filename", false, |
| OptCamera); |
| 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); |
| |
| 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. |
| */ |
| if (props.contains(properties::Location)) { |
| switch (props.get(properties::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 && props.contains(properties::Model)) { |
| /* |
| * If the camera location is not availble use the camera model |
| * to build the camera name. |
| */ |
| name = "'" + props.get(properties::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; |
| } |