Message ID | 20190323073125.25497-3-laurent.pinchart@ideasonboard.com |
---|---|
State | Accepted |
Headers | show |
Series |
|
Related | show |
Hi Laurent, Thanks for your work. 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 ? > > Signed-off-by: Laurent Pinchart <laurent.pinchart@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 > > diff --git a/src/meson.build b/src/meson.build > index a7f2e75de158..4e41fd3e7887 100644 > --- a/src/meson.build > +++ b/src/meson.build > @@ -1,2 +1,3 @@ > subdir('libcamera') > subdir('cam') > +subdir('qcam') > diff --git a/src/qcam/format_converter.cpp b/src/qcam/format_converter.cpp > new file mode 100644 > index 000000000000..0decad74f28e > --- /dev/null > +++ b/src/qcam/format_converter.cpp > @@ -0,0 +1,99 @@ > +/* SPDX-License-Identifier: GPL-2.0-or-later */ > +/* > + * Copyright (C) 2019, Google Inc. > + * > + * format_convert.cpp - qcam - Convert buffer to RGB > + */ > + > +#include <errno.h> > + > +#include <linux/videodev2.h> > + > +#include "format_converter.h" > + > +#define RGBSHIFT 8 > +#ifndef MAX > +#define MAX(a,b) ((a)>(b)?(a):(b)) > +#endif > +#ifndef MIN > +#define MIN(a,b) ((a)<(b)?(a):(b)) > +#endif > +#ifndef CLAMP > +#define CLAMP(a,low,high) MAX((low),MIN((high),(a))) > +#endif > +#ifndef CLIP > +#define CLIP(x) CLAMP(x,0,255) > +#endif > + > +static void yuv_to_rgb(int y, int u, int v, int *r, int *g, int *b) > +{ > + int c = y - 16; > + int d = u - 128; > + int e = v - 128; > + *r = CLIP(( 298 * c + 409 * e + 128) >> RGBSHIFT); > + *g = CLIP(( 298 * c - 100 * d - 208 * e + 128) >> RGBSHIFT); > + *b = CLIP(( 298 * c + 516 * d + 128) >> RGBSHIFT); > +} > + > +int buffer_to_rgb32(unsigned char *src, unsigned int format, > + unsigned int width, unsigned int height, > + unsigned char *dst) > +{ > + unsigned int src_x, src_y, dst_x, dst_y; > + unsigned int src_stride; > + unsigned int dst_stride; > + int y_pos, cb_pos, cr_pos; > + int r, g, b, y, cr, cb; > + > + switch (format) { > + case V4L2_PIX_FMT_VYUY: > + y_pos = 1; > + cb_pos = 2; > + break; > + case V4L2_PIX_FMT_YVYU: > + y_pos = 0; > + cb_pos = 3; > + break; > + case V4L2_PIX_FMT_UYVY: > + y_pos = 1; > + cb_pos = 0; > + break; > + case V4L2_PIX_FMT_YUYV: > + y_pos = 0; > + cb_pos = 1; > + break; > + default: > + return -EINVAL; > + }; > + > + cr_pos = (cb_pos + 2) % 4; > + src_stride = width * 2; > + dst_stride = width * 4; > + > + for (src_y = 0, dst_y = 0; dst_y < height; src_y++, dst_y++) { > + for (src_x = 0, dst_x = 0; dst_x < width; ) { > + cb = src[src_y * src_stride + src_x * 4 + cb_pos]; > + cr = src[src_y * src_stride + src_x * 4 + cr_pos]; > + > + y = src[src_y * src_stride + src_x * 4 + y_pos]; > + yuv_to_rgb(y, cb, cr, &r, &g, &b); > + dst[dst_y * dst_stride + 4 * dst_x + 0] = b; > + dst[dst_y * dst_stride + 4 * dst_x + 1] = g; > + dst[dst_y * dst_stride + 4 * dst_x + 2] = r; > + dst[dst_y * dst_stride + 4 * dst_x + 3] = 0xff; > + dst_x++; > + > + y = src[src_y * src_stride + src_x * 4 + y_pos + 2]; > + yuv_to_rgb(y, cb, cr, &r, &g, &b); > + dst[dst_y * dst_stride + 4 * dst_x + 0] = b; > + dst[dst_y * dst_stride + 4 * dst_x + 1] = g; > + dst[dst_y * dst_stride + 4 * dst_x + 2] = r; > + dst[dst_y * dst_stride + 4 * dst_x + 3] = 0xff; > + dst_x++; > + > + src_x++; > + } > + } > + > + return 0; > +} > diff --git a/src/qcam/format_converter.h b/src/qcam/format_converter.h > new file mode 100644 > index 000000000000..3904b139a4fd > --- /dev/null > +++ b/src/qcam/format_converter.h > @@ -0,0 +1,14 @@ > +/* SPDX-License-Identifier: GPL-2.0-or-later */ > +/* > + * Copyright (C) 2019, Google Inc. > + * > + * format_convert.h - qcam - Convert buffer to RGB > + */ > +#ifndef __QCAM_FORMAT_CONVERTER_H__ > +#define __QCAM_FORMAT_CONVERTER_H__ > + > +int buffer_to_rgb32(unsigned char *src, unsigned int format, > + unsigned int width, unsigned int height, > + unsigned char *dst); > + > +#endif /* __QCAM_FORMAT_CONVERTER_H__ */ > diff --git a/src/qcam/main.cpp b/src/qcam/main.cpp > new file mode 100644 > index 000000000000..106b8a162d9f > --- /dev/null > +++ b/src/qcam/main.cpp > @@ -0,0 +1,75 @@ > +/* SPDX-License-Identifier: GPL-2.0-or-later */ > +/* > + * Copyright (C) 2019, Google Inc. > + * > + * main.cpp - cam - The libcamera swiss army knife > + */ > + > +#include <iostream> > +#include <signal.h> > +#include <string.h> > + > +#include <QApplication> > + > +#include <libcamera/camera_manager.h> > + > +#include "main_window.h" > +#include "../cam/options.h" > +#include "qt_event_dispatcher.h" > + > +void signalHandler(int signal) > +{ > + std::cout << "Exiting" << std::endl; > + qApp->quit(); > +} > + > +OptionsParser::Options parseOptions(int argc, char *argv[]) > +{ > + OptionsParser parser; > + parser.addOption(OptCamera, OptionString, > + "Specify which camera to operate on", "camera", > + ArgumentRequired, "camera"); > + parser.addOption(OptHelp, OptionNone, "Display this help message", > + "help"); > + > + OptionsParser::Options options = parser.parse(argc, argv); > + if (options.isSet(OptHelp)) > + parser.usage(); > + > + return options; > +} > + > +int main(int argc, char **argv) > +{ > + QApplication app(argc, argv); > + int ret; > + > + OptionsParser::Options options = parseOptions(argc, argv); > + if (!options.valid()) > + return EXIT_FAILURE; > + if (options.isSet(OptHelp)) > + return 0; > + > + struct sigaction sa = {}; > + sa.sa_handler = &signalHandler; > + sigaction(SIGINT, &sa, nullptr); > + > + std::unique_ptr<EventDispatcher> dispatcher(new QtEventDispatcher()); > + CameraManager *cm = CameraManager::instance(); > + cm->setEventDispatcher(std::move(dispatcher)); > + > + ret = cm->start(); > + if (ret) { > + std::cout << "Failed to start camera manager: " > + << strerror(-ret) << std::endl; > + return EXIT_FAILURE; > + } > + > + MainWindow *mainWindow = new MainWindow(options); > + mainWindow->show(); > + ret = app.exec(); > + delete mainWindow; > + > + cm->stop(); > + return ret; > +} > 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 > + } > + > + 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. > + } > + > + 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. > + 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; > +} > diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h > new file mode 100644 > index 000000000000..5e27a8fd6b4e > --- /dev/null > +++ b/src/qcam/main_window.h > @@ -0,0 +1,54 @@ > +/* SPDX-License-Identifier: GPL-2.0-or-later */ > +/* > + * Copyright (C) 2019, Google Inc. > + * > + * main_window.h - qcam - Main application window > + */ > +#ifndef __QCAM_MAIN_WINDOW_H__ > +#define __QCAM_MAIN_WINDOW_H__ > + > +#include <map> > + > +#include <QMainWindow> > + > +#include <libcamera/camera.h> > +#include <libcamera/stream.h> > + > +#include "../cam/options.h" > + > +using namespace libcamera; > + > +class ViewFinder; > + > +enum { > + OptCamera = 'c', > + OptHelp = 'h', > +}; > + > +class MainWindow : public QMainWindow > +{ > +public: > + MainWindow(const OptionsParser::Options &options); > + ~MainWindow(); > + > +private: > + int openCamera(); > + > + int startCapture(); > + int configureStreams(Camera *camera, std::set<Stream *> &streams); > + void stopCapture(); > + > + void requestComplete(Request *request, > + const std::map<Stream *, Buffer *> &buffers); > + int display(Buffer *buffer); > + > + const OptionsParser::Options &options_; > + > + std::shared_ptr<Camera> camera_; > + bool isCapturing_; > + std::map<Stream *, StreamConfiguration> config_; > + > + ViewFinder *viewfinder_; > +}; > + > +#endif /* __QCAM_MAIN_WINDOW__ */ > diff --git a/src/qcam/meson.build b/src/qcam/meson.build > new file mode 100644 > index 000000000000..8a71cda3dfe5 > --- /dev/null > +++ b/src/qcam/meson.build > @@ -0,0 +1,19 @@ > +qcam_sources = files([ > + 'format_converter.cpp', > + 'main.cpp', > + 'main_window.cpp', > + '../cam/options.cpp', > + 'qt_event_dispatcher.cpp', > + 'viewfinder.cpp', > +]) > + > +import('qt5') > +qt5_dep = dependency('qt5', modules: ['Core', 'Gui', 'Widgets'], required : false) > + > +if qt5_dep.found() > + qcam = executable('qcam', qcam_sources, > + link_with : libcamera, > + include_directories : libcamera_includes, > + dependencies : qt5_dep, > + cpp_args : '-DQT_NO_KEYWORDS') > +endif > diff --git a/src/qcam/qt_event_dispatcher.cpp b/src/qcam/qt_event_dispatcher.cpp > new file mode 100644 > index 000000000000..5ba451bf88ce > --- /dev/null > +++ b/src/qcam/qt_event_dispatcher.cpp > @@ -0,0 +1,145 @@ > +/* SPDX-License-Identifier: GPL-2.0-or-later */ > +/* > + * Copyright (C) 2019, Google Inc. > + * > + * qt_event_dispatcher.cpp - qcam - Qt-based event dispatcher > + */ > + > +#include <iostream> > + > +#include <QAbstractEventDispatcher> > +#include <QCoreApplication> > +#include <QSocketNotifier> > +#include <QTimerEvent> > + > +#include <libcamera/event_notifier.h> > +#include <libcamera/timer.h> > + > +#include "qt_event_dispatcher.h" > + > +using namespace libcamera; > + > +QtEventDispatcher::QtEventDispatcher() > +{ > +} > + > +QtEventDispatcher::~QtEventDispatcher() > +{ > + for (auto &it : notifiers_) { > + NotifierSet &set = it.second; > + delete set.read.qnotifier; > + delete set.write.qnotifier; > + delete set.exception.qnotifier; > + } > +} > + > +void QtEventDispatcher::registerEventNotifier(EventNotifier *notifier) > +{ > + NotifierSet &set = notifiers_[notifier->fd()]; > + QSocketNotifier::Type qtype; > + void (QtEventDispatcher::*method)(int); > + NotifierPair *pair; > + > + switch (notifier->type()) { > + case EventNotifier::Read: > + default: > + qtype = QSocketNotifier::Read; > + method = &QtEventDispatcher::readNotifierActivated; > + pair = &set.read; > + break; > + > + case EventNotifier::Write: > + qtype = QSocketNotifier::Write; > + method = &QtEventDispatcher::writeNotifierActivated; > + pair = &set.write; > + break; > + > + case EventNotifier::Exception: > + qtype = QSocketNotifier::Exception; > + method = &QtEventDispatcher::exceptionNotifierActivated; > + pair = &set.exception; > + break; > + } > + > + QSocketNotifier *qnotifier = new QSocketNotifier(notifier->fd(), qtype); > + connect(qnotifier, &QSocketNotifier::activated, this, method); > + pair->notifier = notifier; > + pair->qnotifier = qnotifier; > +} > + > +void QtEventDispatcher::unregisterEventNotifier(EventNotifier *notifier) > +{ > + NotifierSet &set = notifiers_[notifier->fd()]; > + NotifierPair *pair; > + > + switch (notifier->type()) { > + case EventNotifier::Read: > + default: > + pair = &set.read; > + break; > + > + case EventNotifier::Write: > + pair = &set.write; > + break; > + > + case EventNotifier::Exception: > + pair = &set.exception; > + break; > + } > + > + delete pair->qnotifier; > + pair->qnotifier = nullptr; > + pair->notifier = nullptr; > +} > + > +void QtEventDispatcher::readNotifierActivated(int socket) > +{ > + EventNotifier *notifier = notifiers_[socket].read.notifier; > + notifier->activated.emit(notifier); > +} > + > +void QtEventDispatcher::writeNotifierActivated(int socket) > +{ > + EventNotifier *notifier = notifiers_[socket].write.notifier; > + notifier->activated.emit(notifier); > +} > + > +void QtEventDispatcher::exceptionNotifierActivated(int socket) > +{ > + EventNotifier *notifier = notifiers_[socket].exception.notifier; > + notifier->activated.emit(notifier); > +} > + > +void QtEventDispatcher::registerTimer(Timer *timer) > +{ > + int timerId = startTimer(timer->interval()); > + timers_[timerId] = timer; > + timerIds_[timer] = timerId; > +} > + > +void QtEventDispatcher::unregisterTimer(Timer *timer) > +{ > + auto it = timerIds_.find(timer); > + timers_.erase(it->second); > + killTimer(it->second); > + timerIds_.erase(it); > +} > + > +void QtEventDispatcher::timerEvent(QTimerEvent *event) > +{ > + auto it = timers_.find(event->timerId()); > + timerIds_.erase(it->second); > + killTimer(it->first); > + timers_.erase(it); > +} > + > +void QtEventDispatcher::processEvents() > +{ > + std::cout << "QtEventDispatcher::processEvents() should not be called" > + << std::endl; > +} > + > +void QtEventDispatcher::interrupt() > +{ > + QCoreApplication::eventDispatcher()->interrupt(); > +} > diff --git a/src/qcam/qt_event_dispatcher.h b/src/qcam/qt_event_dispatcher.h > new file mode 100644 > index 000000000000..b0f123e52d06 > --- /dev/null > +++ b/src/qcam/qt_event_dispatcher.h > @@ -0,0 +1,62 @@ > +/* SPDX-License-Identifier: GPL-2.0-or-later */ > +/* > + * Copyright (C) 2019, Google Inc. > + * > + * qt_event_dispatcher.h - qcam - Qt-based event dispatcher > + */ > +#ifndef __QCAM_QT_EVENT_DISPATCHER_H__ > +#define __QCAM_QT_EVENT_DISPATCHER_H__ > + > +#include <map> > + > +#include <libcamera/event_dispatcher.h> > + > +using namespace libcamera; > + > +class QSocketNotifier; > + > +class QtEventDispatcher final : public EventDispatcher, public QObject > +{ > +public: > + QtEventDispatcher(); > + ~QtEventDispatcher(); > + > + void registerEventNotifier(EventNotifier *notifier); > + void unregisterEventNotifier(EventNotifier *notifier); > + > + void registerTimer(Timer *timer); > + void unregisterTimer(Timer *timer); > + > + void processEvents(); > + > + void interrupt(); > + > +protected: > + void timerEvent(QTimerEvent *event); > + > +private: > + void readNotifierActivated(int socket); > + void writeNotifierActivated(int socket); > + void exceptionNotifierActivated(int socket); > + > + struct NotifierPair { > + NotifierPair() > + : notifier(nullptr), qnotifier(nullptr) > + { > + } > + EventNotifier *notifier; > + QSocketNotifier *qnotifier; > + }; > + > + struct NotifierSet { > + NotifierPair read; > + NotifierPair write; > + NotifierPair exception; > + }; > + > + std::map<int, NotifierSet> notifiers_; > + std::map<int, Timer *> timers_; > + std::map<Timer *, int> timerIds_; > +}; > + > +#endif /* __QCAM_QT_EVENT_DISPATCHER_H__ */ > 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. > + > + 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 > > _______________________________________________ > libcamera-devel mailing list > libcamera-devel@lists.libcamera.org > https://lists.libcamera.org/listinfo/libcamera-devel
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@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__ */
diff --git a/src/meson.build b/src/meson.build index a7f2e75de158..4e41fd3e7887 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,2 +1,3 @@ subdir('libcamera') subdir('cam') +subdir('qcam') diff --git a/src/qcam/format_converter.cpp b/src/qcam/format_converter.cpp new file mode 100644 index 000000000000..0decad74f28e --- /dev/null +++ b/src/qcam/format_converter.cpp @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * format_convert.cpp - qcam - Convert buffer to RGB + */ + +#include <errno.h> + +#include <linux/videodev2.h> + +#include "format_converter.h" + +#define RGBSHIFT 8 +#ifndef MAX +#define MAX(a,b) ((a)>(b)?(a):(b)) +#endif +#ifndef MIN +#define MIN(a,b) ((a)<(b)?(a):(b)) +#endif +#ifndef CLAMP +#define CLAMP(a,low,high) MAX((low),MIN((high),(a))) +#endif +#ifndef CLIP +#define CLIP(x) CLAMP(x,0,255) +#endif + +static void yuv_to_rgb(int y, int u, int v, int *r, int *g, int *b) +{ + int c = y - 16; + int d = u - 128; + int e = v - 128; + *r = CLIP(( 298 * c + 409 * e + 128) >> RGBSHIFT); + *g = CLIP(( 298 * c - 100 * d - 208 * e + 128) >> RGBSHIFT); + *b = CLIP(( 298 * c + 516 * d + 128) >> RGBSHIFT); +} + +int buffer_to_rgb32(unsigned char *src, unsigned int format, + unsigned int width, unsigned int height, + unsigned char *dst) +{ + unsigned int src_x, src_y, dst_x, dst_y; + unsigned int src_stride; + unsigned int dst_stride; + int y_pos, cb_pos, cr_pos; + int r, g, b, y, cr, cb; + + switch (format) { + case V4L2_PIX_FMT_VYUY: + y_pos = 1; + cb_pos = 2; + break; + case V4L2_PIX_FMT_YVYU: + y_pos = 0; + cb_pos = 3; + break; + case V4L2_PIX_FMT_UYVY: + y_pos = 1; + cb_pos = 0; + break; + case V4L2_PIX_FMT_YUYV: + y_pos = 0; + cb_pos = 1; + break; + default: + return -EINVAL; + }; + + cr_pos = (cb_pos + 2) % 4; + src_stride = width * 2; + dst_stride = width * 4; + + for (src_y = 0, dst_y = 0; dst_y < height; src_y++, dst_y++) { + for (src_x = 0, dst_x = 0; dst_x < width; ) { + cb = src[src_y * src_stride + src_x * 4 + cb_pos]; + cr = src[src_y * src_stride + src_x * 4 + cr_pos]; + + y = src[src_y * src_stride + src_x * 4 + y_pos]; + yuv_to_rgb(y, cb, cr, &r, &g, &b); + dst[dst_y * dst_stride + 4 * dst_x + 0] = b; + dst[dst_y * dst_stride + 4 * dst_x + 1] = g; + dst[dst_y * dst_stride + 4 * dst_x + 2] = r; + dst[dst_y * dst_stride + 4 * dst_x + 3] = 0xff; + dst_x++; + + y = src[src_y * src_stride + src_x * 4 + y_pos + 2]; + yuv_to_rgb(y, cb, cr, &r, &g, &b); + dst[dst_y * dst_stride + 4 * dst_x + 0] = b; + dst[dst_y * dst_stride + 4 * dst_x + 1] = g; + dst[dst_y * dst_stride + 4 * dst_x + 2] = r; + dst[dst_y * dst_stride + 4 * dst_x + 3] = 0xff; + dst_x++; + + src_x++; + } + } + + return 0; +} diff --git a/src/qcam/format_converter.h b/src/qcam/format_converter.h new file mode 100644 index 000000000000..3904b139a4fd --- /dev/null +++ b/src/qcam/format_converter.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * format_convert.h - qcam - Convert buffer to RGB + */ +#ifndef __QCAM_FORMAT_CONVERTER_H__ +#define __QCAM_FORMAT_CONVERTER_H__ + +int buffer_to_rgb32(unsigned char *src, unsigned int format, + unsigned int width, unsigned int height, + unsigned char *dst); + +#endif /* __QCAM_FORMAT_CONVERTER_H__ */ diff --git a/src/qcam/main.cpp b/src/qcam/main.cpp new file mode 100644 index 000000000000..106b8a162d9f --- /dev/null +++ b/src/qcam/main.cpp @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * main.cpp - cam - The libcamera swiss army knife + */ + +#include <iostream> +#include <signal.h> +#include <string.h> + +#include <QApplication> + +#include <libcamera/camera_manager.h> + +#include "main_window.h" +#include "../cam/options.h" +#include "qt_event_dispatcher.h" + +void signalHandler(int signal) +{ + std::cout << "Exiting" << std::endl; + qApp->quit(); +} + +OptionsParser::Options parseOptions(int argc, char *argv[]) +{ + OptionsParser parser; + parser.addOption(OptCamera, OptionString, + "Specify which camera to operate on", "camera", + ArgumentRequired, "camera"); + parser.addOption(OptHelp, OptionNone, "Display this help message", + "help"); + + OptionsParser::Options options = parser.parse(argc, argv); + if (options.isSet(OptHelp)) + parser.usage(); + + return options; +} + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + int ret; + + OptionsParser::Options options = parseOptions(argc, argv); + if (!options.valid()) + return EXIT_FAILURE; + if (options.isSet(OptHelp)) + return 0; + + struct sigaction sa = {}; + sa.sa_handler = &signalHandler; + sigaction(SIGINT, &sa, nullptr); + + std::unique_ptr<EventDispatcher> dispatcher(new QtEventDispatcher()); + CameraManager *cm = CameraManager::instance(); + cm->setEventDispatcher(std::move(dispatcher)); + + ret = cm->start(); + if (ret) { + std::cout << "Failed to start camera manager: " + << strerror(-ret) << std::endl; + return EXIT_FAILURE; + } + + MainWindow *mainWindow = new MainWindow(options); + mainWindow->show(); + ret = app.exec(); + delete mainWindow; + + cm->stop(); + return ret; +} 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(); + } + + 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]); + } + + 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); + 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; +} diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h new file mode 100644 index 000000000000..5e27a8fd6b4e --- /dev/null +++ b/src/qcam/main_window.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * main_window.h - qcam - Main application window + */ +#ifndef __QCAM_MAIN_WINDOW_H__ +#define __QCAM_MAIN_WINDOW_H__ + +#include <map> + +#include <QMainWindow> + +#include <libcamera/camera.h> +#include <libcamera/stream.h> + +#include "../cam/options.h" + +using namespace libcamera; + +class ViewFinder; + +enum { + OptCamera = 'c', + OptHelp = 'h', +}; + +class MainWindow : public QMainWindow +{ +public: + MainWindow(const OptionsParser::Options &options); + ~MainWindow(); + +private: + int openCamera(); + + int startCapture(); + int configureStreams(Camera *camera, std::set<Stream *> &streams); + void stopCapture(); + + void requestComplete(Request *request, + const std::map<Stream *, Buffer *> &buffers); + int display(Buffer *buffer); + + const OptionsParser::Options &options_; + + std::shared_ptr<Camera> camera_; + bool isCapturing_; + std::map<Stream *, StreamConfiguration> config_; + + ViewFinder *viewfinder_; +}; + +#endif /* __QCAM_MAIN_WINDOW__ */ diff --git a/src/qcam/meson.build b/src/qcam/meson.build new file mode 100644 index 000000000000..8a71cda3dfe5 --- /dev/null +++ b/src/qcam/meson.build @@ -0,0 +1,19 @@ +qcam_sources = files([ + 'format_converter.cpp', + 'main.cpp', + 'main_window.cpp', + '../cam/options.cpp', + 'qt_event_dispatcher.cpp', + 'viewfinder.cpp', +]) + +import('qt5') +qt5_dep = dependency('qt5', modules: ['Core', 'Gui', 'Widgets'], required : false) + +if qt5_dep.found() + qcam = executable('qcam', qcam_sources, + link_with : libcamera, + include_directories : libcamera_includes, + dependencies : qt5_dep, + cpp_args : '-DQT_NO_KEYWORDS') +endif diff --git a/src/qcam/qt_event_dispatcher.cpp b/src/qcam/qt_event_dispatcher.cpp new file mode 100644 index 000000000000..5ba451bf88ce --- /dev/null +++ b/src/qcam/qt_event_dispatcher.cpp @@ -0,0 +1,145 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * qt_event_dispatcher.cpp - qcam - Qt-based event dispatcher + */ + +#include <iostream> + +#include <QAbstractEventDispatcher> +#include <QCoreApplication> +#include <QSocketNotifier> +#include <QTimerEvent> + +#include <libcamera/event_notifier.h> +#include <libcamera/timer.h> + +#include "qt_event_dispatcher.h" + +using namespace libcamera; + +QtEventDispatcher::QtEventDispatcher() +{ +} + +QtEventDispatcher::~QtEventDispatcher() +{ + for (auto &it : notifiers_) { + NotifierSet &set = it.second; + delete set.read.qnotifier; + delete set.write.qnotifier; + delete set.exception.qnotifier; + } +} + +void QtEventDispatcher::registerEventNotifier(EventNotifier *notifier) +{ + NotifierSet &set = notifiers_[notifier->fd()]; + QSocketNotifier::Type qtype; + void (QtEventDispatcher::*method)(int); + NotifierPair *pair; + + switch (notifier->type()) { + case EventNotifier::Read: + default: + qtype = QSocketNotifier::Read; + method = &QtEventDispatcher::readNotifierActivated; + pair = &set.read; + break; + + case EventNotifier::Write: + qtype = QSocketNotifier::Write; + method = &QtEventDispatcher::writeNotifierActivated; + pair = &set.write; + break; + + case EventNotifier::Exception: + qtype = QSocketNotifier::Exception; + method = &QtEventDispatcher::exceptionNotifierActivated; + pair = &set.exception; + break; + } + + QSocketNotifier *qnotifier = new QSocketNotifier(notifier->fd(), qtype); + connect(qnotifier, &QSocketNotifier::activated, this, method); + pair->notifier = notifier; + pair->qnotifier = qnotifier; +} + +void QtEventDispatcher::unregisterEventNotifier(EventNotifier *notifier) +{ + NotifierSet &set = notifiers_[notifier->fd()]; + NotifierPair *pair; + + switch (notifier->type()) { + case EventNotifier::Read: + default: + pair = &set.read; + break; + + case EventNotifier::Write: + pair = &set.write; + break; + + case EventNotifier::Exception: + pair = &set.exception; + break; + } + + delete pair->qnotifier; + pair->qnotifier = nullptr; + pair->notifier = nullptr; +} + +void QtEventDispatcher::readNotifierActivated(int socket) +{ + EventNotifier *notifier = notifiers_[socket].read.notifier; + notifier->activated.emit(notifier); +} + +void QtEventDispatcher::writeNotifierActivated(int socket) +{ + EventNotifier *notifier = notifiers_[socket].write.notifier; + notifier->activated.emit(notifier); +} + +void QtEventDispatcher::exceptionNotifierActivated(int socket) +{ + EventNotifier *notifier = notifiers_[socket].exception.notifier; + notifier->activated.emit(notifier); +} + +void QtEventDispatcher::registerTimer(Timer *timer) +{ + int timerId = startTimer(timer->interval()); + timers_[timerId] = timer; + timerIds_[timer] = timerId; +} + +void QtEventDispatcher::unregisterTimer(Timer *timer) +{ + auto it = timerIds_.find(timer); + timers_.erase(it->second); + killTimer(it->second); + timerIds_.erase(it); +} + +void QtEventDispatcher::timerEvent(QTimerEvent *event) +{ + auto it = timers_.find(event->timerId()); + timerIds_.erase(it->second); + killTimer(it->first); + timers_.erase(it); +} + +void QtEventDispatcher::processEvents() +{ + std::cout << "QtEventDispatcher::processEvents() should not be called" + << std::endl; +} + +void QtEventDispatcher::interrupt() +{ + QCoreApplication::eventDispatcher()->interrupt(); +} diff --git a/src/qcam/qt_event_dispatcher.h b/src/qcam/qt_event_dispatcher.h new file mode 100644 index 000000000000..b0f123e52d06 --- /dev/null +++ b/src/qcam/qt_event_dispatcher.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * qt_event_dispatcher.h - qcam - Qt-based event dispatcher + */ +#ifndef __QCAM_QT_EVENT_DISPATCHER_H__ +#define __QCAM_QT_EVENT_DISPATCHER_H__ + +#include <map> + +#include <libcamera/event_dispatcher.h> + +using namespace libcamera; + +class QSocketNotifier; + +class QtEventDispatcher final : public EventDispatcher, public QObject +{ +public: + QtEventDispatcher(); + ~QtEventDispatcher(); + + void registerEventNotifier(EventNotifier *notifier); + void unregisterEventNotifier(EventNotifier *notifier); + + void registerTimer(Timer *timer); + void unregisterTimer(Timer *timer); + + void processEvents(); + + void interrupt(); + +protected: + void timerEvent(QTimerEvent *event); + +private: + void readNotifierActivated(int socket); + void writeNotifierActivated(int socket); + void exceptionNotifierActivated(int socket); + + struct NotifierPair { + NotifierPair() + : notifier(nullptr), qnotifier(nullptr) + { + } + EventNotifier *notifier; + QSocketNotifier *qnotifier; + }; + + struct NotifierSet { + NotifierPair read; + NotifierPair write; + NotifierPair exception; + }; + + std::map<int, NotifierSet> notifiers_; + std::map<int, Timer *> timers_; + std::map<Timer *, int> timerIds_; +}; + +#endif /* __QCAM_QT_EVENT_DISPATCHER_H__ */ 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()); + + 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__ */
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. Signed-off-by: Laurent Pinchart <laurent.pinchart@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