Message ID | 20190523151306.22007-2-niklas.soderlund@ragnatech.se |
---|---|
State | Superseded |
Headers | show |
Series |
|
Related | show |
Hi Niklas, Thank you for the patch. On Thu, May 23, 2019 at 05:13:05PM +0200, Niklas Söderlund wrote: > 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> > --- > src/cam/capture.cpp | 249 ++++++++++++++++++++++++++++++++++++++++++++ > src/cam/capture.h | 43 ++++++++ > src/cam/main.cpp | 236 +---------------------------------------- > src/cam/main.h | 19 ++++ > src/cam/meson.build | 1 + > 5 files changed, 316 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..a88c00c81daba0b4 > --- /dev/null > +++ b/src/cam/capture.cpp > @@ -0,0 +1,249 @@ > +/* 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_(nullptr), writer_(nullptr), last_(0) Would it make sense to pass the camera to the constructor ? > +{ > +} > + > +int Capture::run(Camera *camera, EventLoop *loop, > + const OptionsParser::Options &options) > +{ > + int ret; > + > + if (!camera) { > + std::cout << "Can't capture without a camera" << std::endl; > + return -ENODEV; > + } > + > + camera_ = camera; > + > + ret = prepareConfig(options); > + if (ret) { > + std::cout << "Failed to prepare camera configuration" << std::endl; > + return -EINVAL; > + } > + > + if (options.isSet(OptFile)) { > + if (!options[OptFile].toString().empty()) > + writer_ = new BufferWriter(options[OptFile]); > + else > + writer_ = new BufferWriter(); > + } > + > + ret = camera_->configure(config_.get()); > + if (ret < 0) { > + std::cout << "Failed to configure camera" << std::endl; > + return ret; You're leaking writer_ here and below. > + } > + > + ret = camera_->allocateBuffers(); > + if (ret) { > + std::cerr << "Failed to allocate buffers" << std::endl; > + return ret; > + } > + > + camera_->requestCompleted.connect(this, &Capture::requestComplete); > + > + ret = capture(loop); > + > + camera_->freeBuffers(); > + config_.reset(); > + > + if (options.isSet(OptFile)) { > + delete writer_; > + writer_ = nullptr; > + } > + > + 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(); No need to assign the return value to ret if you then ignore it. With these small issues fixed, Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> > + > + 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 0000000000000000..de70f22e59f00bfd > --- /dev/null > +++ b/src/cam/capture.h > @@ -0,0 +1,43 @@ > +/* 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(); > + > + int run(libcamera::Camera *camera, 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 4155889678ce1897..fe7d4f90dbf14ffd 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; > + ret = capture.run(camera.get(), 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',
diff --git a/src/cam/capture.cpp b/src/cam/capture.cpp new file mode 100644 index 0000000000000000..a88c00c81daba0b4 --- /dev/null +++ b/src/cam/capture.cpp @@ -0,0 +1,249 @@ +/* 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_(nullptr), writer_(nullptr), last_(0) +{ +} + +int Capture::run(Camera *camera, EventLoop *loop, + const OptionsParser::Options &options) +{ + int ret; + + if (!camera) { + std::cout << "Can't capture without a camera" << std::endl; + return -ENODEV; + } + + camera_ = camera; + + ret = prepareConfig(options); + if (ret) { + std::cout << "Failed to prepare camera configuration" << std::endl; + return -EINVAL; + } + + if (options.isSet(OptFile)) { + if (!options[OptFile].toString().empty()) + writer_ = new BufferWriter(options[OptFile]); + else + writer_ = new BufferWriter(); + } + + 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); + + ret = capture(loop); + + camera_->freeBuffers(); + config_.reset(); + + if (options.isSet(OptFile)) { + delete writer_; + writer_ = nullptr; + } + + 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(); + + 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 0000000000000000..de70f22e59f00bfd --- /dev/null +++ b/src/cam/capture.h @@ -0,0 +1,43 @@ +/* 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(); + + int run(libcamera::Camera *camera, 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 4155889678ce1897..fe7d4f90dbf14ffd 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; + ret = capture.run(camera.get(), 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',