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

Message ID 20190523151306.22007-2-niklas.soderlund@ragnatech.se
State Superseded
Headers show
Series
  • cam: cleanup code structure
Related show

Commit Message

Niklas Söderlund May 23, 2019, 3:13 p.m. UTC
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

Comments

Laurent Pinchart May 23, 2019, 3:23 p.m. UTC | #1
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',

Patch

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',