{"id":1267,"url":"https://patchwork.libcamera.org/api/patches/1267/?format=json","web_url":"https://patchwork.libcamera.org/patch/1267/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20190523005534.9631-2-niklas.soderlund@ragnatech.se>","date":"2019-05-23T00:55:33","name":"[libcamera-devel,1/2] cam: capture: Break out capture to a new class","commit_ref":null,"pull_url":null,"state":"superseded","archived":false,"hash":"52bf78d94c25c6acadd59bfe5b1e08095872cc36","submitter":{"id":5,"url":"https://patchwork.libcamera.org/api/people/5/?format=json","name":"Niklas Söderlund","email":"niklas.soderlund@ragnatech.se"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/1267/mbox/","series":[{"id":321,"url":"https://patchwork.libcamera.org/api/series/321/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=321","date":"2019-05-23T00:55:32","name":"cam: cleanup code structure","version":1,"mbox":"https://patchwork.libcamera.org/series/321/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/1267/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/1267/checks/","tags":{},"headers":{"Return-Path":"<niklas.soderlund@ragnatech.se>","Received":["from bin-mail-out-06.binero.net (bin-mail-out-06.binero.net\n\t[195.74.38.229])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 0719E60C0D\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 23 May 2019 02:55:46 +0200 (CEST)","from bismarck.berto.se (unknown [89.233.230.99])\n\tby bin-vsp-out-02.atm.binero.net (Halon) with ESMTPA\n\tid 7e455573-7cf5-11e9-8d05-005056917f90;\n\tThu, 23 May 2019 02:55:44 +0200 (CEST)"],"X-Halon-ID":"7e455573-7cf5-11e9-8d05-005056917f90","Authorized-sender":"niklas@soderlund.pp.se","From":"=?utf-8?q?Niklas_S=C3=B6derlund?= <niklas.soderlund@ragnatech.se>","To":"libcamera-devel@lists.libcamera.org","Date":"Thu, 23 May 2019 02:55:33 +0200","Message-Id":"<20190523005534.9631-2-niklas.soderlund@ragnatech.se>","X-Mailer":"git-send-email 2.21.0","In-Reply-To":"<20190523005534.9631-1-niklas.soderlund@ragnatech.se>","References":"<20190523005534.9631-1-niklas.soderlund@ragnatech.se>","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Subject":"[libcamera-devel] [PATCH 1/2] cam: capture: Break out capture to a\n\tnew class","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.23","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Thu, 23 May 2019 00:55:47 -0000"},"content":"Reduce the complexity of main.cpp by compartmentalize the capture logic\nin its own class. There is no functional change.\n\nSigned-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\n---\n src/cam/capture.cpp | 244 ++++++++++++++++++++++++++++++++++++++++++++\n src/cam/capture.h   |  42 ++++++++\n src/cam/main.cpp    | 236 +-----------------------------------------\n src/cam/main.h      |  19 ++++\n src/cam/meson.build |   1 +\n 5 files changed, 310 insertions(+), 232 deletions(-)\n create mode 100644 src/cam/capture.cpp\n create mode 100644 src/cam/capture.h\n create mode 100644 src/cam/main.h","diff":"diff --git a/src/cam/capture.cpp b/src/cam/capture.cpp\nnew file mode 100644\nindex 0000000000000000..91f65e8cf23c888d\n--- /dev/null\n+++ b/src/cam/capture.cpp\n@@ -0,0 +1,244 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2019, Google Inc.\n+ *\n+ * capture.cpp - Cam capture\n+ */\n+\n+#include <climits>\n+#include <iomanip>\n+#include <iostream>\n+#include <sstream>\n+\n+#include \"capture.h\"\n+#include \"main.h\"\n+\n+using namespace libcamera;\n+\n+Capture::Capture()\n+\t: camera_(nullptr), writer_(nullptr)\n+{\n+}\n+\n+int Capture::run(libcamera::Camera *camera, EventLoop *loop,\n+\t\t const OptionsParser::Options &options)\n+{\n+\tint ret;\n+\n+\tif (!camera) {\n+\t\tstd::cout << \"Can't capture without a camera\" << std::endl;\n+\t\treturn -ENODEV;\n+\t}\n+\n+\tcamera_ = camera;\n+\n+\tret = prepareConfig(options);\n+\tif (ret) {\n+\t\tstd::cout << \"Failed to prepare camera configuration\" << std::endl;\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (options.isSet(OptFile)) {\n+\t\tif (!options[OptFile].toString().empty())\n+\t\t\twriter_ = new BufferWriter(options[OptFile]);\n+\t\telse\n+\t\t\twriter_ = new BufferWriter();\n+\t}\n+\n+\tret = capture(loop);\n+\n+\tif (options.isSet(OptFile))\n+\t\tdelete writer_;\n+\n+\treturn ret;\n+}\n+\n+int Capture::prepareConfig(const OptionsParser::Options &options)\n+{\n+\tStreamRoles roles;\n+\n+\t/* If no configuration is provided assume a single video stream. */\n+\tif (!options.isSet(OptStream)) {\n+\t\tconfig_ = camera_->generateConfiguration({ StreamRole::VideoRecording });\n+\t\treturn 0;\n+\t}\n+\n+\tconst std::vector<OptionValue> &streamOptions =\n+\t\toptions[OptStream].toArray();\n+\n+\t/* Use roles and get a default configuration. */\n+\tfor (auto const &value : streamOptions) {\n+\t\tKeyValueParser::Options conf = value.toKeyValues();\n+\n+\t\tif (!conf.isSet(\"role\")) {\n+\t\t\troles.push_back(StreamRole::VideoRecording);\n+\t\t} else if (conf[\"role\"].toString() == \"viewfinder\") {\n+\t\t\troles.push_back(StreamRole::Viewfinder);\n+\t\t} else if (conf[\"role\"].toString() == \"video\") {\n+\t\t\troles.push_back(StreamRole::VideoRecording);\n+\t\t} else if (conf[\"role\"].toString() == \"still\") {\n+\t\t\troles.push_back(StreamRole::StillCapture);\n+\t\t} else {\n+\t\t\tstd::cerr << \"Unknown stream role \"\n+\t\t\t\t  << conf[\"role\"].toString() << std::endl;\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t}\n+\n+\tconfig_ = camera_->generateConfiguration(roles);\n+\tif (!config_ || config_->size() != roles.size()) {\n+\t\tstd::cerr << \"Failed to get default stream configuration\"\n+\t\t\t  << std::endl;\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/* Apply configuration explicitly requested. */\n+\tunsigned int i = 0;\n+\tfor (auto const &value : streamOptions) {\n+\t\tKeyValueParser::Options conf = value.toKeyValues();\n+\t\tStreamConfiguration &cfg = config_->at(i++);\n+\n+\t\tif (conf.isSet(\"width\"))\n+\t\t\tcfg.size.width = conf[\"width\"];\n+\n+\t\tif (conf.isSet(\"height\"))\n+\t\t\tcfg.size.height = conf[\"height\"];\n+\n+\t\t/* TODO: Translate 4CC string to ID. */\n+\t\tif (conf.isSet(\"pixelformat\"))\n+\t\t\tcfg.pixelFormat = conf[\"pixelformat\"];\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int Capture::capture(EventLoop *loop)\n+{\n+\tint ret;\n+\n+\tret = camera_->configure(config_.get());\n+\tif (ret < 0) {\n+\t\tstd::cout << \"Failed to configure camera\" << std::endl;\n+\t\treturn ret;\n+\t}\n+\n+\tstreamName_.clear();\n+\n+\tfor (unsigned int index = 0; index < config_->size(); ++index) {\n+\t\tStreamConfiguration &cfg = config_->at(index);\n+\t\tstreamName_[cfg.stream()] = \"stream\" + std::to_string(index);\n+\t}\n+\n+\tret = camera_->allocateBuffers();\n+\tif (ret) {\n+\t\tstd::cerr << \"Failed to allocate buffers\" << std::endl;\n+\t\treturn ret;\n+\t}\n+\n+\tcamera_->requestCompleted.connect(this, &Capture::requestComplete);\n+\n+\t/* Identify the stream with the least number of buffers. */\n+\tunsigned int nbuffers = UINT_MAX;\n+\tfor (StreamConfiguration &cfg : *config_) {\n+\t\tStream *stream = cfg.stream();\n+\t\tnbuffers = std::min(nbuffers, stream->bufferPool().count());\n+\t}\n+\n+\t/*\n+\t * TODO: make cam tool smarter to support still capture by for\n+\t * example pushing a button. For now run all streams all the time.\n+\t */\n+\n+\tstd::vector<Request *> requests;\n+\tfor (unsigned int i = 0; i < nbuffers; i++) {\n+\t\tRequest *request = camera_->createRequest();\n+\t\tif (!request) {\n+\t\t\tstd::cerr << \"Can't create request\" << std::endl;\n+\t\t\tret = -ENOMEM;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tstd::map<Stream *, Buffer *> map;\n+\t\tfor (StreamConfiguration &cfg : *config_) {\n+\t\t\tStream *stream = cfg.stream();\n+\t\t\tmap[stream] = &stream->bufferPool().buffers()[i];\n+\t\t}\n+\n+\t\tret = request->setBuffers(map);\n+\t\tif (ret < 0) {\n+\t\t\tstd::cerr << \"Can't set buffers for request\" << std::endl;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\trequests.push_back(request);\n+\t}\n+\n+\tret = camera_->start();\n+\tif (ret) {\n+\t\tstd::cout << \"Failed to start capture\" << std::endl;\n+\t\tgoto out;\n+\t}\n+\n+\tfor (Request *request : requests) {\n+\t\tret = camera_->queueRequest(request);\n+\t\tif (ret < 0) {\n+\t\t\tstd::cerr << \"Can't queue request\" << std::endl;\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n+\n+\tstd::cout << \"Capture until user interrupts by SIGINT\" << std::endl;\n+\tret = loop->exec();\n+\n+\tret = camera_->stop();\n+\tif (ret)\n+\t\tstd::cout << \"Failed to stop capture\" << std::endl;\n+out:\n+\tcamera_->freeBuffers();\n+\n+\treturn ret;\n+}\n+\n+void Capture::requestComplete(Request *request, const std::map<Stream *, Buffer *> &buffers)\n+{\n+\tstatic uint64_t now, last = 0;\n+\tdouble fps = 0.0;\n+\n+\tif (request->status() == Request::RequestCancelled)\n+\t\treturn;\n+\n+\tstruct timespec time;\n+\tclock_gettime(CLOCK_MONOTONIC, &time);\n+\tnow = time.tv_sec * 1000 + time.tv_nsec / 1000000;\n+\tfps = now - last;\n+\tfps = last && fps ? 1000.0 / fps : 0.0;\n+\tlast = now;\n+\n+\tstd::stringstream info;\n+\tinfo << \"fps: \" << std::fixed << std::setprecision(2) << fps;\n+\n+\tfor (auto it = buffers.begin(); it != buffers.end(); ++it) {\n+\t\tStream *stream = it->first;\n+\t\tBuffer *buffer = it->second;\n+\t\tconst std::string &name = streamName_[stream];\n+\n+\t\tinfo << \" \" << name\n+\t\t     << \" (\" << buffer->index() << \")\"\n+\t\t     << \" seq: \" << std::setw(6) << std::setfill('0') << buffer->sequence()\n+\t\t     << \" bytesused: \" << buffer->bytesused();\n+\n+\t\tif (writer_)\n+\t\t\twriter_->write(buffer, name);\n+\t}\n+\n+\tstd::cout << info.str() << std::endl;\n+\n+\trequest = camera_->createRequest();\n+\tif (!request) {\n+\t\tstd::cerr << \"Can't create request\" << std::endl;\n+\t\treturn;\n+\t}\n+\n+\trequest->setBuffers(buffers);\n+\tcamera_->queueRequest(request);\n+}\ndiff --git a/src/cam/capture.h b/src/cam/capture.h\nnew file mode 100644\nindex 0000000000000000..728b1d22b159b046\n--- /dev/null\n+++ b/src/cam/capture.h\n@@ -0,0 +1,42 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2019, Google Inc.\n+ *\n+ * capture.h - Cam capture\n+ */\n+#ifndef __CAM_CAPTURE_H__\n+#define __CAM_CAPTURE_H__\n+\n+#include <memory>\n+\n+#include <libcamera/camera.h>\n+#include <libcamera/request.h>\n+#include <libcamera/stream.h>\n+\n+#include \"buffer_writer.h\"\n+#include \"event_loop.h\"\n+#include \"options.h\"\n+\n+class Capture\n+{\n+public:\n+\tCapture();\n+\n+\tint run(libcamera::Camera *camera, EventLoop *loop,\n+\t\tconst OptionsParser::Options &options);\n+private:\n+\tint prepareConfig(const OptionsParser::Options &options);\n+\n+\tint capture(EventLoop *loop);\n+\n+\tvoid requestComplete(libcamera::Request *request,\n+\t\t\t     const std::map<libcamera::Stream *, libcamera::Buffer *> &buffers);\n+\n+\tlibcamera::Camera *camera_;\n+\tstd::unique_ptr<libcamera::CameraConfiguration> config_;\n+\n+\tstd::map<libcamera::Stream *, std::string> streamName_;\n+\tBufferWriter *writer_;\n+};\n+\n+#endif /* __CAM_CAPTURE_H__ */\ndiff --git a/src/cam/main.cpp b/src/cam/main.cpp\nindex 5ecd7e0e38d768bb..fe7d4f90dbf14ffd 100644\n--- a/src/cam/main.cpp\n+++ b/src/cam/main.cpp\n@@ -5,37 +5,22 @@\n  * main.cpp - cam - The libcamera swiss army knife\n  */\n \n-#include <algorithm>\n-#include <iomanip>\n #include <iostream>\n-#include <limits.h>\n-#include <map>\n #include <signal.h>\n-#include <sstream>\n #include <string.h>\n \n #include <libcamera/libcamera.h>\n \n-#include \"buffer_writer.h\"\n+#include \"capture.h\"\n #include \"event_loop.h\"\n+#include \"main.h\"\n #include \"options.h\"\n \n using namespace libcamera;\n \n OptionsParser::Options options;\n std::shared_ptr<Camera> camera;\n-std::map<Stream *, std::string> streamInfo;\n EventLoop *loop;\n-BufferWriter *writer;\n-\n-enum {\n-\tOptCamera = 'c',\n-\tOptCapture = 'C',\n-\tOptFile = 'F',\n-\tOptHelp = 'h',\n-\tOptList = 'l',\n-\tOptStream = 's',\n-};\n \n void signalHandler(int signal)\n {\n@@ -85,201 +70,6 @@ static int parseOptions(int argc, char *argv[])\n \treturn 0;\n }\n \n-static std::unique_ptr<CameraConfiguration> prepareCameraConfig()\n-{\n-\tStreamRoles roles;\n-\n-\t/* If no configuration is provided assume a single video stream. */\n-\tif (!options.isSet(OptStream))\n-\t\treturn camera->generateConfiguration({ StreamRole::VideoRecording });\n-\n-\tconst std::vector<OptionValue> &streamOptions =\n-\t\toptions[OptStream].toArray();\n-\n-\t/* Use roles and get a default configuration. */\n-\tfor (auto const &value : streamOptions) {\n-\t\tKeyValueParser::Options conf = value.toKeyValues();\n-\n-\t\tif (!conf.isSet(\"role\")) {\n-\t\t\troles.push_back(StreamRole::VideoRecording);\n-\t\t} else if (conf[\"role\"].toString() == \"viewfinder\") {\n-\t\t\troles.push_back(StreamRole::Viewfinder);\n-\t\t} else if (conf[\"role\"].toString() == \"video\") {\n-\t\t\troles.push_back(StreamRole::VideoRecording);\n-\t\t} else if (conf[\"role\"].toString() == \"still\") {\n-\t\t\troles.push_back(StreamRole::StillCapture);\n-\t\t} else {\n-\t\t\tstd::cerr << \"Unknown stream role \"\n-\t\t\t\t  << conf[\"role\"].toString() << std::endl;\n-\t\t\treturn nullptr;\n-\t\t}\n-\t}\n-\n-\tstd::unique_ptr<CameraConfiguration> config = camera->generateConfiguration(roles);\n-\tif (!config || config->size() != roles.size()) {\n-\t\tstd::cerr << \"Failed to get default stream configuration\"\n-\t\t\t  << std::endl;\n-\t\treturn nullptr;\n-\t}\n-\n-\t/* Apply configuration explicitly requested. */\n-\tunsigned int i = 0;\n-\tfor (auto const &value : streamOptions) {\n-\t\tKeyValueParser::Options conf = value.toKeyValues();\n-\t\tStreamConfiguration &cfg = config->at(i++);\n-\n-\t\tif (conf.isSet(\"width\"))\n-\t\t\tcfg.size.width = conf[\"width\"];\n-\n-\t\tif (conf.isSet(\"height\"))\n-\t\t\tcfg.size.height = conf[\"height\"];\n-\n-\t\t/* TODO: Translate 4CC string to ID. */\n-\t\tif (conf.isSet(\"pixelformat\"))\n-\t\t\tcfg.pixelFormat = conf[\"pixelformat\"];\n-\t}\n-\n-\treturn config;\n-}\n-\n-static void requestComplete(Request *request, const std::map<Stream *, Buffer *> &buffers)\n-{\n-\tstatic uint64_t now, last = 0;\n-\tdouble fps = 0.0;\n-\n-\tif (request->status() == Request::RequestCancelled)\n-\t\treturn;\n-\n-\tstruct timespec time;\n-\tclock_gettime(CLOCK_MONOTONIC, &time);\n-\tnow = time.tv_sec * 1000 + time.tv_nsec / 1000000;\n-\tfps = now - last;\n-\tfps = last && fps ? 1000.0 / fps : 0.0;\n-\tlast = now;\n-\n-\tstd::stringstream info;\n-\tinfo << \"fps: \" << std::fixed << std::setprecision(2) << fps;\n-\n-\tfor (auto it = buffers.begin(); it != buffers.end(); ++it) {\n-\t\tStream *stream = it->first;\n-\t\tBuffer *buffer = it->second;\n-\t\tconst std::string &name = streamInfo[stream];\n-\n-\t\tinfo << \" \" << name\n-\t\t     << \" (\" << buffer->index() << \")\"\n-\t\t     << \" seq: \" << std::setw(6) << std::setfill('0') << buffer->sequence()\n-\t\t     << \" bytesused: \" << buffer->bytesused();\n-\n-\t\tif (writer)\n-\t\t\twriter->write(buffer, name);\n-\t}\n-\n-\tstd::cout << info.str() << std::endl;\n-\n-\trequest = camera->createRequest();\n-\tif (!request) {\n-\t\tstd::cerr << \"Can't create request\" << std::endl;\n-\t\treturn;\n-\t}\n-\n-\trequest->setBuffers(buffers);\n-\tcamera->queueRequest(request);\n-}\n-\n-static int capture()\n-{\n-\tint ret;\n-\n-\tstd::unique_ptr<CameraConfiguration> config = prepareCameraConfig();\n-\tif (!config) {\n-\t\tstd::cout << \"Failed to prepare camera configuration\" << std::endl;\n-\t\treturn -EINVAL;\n-\t}\n-\n-\tret = camera->configure(config.get());\n-\tif (ret < 0) {\n-\t\tstd::cout << \"Failed to configure camera\" << std::endl;\n-\t\treturn ret;\n-\t}\n-\n-\tstreamInfo.clear();\n-\n-\tfor (unsigned int index = 0; index < config->size(); ++index) {\n-\t\tStreamConfiguration &cfg = config->at(index);\n-\t\tstreamInfo[cfg.stream()] = \"stream\" + std::to_string(index);\n-\t}\n-\n-\tret = camera->allocateBuffers();\n-\tif (ret) {\n-\t\tstd::cerr << \"Failed to allocate buffers\"\n-\t\t\t  << std::endl;\n-\t\treturn ret;\n-\t}\n-\n-\tcamera->requestCompleted.connect(requestComplete);\n-\n-\t/* Identify the stream with the least number of buffers. */\n-\tunsigned int nbuffers = UINT_MAX;\n-\tfor (StreamConfiguration &cfg : *config) {\n-\t\tStream *stream = cfg.stream();\n-\t\tnbuffers = std::min(nbuffers, stream->bufferPool().count());\n-\t}\n-\n-\t/*\n-\t * TODO: make cam tool smarter to support still capture by for\n-\t * example pushing a button. For now run all streams all the time.\n-\t */\n-\n-\tstd::vector<Request *> requests;\n-\tfor (unsigned int i = 0; i < nbuffers; i++) {\n-\t\tRequest *request = camera->createRequest();\n-\t\tif (!request) {\n-\t\t\tstd::cerr << \"Can't create request\" << std::endl;\n-\t\t\tret = -ENOMEM;\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tstd::map<Stream *, Buffer *> map;\n-\t\tfor (StreamConfiguration &cfg : *config) {\n-\t\t\tStream *stream = cfg.stream();\n-\t\t\tmap[stream] = &stream->bufferPool().buffers()[i];\n-\t\t}\n-\n-\t\tret = request->setBuffers(map);\n-\t\tif (ret < 0) {\n-\t\t\tstd::cerr << \"Can't set buffers for request\" << std::endl;\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\trequests.push_back(request);\n-\t}\n-\n-\tret = camera->start();\n-\tif (ret) {\n-\t\tstd::cout << \"Failed to start capture\" << std::endl;\n-\t\tgoto out;\n-\t}\n-\n-\tfor (Request *request : requests) {\n-\t\tret = camera->queueRequest(request);\n-\t\tif (ret < 0) {\n-\t\t\tstd::cerr << \"Can't queue request\" << std::endl;\n-\t\t\tgoto out;\n-\t\t}\n-\t}\n-\n-\tstd::cout << \"Capture until user interrupts by SIGINT\" << std::endl;\n-\tret = loop->exec();\n-\n-\tret = camera->stop();\n-\tif (ret)\n-\t\tstd::cout << \"Failed to stop capture\" << std::endl;\n-out:\n-\tcamera->freeBuffers();\n-\n-\treturn ret;\n-}\n-\n int main(int argc, char **argv)\n {\n \tint ret;\n@@ -327,26 +117,8 @@ int main(int argc, char **argv)\n \t}\n \n \tif (options.isSet(OptCapture)) {\n-\t\tif (!camera) {\n-\t\t\tstd::cout << \"Can't capture without a camera\"\n-\t\t\t\t  << std::endl;\n-\t\t\tret = EXIT_FAILURE;\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tif (options.isSet(OptFile)) {\n-\t\t\tif (!options[OptFile].toString().empty())\n-\t\t\t\twriter = new BufferWriter(options[OptFile]);\n-\t\t\telse\n-\t\t\t\twriter = new BufferWriter();\n-\t\t}\n-\n-\t\tcapture();\n-\n-\t\tif (options.isSet(OptFile)) {\n-\t\t\tdelete writer;\n-\t\t\twriter = nullptr;\n-\t\t}\n+\t\tCapture capture;\n+\t\tret = capture.run(camera.get(), loop, options);\n \t}\n \n \tif (camera) {\ndiff --git a/src/cam/main.h b/src/cam/main.h\nnew file mode 100644\nindex 0000000000000000..a48bde620dc957f0\n--- /dev/null\n+++ b/src/cam/main.h\n@@ -0,0 +1,19 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2019, Google Inc.\n+ *\n+ * main.h - Cam application\n+ */\n+#ifndef __CAM_MAIN_H__\n+#define __CAM_MAIN_H__\n+\n+enum {\n+\tOptCamera = 'c',\n+\tOptCapture = 'C',\n+\tOptFile = 'F',\n+\tOptHelp = 'h',\n+\tOptList = 'l',\n+\tOptStream = 's',\n+};\n+\n+#endif /* __CAM_CAPTURE_H__ */\ndiff --git a/src/cam/meson.build b/src/cam/meson.build\nindex 851295091d0d5132..6d27b57393584fac 100644\n--- a/src/cam/meson.build\n+++ b/src/cam/meson.build\n@@ -1,5 +1,6 @@\n cam_sources = files([\n     'buffer_writer.cpp',\n+    'capture.cpp',\n     'event_loop.cpp',\n     'main.cpp',\n     'options.cpp',\n","prefixes":["libcamera-devel","1/2"]}