[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