diff options
author | Niklas Söderlund <niklas.soderlund@ragnatech.se> | 2019-05-23 01:45:09 +0200 |
---|---|---|
committer | Niklas Söderlund <niklas.soderlund@ragnatech.se> | 2019-05-25 14:27:55 +0200 |
commit | e741a5482c3e5a8c5468d030a7b5ba2f07201577 (patch) | |
tree | c459ed89a8f8f9987ac4b91bca010e847bb463b2 | |
parent | 9342c606495a2bc5485b69c51f16d5730bb0386c (diff) |
cam: capture: Break out capture to a new class
Reduce the complexity of main.cpp by compartmentalising the capture
logic into its own class. There is no functional change.
Signed-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
-rw-r--r-- | src/cam/capture.cpp | 248 | ||||
-rw-r--r-- | src/cam/capture.h | 42 | ||||
-rw-r--r-- | src/cam/main.cpp | 236 | ||||
-rw-r--r-- | src/cam/main.h | 19 | ||||
-rw-r--r-- | src/cam/meson.build | 1 |
5 files changed, 314 insertions, 232 deletions
diff --git a/src/cam/capture.cpp b/src/cam/capture.cpp new file mode 100644 index 00000000..a4aa44af --- /dev/null +++ b/src/cam/capture.cpp @@ -0,0 +1,248 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * capture.cpp - Cam capture + */ + +#include <climits> +#include <iomanip> +#include <iostream> +#include <sstream> + +#include "capture.h" +#include "main.h" + +using namespace libcamera; + +Capture::Capture(Camera *camera) + : camera_(camera), writer_(nullptr), last_(0) +{ +} + +int Capture::run(EventLoop *loop, const OptionsParser::Options &options) +{ + int ret; + + if (!camera_) { + std::cout << "Can't capture without a camera" << std::endl; + return -ENODEV; + } + + ret = prepareConfig(options); + if (ret) { + std::cout << "Failed to prepare camera configuration" << std::endl; + return -EINVAL; + } + + ret = camera_->configure(config_.get()); + if (ret < 0) { + std::cout << "Failed to configure camera" << std::endl; + return ret; + } + + ret = camera_->allocateBuffers(); + if (ret) { + std::cerr << "Failed to allocate buffers" << std::endl; + return ret; + } + + camera_->requestCompleted.connect(this, &Capture::requestComplete); + + if (options.isSet(OptFile)) { + if (!options[OptFile].toString().empty()) + writer_ = new BufferWriter(options[OptFile]); + else + writer_ = new BufferWriter(); + } + + ret = capture(loop); + + if (options.isSet(OptFile)) { + delete writer_; + writer_ = nullptr; + } + + camera_->freeBuffers(); + config_.reset(); + + return ret; +} + +int Capture::prepareConfig(const OptionsParser::Options &options) +{ + StreamRoles roles; + + if (options.isSet(OptStream)) { + const std::vector<OptionValue> &streamOptions = + options[OptStream].toArray(); + + /* Use roles and get a default configuration. */ + for (auto const &value : streamOptions) { + KeyValueParser::Options opt = value.toKeyValues(); + + if (!opt.isSet("role")) { + roles.push_back(StreamRole::VideoRecording); + } else if (opt["role"].toString() == "viewfinder") { + roles.push_back(StreamRole::Viewfinder); + } else if (opt["role"].toString() == "video") { + roles.push_back(StreamRole::VideoRecording); + } else if (opt["role"].toString() == "still") { + roles.push_back(StreamRole::StillCapture); + } else { + std::cerr << "Unknown stream role " + << opt["role"].toString() << std::endl; + return -EINVAL; + } + } + } else { + /* If no configuration is provided assume a single video stream. */ + roles.push_back(StreamRole::VideoRecording); + } + + config_ = camera_->generateConfiguration(roles); + if (!config_ || config_->size() != roles.size()) { + std::cerr << "Failed to get default stream configuration" + << std::endl; + return -EINVAL; + } + + /* Apply configuration if explicitly requested. */ + if (options.isSet(OptStream)) { + const std::vector<OptionValue> &streamOptions = + options[OptStream].toArray(); + + unsigned int i = 0; + for (auto const &value : streamOptions) { + KeyValueParser::Options opt = value.toKeyValues(); + StreamConfiguration &cfg = config_->at(i++); + + if (opt.isSet("width")) + cfg.size.width = opt["width"]; + + if (opt.isSet("height")) + cfg.size.height = opt["height"]; + + /* TODO: Translate 4CC string to ID. */ + if (opt.isSet("pixelformat")) + cfg.pixelFormat = opt["pixelformat"]; + } + } + + streamName_.clear(); + for (unsigned int index = 0; index < config_->size(); ++index) { + StreamConfiguration &cfg = config_->at(index); + streamName_[cfg.stream()] = "stream" + std::to_string(index); + } + + return 0; +} + +int Capture::capture(EventLoop *loop) +{ + int ret; + + /* Identify the stream with the least number of buffers. */ + unsigned int nbuffers = UINT_MAX; + for (StreamConfiguration &cfg : *config_) { + Stream *stream = cfg.stream(); + nbuffers = std::min(nbuffers, stream->bufferPool().count()); + } + + /* + * TODO: make cam tool smarter to support still capture by for + * example pushing a button. For now run all streams all the time. + */ + + std::vector<Request *> requests; + for (unsigned int i = 0; i < nbuffers; i++) { + Request *request = camera_->createRequest(); + if (!request) { + std::cerr << "Can't create request" << std::endl; + return -ENOMEM; + } + + std::map<Stream *, Buffer *> map; + for (StreamConfiguration &cfg : *config_) { + Stream *stream = cfg.stream(); + map[stream] = &stream->bufferPool().buffers()[i]; + } + + ret = request->setBuffers(map); + if (ret < 0) { + std::cerr << "Can't set buffers for request" << std::endl; + return ret; + } + + requests.push_back(request); + } + + ret = camera_->start(); + if (ret) { + std::cout << "Failed to start capture" << std::endl; + return ret; + } + + for (Request *request : requests) { + ret = camera_->queueRequest(request); + if (ret < 0) { + std::cerr << "Can't queue request" << std::endl; + return ret; + } + } + + std::cout << "Capture until user interrupts by SIGINT" << std::endl; + ret = loop->exec(); + if (ret) + std::cout << "Failed to run capture loop" << std::endl; + + ret = camera_->stop(); + if (ret) + std::cout << "Failed to stop capture" << std::endl; + + return ret; +} + +void Capture::requestComplete(Request *request, const std::map<Stream *, Buffer *> &buffers) +{ + double fps = 0.0; + uint64_t now; + + if (request->status() == Request::RequestCancelled) + return; + + struct timespec time; + clock_gettime(CLOCK_MONOTONIC, &time); + now = time.tv_sec * 1000 + time.tv_nsec / 1000000; + fps = now - last_; + fps = last_ && fps ? 1000.0 / fps : 0.0; + last_ = now; + + std::stringstream info; + info << "fps: " << std::fixed << std::setprecision(2) << fps; + + for (auto it = buffers.begin(); it != buffers.end(); ++it) { + Stream *stream = it->first; + Buffer *buffer = it->second; + const std::string &name = streamName_[stream]; + + info << " " << name + << " (" << buffer->index() << ")" + << " seq: " << std::setw(6) << std::setfill('0') << buffer->sequence() + << " bytesused: " << buffer->bytesused(); + + if (writer_) + writer_->write(buffer, name); + } + + std::cout << info.str() << std::endl; + + request = camera_->createRequest(); + if (!request) { + std::cerr << "Can't create request" << std::endl; + return; + } + + request->setBuffers(buffers); + camera_->queueRequest(request); +} diff --git a/src/cam/capture.h b/src/cam/capture.h new file mode 100644 index 00000000..a97d1f44 --- /dev/null +++ b/src/cam/capture.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * capture.h - Cam capture + */ +#ifndef __CAM_CAPTURE_H__ +#define __CAM_CAPTURE_H__ + +#include <memory> + +#include <libcamera/camera.h> +#include <libcamera/request.h> +#include <libcamera/stream.h> + +#include "buffer_writer.h" +#include "event_loop.h" +#include "options.h" + +class Capture +{ +public: + Capture(libcamera::Camera *camera); + + int run(EventLoop *loop, const OptionsParser::Options &options); +private: + int prepareConfig(const OptionsParser::Options &options); + + int capture(EventLoop *loop); + + void requestComplete(libcamera::Request *request, + const std::map<libcamera::Stream *, libcamera::Buffer *> &buffers); + + libcamera::Camera *camera_; + std::unique_ptr<libcamera::CameraConfiguration> config_; + + std::map<libcamera::Stream *, std::string> streamName_; + BufferWriter *writer_; + uint64_t last_; +}; + +#endif /* __CAM_CAPTURE_H__ */ diff --git a/src/cam/main.cpp b/src/cam/main.cpp index 41558896..d071a75d 100644 --- a/src/cam/main.cpp +++ b/src/cam/main.cpp @@ -5,37 +5,22 @@ * main.cpp - cam - The libcamera swiss army knife */ -#include <algorithm> -#include <iomanip> #include <iostream> -#include <limits.h> -#include <map> #include <signal.h> -#include <sstream> #include <string.h> #include <libcamera/libcamera.h> -#include "buffer_writer.h" +#include "capture.h" #include "event_loop.h" +#include "main.h" #include "options.h" using namespace libcamera; OptionsParser::Options options; std::shared_ptr<Camera> camera; -std::map<Stream *, std::string> streamInfo; EventLoop *loop; -BufferWriter *writer; - -enum { - OptCamera = 'c', - OptCapture = 'C', - OptFile = 'F', - OptHelp = 'h', - OptList = 'l', - OptStream = 's', -}; void signalHandler(int signal) { @@ -85,201 +70,6 @@ static int parseOptions(int argc, char *argv[]) return 0; } -static std::unique_ptr<CameraConfiguration> prepareCameraConfig() -{ - StreamRoles roles; - - /* If no configuration is provided assume a single video stream. */ - if (!options.isSet(OptStream)) - return camera->generateConfiguration({ StreamRole::VideoRecording }); - - const std::vector<OptionValue> &streamOptions = - options[OptStream].toArray(); - - /* Use roles and get a default configuration. */ - for (auto const &value : streamOptions) { - KeyValueParser::Options opt = value.toKeyValues(); - - if (!opt.isSet("role")) { - roles.push_back(StreamRole::VideoRecording); - } else if (opt["role"].toString() == "viewfinder") { - roles.push_back(StreamRole::Viewfinder); - } else if (opt["role"].toString() == "video") { - roles.push_back(StreamRole::VideoRecording); - } else if (opt["role"].toString() == "still") { - roles.push_back(StreamRole::StillCapture); - } else { - std::cerr << "Unknown stream role " - << opt["role"].toString() << std::endl; - return nullptr; - } - } - - 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 nullptr; - } - - /* Apply configuration explicitly requested. */ - unsigned int i = 0; - for (auto const &value : streamOptions) { - KeyValueParser::Options opt = value.toKeyValues(); - StreamConfiguration &cfg = config->at(i++); - - if (opt.isSet("width")) - cfg.size.width = opt["width"]; - - if (opt.isSet("height")) - cfg.size.height = opt["height"]; - - /* TODO: Translate 4CC string to ID. */ - if (opt.isSet("pixelformat")) - cfg.pixelFormat = opt["pixelformat"]; - } - - return config; -} - -static void requestComplete(Request *request, const std::map<Stream *, Buffer *> &buffers) -{ - static uint64_t now, last = 0; - double fps = 0.0; - - if (request->status() == Request::RequestCancelled) - return; - - struct timespec time; - clock_gettime(CLOCK_MONOTONIC, &time); - now = time.tv_sec * 1000 + time.tv_nsec / 1000000; - fps = now - last; - fps = last && fps ? 1000.0 / fps : 0.0; - last = now; - - std::stringstream info; - info << "fps: " << std::fixed << std::setprecision(2) << fps; - - for (auto it = buffers.begin(); it != buffers.end(); ++it) { - Stream *stream = it->first; - Buffer *buffer = it->second; - const std::string &name = streamInfo[stream]; - - info << " " << name - << " (" << buffer->index() << ")" - << " seq: " << std::setw(6) << std::setfill('0') << buffer->sequence() - << " bytesused: " << buffer->bytesused(); - - if (writer) - writer->write(buffer, name); - } - - std::cout << info.str() << std::endl; - - request = camera->createRequest(); - if (!request) { - std::cerr << "Can't create request" << std::endl; - return; - } - - request->setBuffers(buffers); - camera->queueRequest(request); -} - -static int capture() -{ - int ret; - - std::unique_ptr<CameraConfiguration> config = prepareCameraConfig(); - if (!config) { - std::cout << "Failed to prepare camera configuration" << std::endl; - return -EINVAL; - } - - ret = camera->configure(config.get()); - if (ret < 0) { - std::cout << "Failed to configure camera" << std::endl; - return ret; - } - - streamInfo.clear(); - - for (unsigned int index = 0; index < config->size(); ++index) { - StreamConfiguration &cfg = config->at(index); - streamInfo[cfg.stream()] = "stream" + std::to_string(index); - } - - ret = camera->allocateBuffers(); - if (ret) { - std::cerr << "Failed to allocate buffers" - << std::endl; - return ret; - } - - camera->requestCompleted.connect(requestComplete); - - /* Identify the stream with the least number of buffers. */ - unsigned int nbuffers = UINT_MAX; - for (StreamConfiguration &cfg : *config) { - Stream *stream = cfg.stream(); - nbuffers = std::min(nbuffers, stream->bufferPool().count()); - } - - /* - * TODO: make cam tool smarter to support still capture by for - * example pushing a button. For now run all streams all the time. - */ - - std::vector<Request *> requests; - for (unsigned int i = 0; i < nbuffers; i++) { - Request *request = camera->createRequest(); - if (!request) { - std::cerr << "Can't create request" << std::endl; - ret = -ENOMEM; - goto out; - } - - std::map<Stream *, Buffer *> map; - for (StreamConfiguration &cfg : *config) { - Stream *stream = cfg.stream(); - map[stream] = &stream->bufferPool().buffers()[i]; - } - - ret = request->setBuffers(map); - if (ret < 0) { - std::cerr << "Can't set buffers for request" << std::endl; - goto out; - } - - requests.push_back(request); - } - - ret = camera->start(); - if (ret) { - std::cout << "Failed to start capture" << std::endl; - goto out; - } - - for (Request *request : requests) { - ret = camera->queueRequest(request); - if (ret < 0) { - std::cerr << "Can't queue request" << std::endl; - goto out; - } - } - - std::cout << "Capture until user interrupts by SIGINT" << std::endl; - ret = loop->exec(); - - ret = camera->stop(); - if (ret) - std::cout << "Failed to stop capture" << std::endl; -out: - camera->freeBuffers(); - - return ret; -} - int main(int argc, char **argv) { int ret; @@ -327,26 +117,8 @@ int main(int argc, char **argv) } if (options.isSet(OptCapture)) { - if (!camera) { - std::cout << "Can't capture without a camera" - << std::endl; - ret = EXIT_FAILURE; - goto out; - } - - if (options.isSet(OptFile)) { - if (!options[OptFile].toString().empty()) - writer = new BufferWriter(options[OptFile]); - else - writer = new BufferWriter(); - } - - capture(); - - if (options.isSet(OptFile)) { - delete writer; - writer = nullptr; - } + Capture capture(camera.get()); + ret = capture.run(loop, options); } if (camera) { diff --git a/src/cam/main.h b/src/cam/main.h new file mode 100644 index 00000000..fff81b1f --- /dev/null +++ b/src/cam/main.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * main.h - Cam application + */ +#ifndef __CAM_MAIN_H__ +#define __CAM_MAIN_H__ + +enum { + OptCamera = 'c', + OptCapture = 'C', + OptFile = 'F', + OptHelp = 'h', + OptList = 'l', + OptStream = 's', +}; + +#endif /* __CAM_MAIN_H__ */ diff --git a/src/cam/meson.build b/src/cam/meson.build index 3faddc6c..478346c5 100644 --- a/src/cam/meson.build +++ b/src/cam/meson.build @@ -1,5 +1,6 @@ cam_sources = files([ 'buffer_writer.cpp', + 'capture.cpp', 'event_loop.cpp', 'main.cpp', 'options.cpp', |