[libcamera-devel] [PATCH v2 1/2] cam: capture: Break out capture to a new class

Laurent Pinchart laurent.pinchart at ideasonboard.com
Thu May 23 17:23:36 CEST 2019


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 at ragnatech.se>
> Reviewed-by: Kieran Bingham <kieran.bingham at 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 at 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',

-- 
Regards,

Laurent Pinchart


More information about the libcamera-devel mailing list