From patchwork Fri May 24 08:23:32 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Niklas_S=C3=B6derlund?= X-Patchwork-Id: 1283 Return-Path: Received: from vsp-unauthed02.binero.net (vsp-unauthed02.binero.net [195.74.38.227]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6363960BFA for ; Fri, 24 May 2019 10:24:38 +0200 (CEST) X-Halon-ID: 5be9e527-7dfd-11e9-8ab4-005056917a89 Authorized-sender: niklas@soderlund.pp.se Received: from bismarck.berto.se (unknown [89.233.230.99]) by bin-vsp-out-01.atm.binero.net (Halon) with ESMTPA id 5be9e527-7dfd-11e9-8ab4-005056917a89; Fri, 24 May 2019 10:24:33 +0200 (CEST) From: =?utf-8?q?Niklas_S=C3=B6derlund?= To: libcamera-devel@lists.libcamera.org Date: Fri, 24 May 2019 10:23:32 +0200 Message-Id: <20190524082333.8974-2-niklas.soderlund@ragnatech.se> X-Mailer: git-send-email 2.21.0 In-Reply-To: <20190524082333.8974-1-niklas.soderlund@ragnatech.se> References: <20190524082333.8974-1-niklas.soderlund@ragnatech.se> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 1/2] cam: capture: Break out capture to a new class X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 24 May 2019 08:24:38 -0000 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 Reviewed-by: Kieran Bingham Reviewed-by: Laurent Pinchart --- src/cam/capture.cpp | 248 ++++++++++++++++++++++++++++++++++++++++++++ src/cam/capture.h | 42 ++++++++ src/cam/main.cpp | 236 +---------------------------------------- src/cam/main.h | 19 ++++ src/cam/meson.build | 1 + 5 files changed, 314 insertions(+), 232 deletions(-) create mode 100644 src/cam/capture.cpp create mode 100644 src/cam/capture.h create mode 100644 src/cam/main.h diff --git a/src/cam/capture.cpp b/src/cam/capture.cpp new file mode 100644 index 0000000000000000..a4aa44af25828f23 --- /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 +#include +#include +#include + +#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 &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 &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 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 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 &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 0000000000000000..a97d1f44d229c214 --- /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 + +#include +#include +#include + +#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 &buffers); + + libcamera::Camera *camera_; + std::unique_ptr config_; + + std::map streamName_; + BufferWriter *writer_; + uint64_t last_; +}; + +#endif /* __CAM_CAPTURE_H__ */ diff --git a/src/cam/main.cpp b/src/cam/main.cpp index 4155889678ce1897..d071a75dd5347cf7 100644 --- a/src/cam/main.cpp +++ b/src/cam/main.cpp @@ -5,37 +5,22 @@ * main.cpp - cam - The libcamera swiss army knife */ -#include -#include #include -#include -#include #include -#include #include #include -#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; -std::map 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 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 &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 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 &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 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 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 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 0000000000000000..fff81b1f6c860b57 --- /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 3faddc6c8d85c765..478346c59590631d 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', From patchwork Fri May 24 08:23:33 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Niklas_S=C3=B6derlund?= X-Patchwork-Id: 1284 Return-Path: Received: from vsp-unauthed02.binero.net (vsp-unauthed02.binero.net [195.74.38.227]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5269160BFA for ; Fri, 24 May 2019 10:24:39 +0200 (CEST) X-Halon-ID: 5df5c701-7dfd-11e9-8ab4-005056917a89 Authorized-sender: niklas@soderlund.pp.se Received: from bismarck.berto.se (unknown [89.233.230.99]) by bin-vsp-out-01.atm.binero.net (Halon) with ESMTPA id 5df5c701-7dfd-11e9-8ab4-005056917a89; Fri, 24 May 2019 10:24:37 +0200 (CEST) From: =?utf-8?q?Niklas_S=C3=B6derlund?= To: libcamera-devel@lists.libcamera.org Date: Fri, 24 May 2019 10:23:33 +0200 Message-Id: <20190524082333.8974-3-niklas.soderlund@ragnatech.se> X-Mailer: git-send-email 2.21.0 In-Reply-To: <20190524082333.8974-1-niklas.soderlund@ragnatech.se> References: <20190524082333.8974-1-niklas.soderlund@ragnatech.se> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 2/2] cam: Add CamApp class X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 24 May 2019 08:24:39 -0000 Add more structure to main.cpp by breaking up the logic into a CamApp class. This makes the code easier to read and removes all of the organically grown global variables. Signed-off-by: Niklas Söderlund Reviewed-by: Laurent Pinchart --- src/cam/main.cpp | 198 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 136 insertions(+), 62 deletions(-) diff --git a/src/cam/main.cpp b/src/cam/main.cpp index d071a75dd5347cf7..dbf04917bcc5aa38 100644 --- a/src/cam/main.cpp +++ b/src/cam/main.cpp @@ -18,17 +18,115 @@ using namespace libcamera; -OptionsParser::Options options; -std::shared_ptr camera; -EventLoop *loop; +class CamApp +{ +public: + CamApp(); + + static CamApp *instance(); + + int init(int argc, char **argv); + void cleanup(); + + int exec(); + void quit(); + +private: + int parseOptions(int argc, char *argv[]); + int run(); + + static CamApp *app_; + OptionsParser::Options options_; + CameraManager *cm_; + std::shared_ptr camera_; + EventLoop *loop_; +}; + +CamApp *CamApp::app_ = nullptr; + +CamApp::CamApp() + : cm_(nullptr), camera_(nullptr), loop_(nullptr) +{ + 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 == -EINTR ? 0 : ret; + + cm_ = CameraManager::instance(); + + ret = cm_->start(); + if (ret) { + std::cout << "Failed to start camera manager: " + << strerror(-ret) << std::endl; + return ret; + } + + if (options_.isSet(OptCamera)) { + camera_ = cm_->get(options_[OptCamera]); + if (!camera_) { + std::cout << "Camera " + << std::string(options_[OptCamera]) + << " not found" << std::endl; + cm_->stop(); + return -ENODEV; + } + + if (camera_->acquire()) { + std::cout << "Failed to acquire camera" << std::endl; + camera_.reset(); + cm_->stop(); + return -EINVAL; + } + + std::cout << "Using camera " << camera_->name() << std::endl; + } + + loop_ = new EventLoop(cm_->eventDispatcher()); + + return 0; +} + +void CamApp::cleanup() +{ + delete loop_; + loop_ = nullptr; + + if (camera_) { + camera_->release(); + camera_.reset(); + } + + cm_->stop(); +} + +int CamApp::exec() +{ + int ret; + + ret = run(); + cleanup(); + + return ret; +} -void signalHandler(int signal) +void CamApp::quit() { - std::cout << "Exiting" << std::endl; - loop->exit(); + if (loop_) + loop_->exit(); } -static int parseOptions(int argc, char *argv[]) +int CamApp::parseOptions(int argc, char *argv[]) { KeyValueParser streamKeyValue; streamKeyValue.addOption("role", OptionString, @@ -58,77 +156,53 @@ static int parseOptions(int argc, char *argv[]) "help"); parser.addOption(OptList, OptionNone, "List all cameras", "list"); - options = parser.parse(argc, argv); - if (!options.valid()) + options_ = parser.parse(argc, argv); + if (!options_.valid()) return -EINVAL; - if (options.empty() || options.isSet(OptHelp)) { + if (options_.empty() || options_.isSet(OptHelp)) { parser.usage(); - return options.empty() ? -EINVAL : -EINTR; + return options_.empty() ? -EINVAL : -EINTR; } return 0; } +int CamApp::run() +{ + if (options_.isSet(OptList)) { + std::cout << "Available cameras:" << std::endl; + for (const std::shared_ptr &cam : cm_->cameras()) + std::cout << "- " << cam->name() << std::endl; + } + + if (options_.isSet(OptCapture)) { + Capture capture(camera_.get()); + return capture.run(loop_, options_); + } + + return 0; +} + +void signalHandler(int signal) +{ + std::cout << "Exiting" << std::endl; + CamApp::instance()->quit(); +} + int main(int argc, char **argv) { - int ret; + CamApp app; - ret = parseOptions(argc, argv); - if (ret < 0) - return ret == -EINTR ? 0 : EXIT_FAILURE; - - CameraManager *cm = CameraManager::instance(); - - ret = cm->start(); - if (ret) { - std::cout << "Failed to start camera manager: " - << strerror(-ret) << std::endl; + if (app.init(argc, argv)) return EXIT_FAILURE; - } - - loop = new EventLoop(cm->eventDispatcher()); struct sigaction sa = {}; sa.sa_handler = &signalHandler; sigaction(SIGINT, &sa, nullptr); - if (options.isSet(OptList)) { - std::cout << "Available cameras:" << std::endl; - for (const std::shared_ptr &cam : cm->cameras()) - std::cout << "- " << cam->name() << std::endl; - } + if (app.exec()) + return EXIT_FAILURE; - if (options.isSet(OptCamera)) { - camera = cm->get(options[OptCamera]); - if (!camera) { - std::cout << "Camera " - << std::string(options[OptCamera]) - << " not found" << std::endl; - goto out; - } - - if (camera->acquire()) { - std::cout << "Failed to acquire camera" << std::endl; - goto out; - } - - std::cout << "Using camera " << camera->name() << std::endl; - } - - if (options.isSet(OptCapture)) { - Capture capture(camera.get()); - ret = capture.run(loop, options); - } - - if (camera) { - camera->release(); - camera.reset(); - } -out: - delete loop; - - cm->stop(); - - return ret; + return 0; }