[libcamera-devel] [PATCH 2/2] qcam: Add Qt-based GUI application

Laurent Pinchart laurent.pinchart at ideasonboard.com
Fri Mar 29 15:50:16 CET 2019


Hi Niklas,

On Wed, Mar 27, 2019 at 01:42:00AM +0100, Niklas Söderlund wrote:
> On 2019-03-23 09:31:25 +0200, Laurent Pinchart wrote:
> > qcam is a sample camera GUI application based on Qt. It demonstrates
> > integration of the Qt event loop with libcamera.
> > 
> > The application lets the user select a camera through the GUI, and then
> > captures a single stream from the camera and displays it in a window.
> 
> Maybe it's worth mentioning in the commit message that qcam only works 
> for YVYU pixelformats ?

I'll do so.

> > Signed-off-by: Laurent Pinchart <laurent.pinchart at ideasonboard.com>
> > ---
> >  src/meson.build                  |   1 +
> >  src/qcam/format_converter.cpp    |  99 ++++++++++++++
> >  src/qcam/format_converter.h      |  14 ++
> >  src/qcam/main.cpp                |  75 +++++++++++
> >  src/qcam/main_window.cpp         | 223 +++++++++++++++++++++++++++++++
> >  src/qcam/main_window.h           |  54 ++++++++
> >  src/qcam/meson.build             |  19 +++
> >  src/qcam/qt_event_dispatcher.cpp | 145 ++++++++++++++++++++
> >  src/qcam/qt_event_dispatcher.h   |  62 +++++++++
> >  src/qcam/viewfinder.cpp          |  38 ++++++
> >  src/qcam/viewfinder.h            |  31 +++++
> >  11 files changed, 761 insertions(+)
> >  create mode 100644 src/qcam/format_converter.cpp
> >  create mode 100644 src/qcam/format_converter.h
> >  create mode 100644 src/qcam/main.cpp
> >  create mode 100644 src/qcam/main_window.cpp
> >  create mode 100644 src/qcam/main_window.h
> >  create mode 100644 src/qcam/meson.build
> >  create mode 100644 src/qcam/qt_event_dispatcher.cpp
> >  create mode 100644 src/qcam/qt_event_dispatcher.h
> >  create mode 100644 src/qcam/viewfinder.cpp
> >  create mode 100644 src/qcam/viewfinder.h

[snip]

> > diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp
> > new file mode 100644
> > index 000000000000..d39dead76b56
> > --- /dev/null
> > +++ b/src/qcam/main_window.cpp
> > @@ -0,0 +1,223 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +/*
> > + * Copyright (C) 2019, Google Inc.
> > + *
> > + * main_window.cpp - qcam - Main application window
> > + */
> > +
> > +#include <iomanip>
> > +#include <iostream>
> > +#include <string>
> > +
> > +#include <QCoreApplication>
> > +#include <QInputDialog>
> > +#include <QTimer>
> > +
> > +#include <libcamera/camera_manager.h>
> > +
> > +#include "main_window.h"
> > +#include "viewfinder.h"
> > +
> > +using namespace libcamera;
> > +
> > +MainWindow::MainWindow(const OptionsParser::Options &options)
> > +	: options_(options), isCapturing_(false)
> > +{
> > +	int ret;
> > +
> > +	viewfinder_ = new ViewFinder(this);
> > +	setCentralWidget(viewfinder_);
> > +	viewfinder_->setFixedSize(500, 500);
> > +	adjustSize();
> > +
> > +	ret = openCamera();
> > +	if (!ret)
> > +		ret = startCapture();
> > +
> > +	if (ret < 0)
> > +		QTimer::singleShot(0, QCoreApplication::instance(),
> > +				   &QCoreApplication::quit);
> > +}
> > +
> > +MainWindow::~MainWindow()
> > +{
> > +	if (camera_) {
> > +		stopCapture();
> > +		camera_->release();
> > +		camera_.reset();
> 
> I know both release() and reset() is needed but it looks but ugly :-P

I agree. Any proposal to improve this ? :-)

> > +	}
> > +
> > +	CameraManager::instance()->stop();
> > +}
> > +
> > +int MainWindow::openCamera()
> > +{
> > +	CameraManager *cm = CameraManager::instance();
> > +	std::string cameraName;
> > +
> > +	if (!options_.isSet(OptCamera)) {
> > +		QStringList cameras;
> > +		bool result;
> > +
> > +		for (const std::shared_ptr<Camera> &cam : cm->cameras())
> > +			cameras.append(QString::fromStdString(cam->name()));
> > +
> > +		QString name = QInputDialog::getItem(this, "Select Camera",
> > +						     "Camera:", cameras, 0,
> > +						     false, &result);
> > +		if (!result)
> > +			return -EINVAL;
> > +
> > +		cameraName = name.toStdString();
> > +	} else {
> > +		cameraName = static_cast<std::string>(options_[OptCamera]);
> 
> Is this static_cast really needed? We have context here so the compiler 
> should do the right thing and use the operator std::string, right? The 
> only time I found my self the need to cast this is when redirecting it 
> to std::c{err,out} as no context exists so it picks operator int.

The compiler otherwise complains about an ambiguous overload for
operator=.

> > +	}
> > +
> > +	camera_ = cm->get(cameraName);
> > +	if (!camera_) {
> > +		std::cout << "Camera " << cameraName << " not found"
> > +			  << std::endl;
> > +		return -ENODEV;
> > +	}
> > +
> > +	if (camera_->acquire()) {
> > +		std::cout << "Failed to acquire camera" << std::endl;
> > +		camera_.reset();
> > +		return -EBUSY;
> > +	}
> > +
> > +	std::cout << "Using camera " << camera_->name() << std::endl;
> > +
> > +	camera_->requestCompleted.connect(this, &MainWindow::requestComplete);
> > +
> > +	return 0;
> > +}
> > +
> > +int MainWindow::startCapture()
> > +{
> > +	int ret;
> > +
> > +	Stream *stream = *camera_->streams().begin();
> > +	std::set<Stream *> streams{ stream };
> > +	config_ = camera_->streamConfiguration(streams);
> 
> This will conflict with my upcoming work to add stream usage hints, but 
> it should be easy to handle. Please scratch my fears discussed on IRC I 
> think we should merge qcam as soon as it's ready.

OK.

> > +	ret = camera_->configureStreams(config_);
> > +	if (ret < 0) {
> > +		std::cout << "Failed to configure camera" << std::endl;
> > +		return ret;
> > +	}
> > +
> > +	const StreamConfiguration &sconf = config_[stream];
> > +	viewfinder_->setFormat(sconf.pixelFormat, sconf.width, sconf.height);
> > +	adjustSize();
> > +
> > +	ret = camera_->allocateBuffers();
> > +	if (ret) {
> > +		std::cerr << "Failed to allocate buffers"
> > +			  << std::endl;
> > +		return ret;
> > +	}
> > +
> > +	BufferPool &pool = stream->bufferPool();
> > +	std::vector<Request *> requests;
> > +
> > +	for (Buffer &buffer : pool.buffers()) {
> > +		Request *request = camera_->createRequest();
> > +		if (!request) {
> > +			std::cerr << "Can't create request" << std::endl;
> > +			ret = -ENOMEM;
> > +			goto error;
> > +		}
> > +
> > +		std::map<Stream *, Buffer *> map;
> > +		map[stream] = &buffer;
> > +		ret = request->setBuffers(map);
> > +		if (ret < 0) {
> > +			std::cerr << "Can't set buffers for request" << std::endl;
> > +			goto error;
> > +		}
> > +
> > +		requests.push_back(request);
> > +	}
> > +
> > +	ret = camera_->start();
> > +	if (ret) {
> > +		std::cout << "Failed to start capture" << std::endl;
> > +		goto error;
> > +	}
> > +
> > +	for (Request *request : requests) {
> > +		ret = camera_->queueRequest(request);
> > +		if (ret < 0) {
> > +			std::cerr << "Can't queue request" << std::endl;
> > +			goto error;
> > +		}
> > +	}
> > +
> > +	isCapturing_ = true;
> > +	return 0;
> > +
> > +error:
> > +	for (Request *request : requests)
> > +		delete request;
> > +
> > +	camera_->freeBuffers();
> > +	return ret;
> > +}
> > +
> > +void MainWindow::stopCapture()
> > +{
> > +	if (!isCapturing_)
> > +		return;
> > +
> > +	int ret = camera_->stop();
> > +	if (ret)
> > +		std::cout << "Failed to stop capture" << std::endl;
> > +
> > +	camera_->freeBuffers();
> > +	isCapturing_ = false;
> > +}
> > +
> > +void MainWindow::requestComplete(Request *request,
> > +				 const std::map<Stream *, Buffer *> &buffers)
> > +{
> > +	static uint64_t last = 0;
> > +
> > +	if (request->status() == Request::RequestCancelled)
> > +		return;
> > +
> > +	Buffer *buffer = buffers.begin()->second;
> > +
> > +	double fps = buffer->timestamp() - last;
> > +	fps = last && fps ? 1000000000.0 / fps : 0.0;
> > +	last = buffer->timestamp();
> > +
> > +	std::cout << "seq: " << std::setw(6) << std::setfill('0') << buffer->sequence()
> > +		  << " buf: " << buffer->index()
> > +		  << " bytesused: " << buffer->bytesused()
> > +		  << " timestamp: " << buffer->timestamp()
> > +		  << " fps: " << std::fixed << std::setprecision(2) << fps
> > +		  << std::endl;
> > +
> > +	display(buffer);
> > +
> > +	request = camera_->createRequest();
> > +	if (!request) {
> > +		std::cerr << "Can't create request" << std::endl;
> > +		return;
> > +	}
> > +
> > +	request->setBuffers(buffers);
> > +	camera_->queueRequest(request);
> > +}
> > +
> > +int MainWindow::display(Buffer *buffer)
> > +{
> > +	if (buffer->planes().size() != 1)
> > +		return -EINVAL;
> > +
> > +	Plane &plane = buffer->planes().front();
> > +	unsigned char *raw = static_cast<unsigned char *>(plane.mem());
> > +	viewfinder_->display(raw);
> > +
> > +	return 0;
> > +}

[snip]

> > diff --git a/src/qcam/viewfinder.cpp b/src/qcam/viewfinder.cpp
> > new file mode 100644
> > index 000000000000..5db55ebbf098
> > --- /dev/null
> > +++ b/src/qcam/viewfinder.cpp
> > @@ -0,0 +1,38 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +/*
> > + * Copyright (C) 2019, Google Inc.
> > + *
> > + * viewfinder.cpp - qcam - Viewfinder
> > + */
> > +
> > +#include <QImage>
> > +#include <QPixmap>
> > +
> > +#include "format_converter.h"
> > +#include "viewfinder.h"
> > +
> > +ViewFinder::ViewFinder(QWidget *parent)
> > +	: QLabel(parent), format_(0), width_(0), height_(0), image_(nullptr)
> > +{
> > +}
> > +
> > +void ViewFinder::display(unsigned char *raw)
> > +{
> > +	buffer_to_rgb32(raw, format_, width_, height_, image_->bits());
> 
> You should check the return value here as if a none YVYU format is used 
> it will return -EINVAL and no image will be displayed which might 
> confuse the user.

I will instead do so in MainWindow::startCapture() and not start the
stream if YUYV is not supported.

> > +
> > +	QPixmap pixmap = QPixmap::fromImage(*image_);
> > +	setPixmap(pixmap);
> > +}
> > +
> > +void ViewFinder::setFormat(unsigned int format, unsigned int width,
> > +			   unsigned int height)
> > +{
> > +	format_ = format;
> > +	width_ = width;
> > +	height_ = height;
> > +
> > +	setFixedSize(width, height);
> > +
> > +	delete image_;
> > +	image_ = new QImage(width, height, QImage::Format_RGB32);
> > +}
> > diff --git a/src/qcam/viewfinder.h b/src/qcam/viewfinder.h
> > new file mode 100644
> > index 000000000000..ecae290a5043
> > --- /dev/null
> > +++ b/src/qcam/viewfinder.h
> > @@ -0,0 +1,31 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +/*
> > + * Copyright (C) 2019, Google Inc.
> > + *
> > + * viewfinder.h - qcam - Viewfinder
> > + */
> > +#ifndef __QCAM_VIEWFINDER_H__
> > +#define __QCAM_VIEWFINDER_H__
> > +
> > +#include <QLabel>
> > +
> > +class QImage;
> > +
> > +class ViewFinder : public QLabel
> > +{
> > +public:
> > +	ViewFinder(QWidget *parent);
> > +
> > +	void setFormat(unsigned int format, unsigned int width,
> > +		       unsigned int height);
> > +	void display(unsigned char *rgb);
> > +
> > +private:
> > +	unsigned int format_;
> > +	unsigned int width_;
> > +	unsigned int height_;
> > +
> > +	QImage *image_;
> > +};
> > +
> > +#endif /* __QCAM_VIEWFINDER__ */

-- 
Regards,

Laurent Pinchart


More information about the libcamera-devel mailing list