qcam: Add Qt-based GUI application
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. Only streams in YUYV formats are supported for now. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Acked-by: Kieran Bingham <kieran.bingham@ideasonboard.com> Tested-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
This commit is contained in:
parent
ffef90a1c1
commit
97e8b3a2eb
11 changed files with 793 additions and 0 deletions
|
@ -1,2 +1,3 @@
|
||||||
subdir('libcamera')
|
subdir('libcamera')
|
||||||
subdir('cam')
|
subdir('cam')
|
||||||
|
subdir('qcam')
|
||||||
|
|
104
src/qcam/format_converter.cpp
Normal file
104
src/qcam/format_converter.cpp
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
/* 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
|
||||||
|
|
||||||
|
int FormatConverter::configure(unsigned int format, unsigned int width,
|
||||||
|
unsigned int height)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
width_ = width;
|
||||||
|
height_ = height;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FormatConverter::convert(const unsigned char *src, unsigned char *dst)
|
||||||
|
{
|
||||||
|
unsigned int src_x, src_y, dst_x, dst_y;
|
||||||
|
unsigned int src_stride;
|
||||||
|
unsigned int dst_stride;
|
||||||
|
unsigned int cr_pos;
|
||||||
|
int r, g, b, y, cr, cb;
|
||||||
|
|
||||||
|
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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
src/qcam/format_converter.h
Normal file
25
src/qcam/format_converter.h
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/* 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__
|
||||||
|
|
||||||
|
class FormatConverter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int configure(unsigned int format, unsigned int width,
|
||||||
|
unsigned int height);
|
||||||
|
|
||||||
|
void convert(const unsigned char *src, unsigned char *dst);
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned int width_;
|
||||||
|
unsigned int height_;
|
||||||
|
unsigned int y_pos_;
|
||||||
|
unsigned int cb_pos_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* __QCAM_FORMAT_CONVERTER_H__ */
|
75
src/qcam/main.cpp
Normal file
75
src/qcam/main.cpp
Normal file
|
@ -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;
|
||||||
|
}
|
228
src/qcam/main_window.cpp
Normal file
228
src/qcam/main_window.cpp
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
/* 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];
|
||||||
|
ret = viewfinder_->setFormat(sconf.pixelFormat, sconf.width, sconf.height);
|
||||||
|
if (ret < 0) {
|
||||||
|
std::cout << "Failed to set viewfinder format" << std::endl;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
54
src/qcam/main_window.h
Normal file
54
src/qcam/main_window.h
Normal file
|
@ -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__ */
|
19
src/qcam/meson.build
Normal file
19
src/qcam/meson.build
Normal file
|
@ -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
|
145
src/qcam/qt_event_dispatcher.cpp
Normal file
145
src/qcam/qt_event_dispatcher.cpp
Normal file
|
@ -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();
|
||||||
|
}
|
62
src/qcam/qt_event_dispatcher.h
Normal file
62
src/qcam/qt_event_dispatcher.h
Normal file
|
@ -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__ */
|
46
src/qcam/viewfinder.cpp
Normal file
46
src/qcam/viewfinder.cpp
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/* 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(const unsigned char *raw)
|
||||||
|
{
|
||||||
|
converter_.convert(raw, image_->bits());
|
||||||
|
|
||||||
|
QPixmap pixmap = QPixmap::fromImage(*image_);
|
||||||
|
setPixmap(pixmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ViewFinder::setFormat(unsigned int format, unsigned int width,
|
||||||
|
unsigned int height)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = converter_.configure(format, width, height);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
format_ = format;
|
||||||
|
width_ = width;
|
||||||
|
height_ = height;
|
||||||
|
|
||||||
|
setFixedSize(width, height);
|
||||||
|
|
||||||
|
delete image_;
|
||||||
|
image_ = new QImage(width, height, QImage::Format_RGB32);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
34
src/qcam/viewfinder.h
Normal file
34
src/qcam/viewfinder.h
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
/* 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>
|
||||||
|
|
||||||
|
#include "format_converter.h"
|
||||||
|
|
||||||
|
class QImage;
|
||||||
|
|
||||||
|
class ViewFinder : public QLabel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ViewFinder(QWidget *parent);
|
||||||
|
|
||||||
|
int setFormat(unsigned int format, unsigned int width,
|
||||||
|
unsigned int height);
|
||||||
|
void display(const unsigned char *rgb);
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned int format_;
|
||||||
|
unsigned int width_;
|
||||||
|
unsigned int height_;
|
||||||
|
|
||||||
|
FormatConverter converter_;
|
||||||
|
QImage *image_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* __QCAM_VIEWFINDER__ */
|
Loading…
Add table
Add a link
Reference in a new issue