[{"id":1144,"web_url":"https://patchwork.libcamera.org/comment/1144/","msgid":"<20190327004200.GH9119@bigcity.dyn.berto.se>","date":"2019-03-27T00:42:00","subject":"Re: [libcamera-devel] [PATCH 2/2] qcam: Add Qt-based GUI application","submitter":{"id":5,"url":"https://patchwork.libcamera.org/api/people/5/","name":"Niklas Söderlund","email":"niklas.soderlund@ragnatech.se"},"content":"Hi Laurent,\n\nThanks for your work.\n\nOn 2019-03-23 09:31:25 +0200, Laurent Pinchart wrote:\n> qcam is a sample camera GUI application based on Qt. It demonstrates\n> integration of the Qt event loop with libcamera.\n> \n> The application lets the user select a camera through the GUI, and then\n> captures a single stream from the camera and displays it in a window.\n\nMaybe it's worth mentioning in the commit message that qcam only works \nfor YVYU pixelformats ?\n\n> \n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> ---\n>  src/meson.build                  |   1 +\n>  src/qcam/format_converter.cpp    |  99 ++++++++++++++\n>  src/qcam/format_converter.h      |  14 ++\n>  src/qcam/main.cpp                |  75 +++++++++++\n>  src/qcam/main_window.cpp         | 223 +++++++++++++++++++++++++++++++\n>  src/qcam/main_window.h           |  54 ++++++++\n>  src/qcam/meson.build             |  19 +++\n>  src/qcam/qt_event_dispatcher.cpp | 145 ++++++++++++++++++++\n>  src/qcam/qt_event_dispatcher.h   |  62 +++++++++\n>  src/qcam/viewfinder.cpp          |  38 ++++++\n>  src/qcam/viewfinder.h            |  31 +++++\n>  11 files changed, 761 insertions(+)\n>  create mode 100644 src/qcam/format_converter.cpp\n>  create mode 100644 src/qcam/format_converter.h\n>  create mode 100644 src/qcam/main.cpp\n>  create mode 100644 src/qcam/main_window.cpp\n>  create mode 100644 src/qcam/main_window.h\n>  create mode 100644 src/qcam/meson.build\n>  create mode 100644 src/qcam/qt_event_dispatcher.cpp\n>  create mode 100644 src/qcam/qt_event_dispatcher.h\n>  create mode 100644 src/qcam/viewfinder.cpp\n>  create mode 100644 src/qcam/viewfinder.h\n> \n> diff --git a/src/meson.build b/src/meson.build\n> index a7f2e75de158..4e41fd3e7887 100644\n> --- a/src/meson.build\n> +++ b/src/meson.build\n> @@ -1,2 +1,3 @@\n>  subdir('libcamera')\n>  subdir('cam')\n> +subdir('qcam')\n> diff --git a/src/qcam/format_converter.cpp b/src/qcam/format_converter.cpp\n> new file mode 100644\n> index 000000000000..0decad74f28e\n> --- /dev/null\n> +++ b/src/qcam/format_converter.cpp\n> @@ -0,0 +1,99 @@\n> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> +/*\n> + * Copyright (C) 2019, Google Inc.\n> + *\n> + * format_convert.cpp - qcam - Convert buffer to RGB\n> + */\n> +\n> +#include <errno.h>\n> +\n> +#include <linux/videodev2.h>\n> +\n> +#include \"format_converter.h\"\n> +\n> +#define RGBSHIFT\t\t8\n> +#ifndef MAX\n> +#define MAX(a,b)\t\t((a)>(b)?(a):(b))\n> +#endif\n> +#ifndef MIN\n> +#define MIN(a,b)\t\t((a)<(b)?(a):(b))\n> +#endif\n> +#ifndef CLAMP\n> +#define CLAMP(a,low,high)\tMAX((low),MIN((high),(a)))\n> +#endif\n> +#ifndef CLIP\n> +#define CLIP(x)\t\t\tCLAMP(x,0,255)\n> +#endif\n> +\n> +static void yuv_to_rgb(int y, int u, int v, int *r, int *g, int *b)\n> +{\n> +\tint c = y - 16;\n> +\tint d = u - 128;\n> +\tint e = v - 128;\n> +\t*r = CLIP(( 298 * c           + 409 * e + 128) >> RGBSHIFT);\n> +\t*g = CLIP(( 298 * c - 100 * d - 208 * e + 128) >> RGBSHIFT);\n> +\t*b = CLIP(( 298 * c + 516 * d           + 128) >> RGBSHIFT);\n> +}\n> +\n> +int buffer_to_rgb32(unsigned char *src, unsigned int format,\n> +\t\t    unsigned int width, unsigned int height,\n> +\t\t    unsigned char *dst)\n> +{\n> +\tunsigned int src_x, src_y, dst_x, dst_y;\n> +\tunsigned int src_stride;\n> +\tunsigned int dst_stride;\n> +\tint y_pos, cb_pos, cr_pos;\n> +\tint r, g, b, y, cr, cb;\n> +\n> +\tswitch (format) {\n> +\tcase V4L2_PIX_FMT_VYUY:\n> +\t\ty_pos = 1;\n> +\t\tcb_pos = 2;\n> +\t\tbreak;\n> +\tcase V4L2_PIX_FMT_YVYU:\n> +\t\ty_pos = 0;\n> +\t\tcb_pos = 3;\n> +\t\tbreak;\n> +\tcase V4L2_PIX_FMT_UYVY:\n> +\t\ty_pos = 1;\n> +\t\tcb_pos = 0;\n> +\t\tbreak;\n> +\tcase V4L2_PIX_FMT_YUYV:\n> +\t\ty_pos = 0;\n> +\t\tcb_pos = 1;\n> +\t\tbreak;\n> +\tdefault:\n> +\t\treturn -EINVAL;\n> +\t};\n> +\n> +\tcr_pos = (cb_pos + 2) % 4;\n> +\tsrc_stride = width * 2;\n> +\tdst_stride = width * 4;\n> +\n> +\tfor (src_y = 0, dst_y = 0; dst_y < height; src_y++, dst_y++) {\n> +\t\tfor (src_x = 0, dst_x = 0; dst_x < width; ) {\n> +\t\t\tcb = src[src_y * src_stride + src_x * 4 + cb_pos];\n> +\t\t\tcr = src[src_y * src_stride + src_x * 4 + cr_pos];\n> +\n> +\t\t\ty = src[src_y * src_stride + src_x * 4 + y_pos];\n> +\t\t\tyuv_to_rgb(y, cb, cr, &r, &g, &b);\n> +\t\t\tdst[dst_y * dst_stride + 4 * dst_x + 0] = b;\n> +\t\t\tdst[dst_y * dst_stride + 4 * dst_x + 1] = g;\n> +\t\t\tdst[dst_y * dst_stride + 4 * dst_x + 2] = r;\n> +\t\t\tdst[dst_y * dst_stride + 4 * dst_x + 3] = 0xff;\n> +\t\t\tdst_x++;\n> +\n> +\t\t\ty = src[src_y * src_stride + src_x * 4 + y_pos + 2];\n> +\t\t\tyuv_to_rgb(y, cb, cr, &r, &g, &b);\n> +\t\t\tdst[dst_y * dst_stride + 4 * dst_x + 0] = b;\n> +\t\t\tdst[dst_y * dst_stride + 4 * dst_x + 1] = g;\n> +\t\t\tdst[dst_y * dst_stride + 4 * dst_x + 2] = r;\n> +\t\t\tdst[dst_y * dst_stride + 4 * dst_x + 3] = 0xff;\n> +\t\t\tdst_x++;\n> +\n> +\t\t\tsrc_x++;\n> +\t\t}\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> diff --git a/src/qcam/format_converter.h b/src/qcam/format_converter.h\n> new file mode 100644\n> index 000000000000..3904b139a4fd\n> --- /dev/null\n> +++ b/src/qcam/format_converter.h\n> @@ -0,0 +1,14 @@\n> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> +/*\n> + * Copyright (C) 2019, Google Inc.\n> + *\n> + * format_convert.h - qcam - Convert buffer to RGB\n> + */\n> +#ifndef __QCAM_FORMAT_CONVERTER_H__\n> +#define __QCAM_FORMAT_CONVERTER_H__\n> +\n> +int buffer_to_rgb32(unsigned char *src, unsigned int format,\n> +\t\t    unsigned int width, unsigned int height,\n> +\t\t    unsigned char *dst);\n> +\n> +#endif /* __QCAM_FORMAT_CONVERTER_H__ */\n> diff --git a/src/qcam/main.cpp b/src/qcam/main.cpp\n> new file mode 100644\n> index 000000000000..106b8a162d9f\n> --- /dev/null\n> +++ b/src/qcam/main.cpp\n> @@ -0,0 +1,75 @@\n> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> +/*\n> + * Copyright (C) 2019, Google Inc.\n> + *\n> + * main.cpp - cam - The libcamera swiss army knife\n> + */\n> +\n> +#include <iostream>\n> +#include <signal.h>\n> +#include <string.h>\n> +\n> +#include <QApplication>\n> +\n> +#include <libcamera/camera_manager.h>\n> +\n> +#include \"main_window.h\"\n> +#include \"../cam/options.h\"\n> +#include \"qt_event_dispatcher.h\"\n> +\n> +void signalHandler(int signal)\n> +{\n> +\tstd::cout << \"Exiting\" << std::endl;\n> +\tqApp->quit();\n> +}\n> +\n> +OptionsParser::Options parseOptions(int argc, char *argv[])\n> +{\n> +\tOptionsParser parser;\n> +\tparser.addOption(OptCamera, OptionString,\n> +\t\t\t \"Specify which camera to operate on\", \"camera\",\n> +\t\t\t ArgumentRequired, \"camera\");\n> +\tparser.addOption(OptHelp, OptionNone, \"Display this help message\",\n> +\t\t\t \"help\");\n> +\n> +\tOptionsParser::Options options = parser.parse(argc, argv);\n> +\tif (options.isSet(OptHelp))\n> +\t\tparser.usage();\n> +\n> +\treturn options;\n> +}\n> +\n> +int main(int argc, char **argv)\n> +{\n> +\tQApplication app(argc, argv);\n> +\tint ret;\n> +\n> +\tOptionsParser::Options options = parseOptions(argc, argv);\n> +\tif (!options.valid())\n> +\t\treturn EXIT_FAILURE;\n> +\tif (options.isSet(OptHelp))\n> +\t\treturn 0;\n> +\n> +\tstruct sigaction sa = {};\n> +\tsa.sa_handler = &signalHandler;\n> +\tsigaction(SIGINT, &sa, nullptr);\n> +\n> +\tstd::unique_ptr<EventDispatcher> dispatcher(new QtEventDispatcher());\n> +\tCameraManager *cm = CameraManager::instance();\n> +\tcm->setEventDispatcher(std::move(dispatcher));\n> +\n> +\tret = cm->start();\n> +\tif (ret) {\n> +\t\tstd::cout << \"Failed to start camera manager: \"\n> +\t\t\t  << strerror(-ret) << std::endl;\n> +\t\treturn EXIT_FAILURE;\n> +\t}\n> +\n> +\tMainWindow *mainWindow = new MainWindow(options);\n> +\tmainWindow->show();\n> +\tret = app.exec();\n> +\tdelete mainWindow;\n> +\n> +\tcm->stop();\n> +\treturn ret;\n> +}\n> diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp\n> new file mode 100644\n> index 000000000000..d39dead76b56\n> --- /dev/null\n> +++ b/src/qcam/main_window.cpp\n> @@ -0,0 +1,223 @@\n> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> +/*\n> + * Copyright (C) 2019, Google Inc.\n> + *\n> + * main_window.cpp - qcam - Main application window\n> + */\n> +\n> +#include <iomanip>\n> +#include <iostream>\n> +#include <string>\n> +\n> +#include <QCoreApplication>\n> +#include <QInputDialog>\n> +#include <QTimer>\n> +\n> +#include <libcamera/camera_manager.h>\n> +\n> +#include \"main_window.h\"\n> +#include \"viewfinder.h\"\n> +\n> +using namespace libcamera;\n> +\n> +MainWindow::MainWindow(const OptionsParser::Options &options)\n> +\t: options_(options), isCapturing_(false)\n> +{\n> +\tint ret;\n> +\n> +\tviewfinder_ = new ViewFinder(this);\n> +\tsetCentralWidget(viewfinder_);\n> +\tviewfinder_->setFixedSize(500, 500);\n> +\tadjustSize();\n> +\n> +\tret = openCamera();\n> +\tif (!ret)\n> +\t\tret = startCapture();\n> +\n> +\tif (ret < 0)\n> +\t\tQTimer::singleShot(0, QCoreApplication::instance(),\n> +\t\t\t\t   &QCoreApplication::quit);\n> +}\n> +\n> +MainWindow::~MainWindow()\n> +{\n> +\tif (camera_) {\n> +\t\tstopCapture();\n> +\t\tcamera_->release();\n> +\t\tcamera_.reset();\n\nI know both release() and reset() is needed but it looks but ugly :-P\n\n> +\t}\n> +\n> +\tCameraManager::instance()->stop();\n> +}\n> +\n> +int MainWindow::openCamera()\n> +{\n> +\tCameraManager *cm = CameraManager::instance();\n> +\tstd::string cameraName;\n> +\n> +\tif (!options_.isSet(OptCamera)) {\n> +\t\tQStringList cameras;\n> +\t\tbool result;\n> +\n> +\t\tfor (const std::shared_ptr<Camera> &cam : cm->cameras())\n> +\t\t\tcameras.append(QString::fromStdString(cam->name()));\n> +\n> +\t\tQString name = QInputDialog::getItem(this, \"Select Camera\",\n> +\t\t\t\t\t\t     \"Camera:\", cameras, 0,\n> +\t\t\t\t\t\t     false, &result);\n> +\t\tif (!result)\n> +\t\t\treturn -EINVAL;\n> +\n> +\t\tcameraName = name.toStdString();\n> +\t} else {\n> +\t\tcameraName = static_cast<std::string>(options_[OptCamera]);\n\nIs this static_cast really needed? We have context here so the compiler \nshould do the right thing and use the operator std::string, right? The \nonly time I found my self the need to cast this is when redirecting it \nto std::c{err,out} as no context exists so it picks operator int.\n\n> +\t}\n> +\n> +\tcamera_ = cm->get(cameraName);\n> +\tif (!camera_) {\n> +\t\tstd::cout << \"Camera \" << cameraName << \" not found\"\n> +\t\t\t  << std::endl;\n> +\t\treturn -ENODEV;\n> +\t}\n> +\n> +\tif (camera_->acquire()) {\n> +\t\tstd::cout << \"Failed to acquire camera\" << std::endl;\n> +\t\tcamera_.reset();\n> +\t\treturn -EBUSY;\n> +\t}\n> +\n> +\tstd::cout << \"Using camera \" << camera_->name() << std::endl;\n> +\n> +\tcamera_->requestCompleted.connect(this, &MainWindow::requestComplete);\n> +\n> +\treturn 0;\n> +}\n> +\n> +int MainWindow::startCapture()\n> +{\n> +\tint ret;\n> +\n> +\tStream *stream = *camera_->streams().begin();\n> +\tstd::set<Stream *> streams{ stream };\n> +\tconfig_ = camera_->streamConfiguration(streams);\n\nThis will conflict with my upcoming work to add stream usage hints, but \nit should be easy to handle. Please scratch my fears discussed on IRC I \nthink we should merge qcam as soon as it's ready.\n\n> +\tret = camera_->configureStreams(config_);\n> +\tif (ret < 0) {\n> +\t\tstd::cout << \"Failed to configure camera\" << std::endl;\n> +\t\treturn ret;\n> +\t}\n> +\n> +\tconst StreamConfiguration &sconf = config_[stream];\n> +\tviewfinder_->setFormat(sconf.pixelFormat, sconf.width, sconf.height);\n> +\tadjustSize();\n> +\n> +\tret = camera_->allocateBuffers();\n> +\tif (ret) {\n> +\t\tstd::cerr << \"Failed to allocate buffers\"\n> +\t\t\t  << std::endl;\n> +\t\treturn ret;\n> +\t}\n> +\n> +\tBufferPool &pool = stream->bufferPool();\n> +\tstd::vector<Request *> requests;\n> +\n> +\tfor (Buffer &buffer : pool.buffers()) {\n> +\t\tRequest *request = camera_->createRequest();\n> +\t\tif (!request) {\n> +\t\t\tstd::cerr << \"Can't create request\" << std::endl;\n> +\t\t\tret = -ENOMEM;\n> +\t\t\tgoto error;\n> +\t\t}\n> +\n> +\t\tstd::map<Stream *, Buffer *> map;\n> +\t\tmap[stream] = &buffer;\n> +\t\tret = request->setBuffers(map);\n> +\t\tif (ret < 0) {\n> +\t\t\tstd::cerr << \"Can't set buffers for request\" << std::endl;\n> +\t\t\tgoto error;\n> +\t\t}\n> +\n> +\t\trequests.push_back(request);\n> +\t}\n> +\n> +\tret = camera_->start();\n> +\tif (ret) {\n> +\t\tstd::cout << \"Failed to start capture\" << std::endl;\n> +\t\tgoto error;\n> +\t}\n> +\n> +\tfor (Request *request : requests) {\n> +\t\tret = camera_->queueRequest(request);\n> +\t\tif (ret < 0) {\n> +\t\t\tstd::cerr << \"Can't queue request\" << std::endl;\n> +\t\t\tgoto error;\n> +\t\t}\n> +\t}\n> +\n> +\tisCapturing_ = true;\n> +\treturn 0;\n> +\n> +error:\n> +\tfor (Request *request : requests)\n> +\t\tdelete request;\n> +\n> +\tcamera_->freeBuffers();\n> +\treturn ret;\n> +}\n> +\n> +void MainWindow::stopCapture()\n> +{\n> +\tif (!isCapturing_)\n> +\t\treturn;\n> +\n> +\tint ret = camera_->stop();\n> +\tif (ret)\n> +\t\tstd::cout << \"Failed to stop capture\" << std::endl;\n> +\n> +\tcamera_->freeBuffers();\n> +\tisCapturing_ = false;\n> +}\n> +\n> +void MainWindow::requestComplete(Request *request,\n> +\t\t\t\t const std::map<Stream *, Buffer *> &buffers)\n> +{\n> +\tstatic uint64_t last = 0;\n> +\n> +\tif (request->status() == Request::RequestCancelled)\n> +\t\treturn;\n> +\n> +\tBuffer *buffer = buffers.begin()->second;\n> +\n> +\tdouble fps = buffer->timestamp() - last;\n> +\tfps = last && fps ? 1000000000.0 / fps : 0.0;\n> +\tlast = buffer->timestamp();\n> +\n> +\tstd::cout << \"seq: \" << std::setw(6) << std::setfill('0') << buffer->sequence()\n> +\t\t  << \" buf: \" << buffer->index()\n> +\t\t  << \" bytesused: \" << buffer->bytesused()\n> +\t\t  << \" timestamp: \" << buffer->timestamp()\n> +\t\t  << \" fps: \" << std::fixed << std::setprecision(2) << fps\n> +\t\t  << std::endl;\n> +\n> +\tdisplay(buffer);\n> +\n> +\trequest = camera_->createRequest();\n> +\tif (!request) {\n> +\t\tstd::cerr << \"Can't create request\" << std::endl;\n> +\t\treturn;\n> +\t}\n> +\n> +\trequest->setBuffers(buffers);\n> +\tcamera_->queueRequest(request);\n> +}\n> +\n> +int MainWindow::display(Buffer *buffer)\n> +{\n> +\tif (buffer->planes().size() != 1)\n> +\t\treturn -EINVAL;\n> +\n> +\tPlane &plane = buffer->planes().front();\n> +\tunsigned char *raw = static_cast<unsigned char *>(plane.mem());\n> +\tviewfinder_->display(raw);\n> +\n> +\treturn 0;\n> +}\n> diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h\n> new file mode 100644\n> index 000000000000..5e27a8fd6b4e\n> --- /dev/null\n> +++ b/src/qcam/main_window.h\n> @@ -0,0 +1,54 @@\n> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> +/*\n> + * Copyright (C) 2019, Google Inc.\n> + *\n> + * main_window.h - qcam - Main application window\n> + */\n> +#ifndef __QCAM_MAIN_WINDOW_H__\n> +#define __QCAM_MAIN_WINDOW_H__\n> +\n> +#include <map>\n> +\n> +#include <QMainWindow>\n> +\n> +#include <libcamera/camera.h>\n> +#include <libcamera/stream.h>\n> +\n> +#include \"../cam/options.h\"\n> +\n> +using namespace libcamera;\n> +\n> +class ViewFinder;\n> +\n> +enum {\n> +\tOptCamera = 'c',\n> +\tOptHelp = 'h',\n> +};\n> +\n> +class MainWindow : public QMainWindow\n> +{\n> +public:\n> +\tMainWindow(const OptionsParser::Options &options);\n> +\t~MainWindow();\n> +\n> +private:\n> +\tint openCamera();\n> +\n> +\tint startCapture();\n> +\tint configureStreams(Camera *camera, std::set<Stream *> &streams);\n> +\tvoid stopCapture();\n> +\n> +\tvoid requestComplete(Request *request,\n> +\t\t\t     const std::map<Stream *, Buffer *> &buffers);\n> +\tint display(Buffer *buffer);\n> +\n> +\tconst OptionsParser::Options &options_;\n> +\n> +\tstd::shared_ptr<Camera> camera_;\n> +\tbool isCapturing_;\n> +\tstd::map<Stream *, StreamConfiguration> config_;\n> +\n> +\tViewFinder *viewfinder_;\n> +};\n> +\n> +#endif /* __QCAM_MAIN_WINDOW__ */\n> diff --git a/src/qcam/meson.build b/src/qcam/meson.build\n> new file mode 100644\n> index 000000000000..8a71cda3dfe5\n> --- /dev/null\n> +++ b/src/qcam/meson.build\n> @@ -0,0 +1,19 @@\n> +qcam_sources = files([\n> +    'format_converter.cpp',\n> +    'main.cpp',\n> +    'main_window.cpp',\n> +    '../cam/options.cpp',\n> +    'qt_event_dispatcher.cpp',\n> +    'viewfinder.cpp',\n> +])\n> +\n> +import('qt5')\n> +qt5_dep = dependency('qt5', modules: ['Core', 'Gui', 'Widgets'], required : false)\n> +\n> +if qt5_dep.found()\n> +    qcam  = executable('qcam', qcam_sources,\n> +                       link_with : libcamera,\n> +                       include_directories : libcamera_includes,\n> +                       dependencies : qt5_dep,\n> +                       cpp_args : '-DQT_NO_KEYWORDS')\n> +endif\n> diff --git a/src/qcam/qt_event_dispatcher.cpp b/src/qcam/qt_event_dispatcher.cpp\n> new file mode 100644\n> index 000000000000..5ba451bf88ce\n> --- /dev/null\n> +++ b/src/qcam/qt_event_dispatcher.cpp\n> @@ -0,0 +1,145 @@\n> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> +/*\n> + * Copyright (C) 2019, Google Inc.\n> + *\n> + * qt_event_dispatcher.cpp - qcam - Qt-based event dispatcher\n> + */\n> +\n> +#include <iostream>\n> +\n> +#include <QAbstractEventDispatcher>\n> +#include <QCoreApplication>\n> +#include <QSocketNotifier>\n> +#include <QTimerEvent>\n> +\n> +#include <libcamera/event_notifier.h>\n> +#include <libcamera/timer.h>\n> +\n> +#include \"qt_event_dispatcher.h\"\n> +\n> +using namespace libcamera;\n> +\n> +QtEventDispatcher::QtEventDispatcher()\n> +{\n> +}\n> +\n> +QtEventDispatcher::~QtEventDispatcher()\n> +{\n> +\tfor (auto &it : notifiers_) {\n> +\t\tNotifierSet &set = it.second;\n> +\t\tdelete set.read.qnotifier;\n> +\t\tdelete set.write.qnotifier;\n> +\t\tdelete set.exception.qnotifier;\n> +\t}\n> +}\n> +\n> +void QtEventDispatcher::registerEventNotifier(EventNotifier *notifier)\n> +{\n> +\tNotifierSet &set = notifiers_[notifier->fd()];\n> +\tQSocketNotifier::Type qtype;\n> +\tvoid (QtEventDispatcher::*method)(int);\n> +\tNotifierPair *pair;\n> +\n> +\tswitch (notifier->type()) {\n> +\tcase EventNotifier::Read:\n> +\tdefault:\n> +\t\tqtype = QSocketNotifier::Read;\n> +\t\tmethod = &QtEventDispatcher::readNotifierActivated;\n> +\t\tpair = &set.read;\n> +\t\tbreak;\n> +\n> +\tcase EventNotifier::Write:\n> +\t\tqtype = QSocketNotifier::Write;\n> +\t\tmethod = &QtEventDispatcher::writeNotifierActivated;\n> +\t\tpair = &set.write;\n> +\t\tbreak;\n> +\n> +\tcase EventNotifier::Exception:\n> +\t\tqtype = QSocketNotifier::Exception;\n> +\t\tmethod = &QtEventDispatcher::exceptionNotifierActivated;\n> +\t\tpair = &set.exception;\n> +\t\tbreak;\n> +\t}\n> +\n> +\tQSocketNotifier *qnotifier = new QSocketNotifier(notifier->fd(), qtype);\n> +\tconnect(qnotifier, &QSocketNotifier::activated, this, method);\n> +\tpair->notifier = notifier;\n> +\tpair->qnotifier = qnotifier;\n> +}\n> +\n> +void QtEventDispatcher::unregisterEventNotifier(EventNotifier *notifier)\n> +{\n> +\tNotifierSet &set = notifiers_[notifier->fd()];\n> +\tNotifierPair *pair;\n> +\n> +\tswitch (notifier->type()) {\n> +\tcase EventNotifier::Read:\n> +\tdefault:\n> +\t\tpair = &set.read;\n> +\t\tbreak;\n> +\n> +\tcase EventNotifier::Write:\n> +\t\tpair = &set.write;\n> +\t\tbreak;\n> +\n> +\tcase EventNotifier::Exception:\n> +\t\tpair = &set.exception;\n> +\t\tbreak;\n> +\t}\n> +\n> +\tdelete pair->qnotifier;\n> +\tpair->qnotifier = nullptr;\n> +\tpair->notifier = nullptr;\n> +}\n> +\n> +void QtEventDispatcher::readNotifierActivated(int socket)\n> +{\n> +\tEventNotifier *notifier = notifiers_[socket].read.notifier;\n> +\tnotifier->activated.emit(notifier);\n> +}\n> +\n> +void QtEventDispatcher::writeNotifierActivated(int socket)\n> +{\n> +\tEventNotifier *notifier = notifiers_[socket].write.notifier;\n> +\tnotifier->activated.emit(notifier);\n> +}\n> +\n> +void QtEventDispatcher::exceptionNotifierActivated(int socket)\n> +{\n> +\tEventNotifier *notifier = notifiers_[socket].exception.notifier;\n> +\tnotifier->activated.emit(notifier);\n> +}\n> +\n> +void QtEventDispatcher::registerTimer(Timer *timer)\n> +{\n> +\tint timerId = startTimer(timer->interval());\n> +\ttimers_[timerId] = timer;\n> +\ttimerIds_[timer] = timerId;\n> +}\n> +\n> +void QtEventDispatcher::unregisterTimer(Timer *timer)\n> +{\n> +\tauto it = timerIds_.find(timer);\n> +\ttimers_.erase(it->second);\n> +\tkillTimer(it->second);\n> +\ttimerIds_.erase(it);\n> +}\n> +\n> +void QtEventDispatcher::timerEvent(QTimerEvent *event)\n> +{\n> +\tauto it = timers_.find(event->timerId());\n> +\ttimerIds_.erase(it->second);\n> +\tkillTimer(it->first);\n> +\ttimers_.erase(it);\n> +}\n> +\n> +void QtEventDispatcher::processEvents()\n> +{\n> +\tstd::cout << \"QtEventDispatcher::processEvents() should not be called\"\n> +\t\t  << std::endl;\n> +}\n> +\n> +void QtEventDispatcher::interrupt()\n> +{\n> +\tQCoreApplication::eventDispatcher()->interrupt();\n> +}\n> diff --git a/src/qcam/qt_event_dispatcher.h b/src/qcam/qt_event_dispatcher.h\n> new file mode 100644\n> index 000000000000..b0f123e52d06\n> --- /dev/null\n> +++ b/src/qcam/qt_event_dispatcher.h\n> @@ -0,0 +1,62 @@\n> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> +/*\n> + * Copyright (C) 2019, Google Inc.\n> + *\n> + * qt_event_dispatcher.h - qcam - Qt-based event dispatcher\n> + */\n> +#ifndef __QCAM_QT_EVENT_DISPATCHER_H__\n> +#define __QCAM_QT_EVENT_DISPATCHER_H__\n> +\n> +#include <map>\n> +\n> +#include <libcamera/event_dispatcher.h>\n> +\n> +using namespace libcamera;\n> +\n> +class QSocketNotifier;\n> +\n> +class QtEventDispatcher final : public EventDispatcher, public QObject\n> +{\n> +public:\n> +\tQtEventDispatcher();\n> +\t~QtEventDispatcher();\n> +\n> +\tvoid registerEventNotifier(EventNotifier *notifier);\n> +\tvoid unregisterEventNotifier(EventNotifier *notifier);\n> +\n> +\tvoid registerTimer(Timer *timer);\n> +\tvoid unregisterTimer(Timer *timer);\n> +\n> +\tvoid processEvents();\n> +\n> +\tvoid interrupt();\n> +\n> +protected:\n> +\tvoid timerEvent(QTimerEvent *event);\n> +\n> +private:\n> +\tvoid readNotifierActivated(int socket);\n> +\tvoid writeNotifierActivated(int socket);\n> +\tvoid exceptionNotifierActivated(int socket);\n> +\n> +\tstruct NotifierPair {\n> +\t\tNotifierPair()\n> +\t\t\t: notifier(nullptr), qnotifier(nullptr)\n> +\t\t{\n> +\t\t}\n> +\t\tEventNotifier *notifier;\n> +\t\tQSocketNotifier *qnotifier;\n> +\t};\n> +\n> +\tstruct NotifierSet {\n> +\t\tNotifierPair read;\n> +\t\tNotifierPair write;\n> +\t\tNotifierPair exception;\n> +\t};\n> +\n> +\tstd::map<int, NotifierSet> notifiers_;\n> +\tstd::map<int, Timer *> timers_;\n> +\tstd::map<Timer *, int> timerIds_;\n> +};\n> +\n> +#endif /* __QCAM_QT_EVENT_DISPATCHER_H__ */\n> diff --git a/src/qcam/viewfinder.cpp b/src/qcam/viewfinder.cpp\n> new file mode 100644\n> index 000000000000..5db55ebbf098\n> --- /dev/null\n> +++ b/src/qcam/viewfinder.cpp\n> @@ -0,0 +1,38 @@\n> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> +/*\n> + * Copyright (C) 2019, Google Inc.\n> + *\n> + * viewfinder.cpp - qcam - Viewfinder\n> + */\n> +\n> +#include <QImage>\n> +#include <QPixmap>\n> +\n> +#include \"format_converter.h\"\n> +#include \"viewfinder.h\"\n> +\n> +ViewFinder::ViewFinder(QWidget *parent)\n> +\t: QLabel(parent), format_(0), width_(0), height_(0), image_(nullptr)\n> +{\n> +}\n> +\n> +void ViewFinder::display(unsigned char *raw)\n> +{\n> +\tbuffer_to_rgb32(raw, format_, width_, height_, image_->bits());\n\nYou should check the return value here as if a none YVYU format is used \nit will return -EINVAL and no image will be displayed which might \nconfuse the user.\n\n> +\n> +\tQPixmap pixmap = QPixmap::fromImage(*image_);\n> +\tsetPixmap(pixmap);\n> +}\n> +\n> +void ViewFinder::setFormat(unsigned int format, unsigned int width,\n> +\t\t\t   unsigned int height)\n> +{\n> +\tformat_ = format;\n> +\twidth_ = width;\n> +\theight_ = height;\n> +\n> +\tsetFixedSize(width, height);\n> +\n> +\tdelete image_;\n> +\timage_ = new QImage(width, height, QImage::Format_RGB32);\n> +}\n> diff --git a/src/qcam/viewfinder.h b/src/qcam/viewfinder.h\n> new file mode 100644\n> index 000000000000..ecae290a5043\n> --- /dev/null\n> +++ b/src/qcam/viewfinder.h\n> @@ -0,0 +1,31 @@\n> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> +/*\n> + * Copyright (C) 2019, Google Inc.\n> + *\n> + * viewfinder.h - qcam - Viewfinder\n> + */\n> +#ifndef __QCAM_VIEWFINDER_H__\n> +#define __QCAM_VIEWFINDER_H__\n> +\n> +#include <QLabel>\n> +\n> +class QImage;\n> +\n> +class ViewFinder : public QLabel\n> +{\n> +public:\n> +\tViewFinder(QWidget *parent);\n> +\n> +\tvoid setFormat(unsigned int format, unsigned int width,\n> +\t\t       unsigned int height);\n> +\tvoid display(unsigned char *rgb);\n> +\n> +private:\n> +\tunsigned int format_;\n> +\tunsigned int width_;\n> +\tunsigned int height_;\n> +\n> +\tQImage *image_;\n> +};\n> +\n> +#endif /* __QCAM_VIEWFINDER__ */\n> -- \n> Regards,\n> \n> Laurent Pinchart\n> \n> _______________________________________________\n> libcamera-devel mailing list\n> libcamera-devel@lists.libcamera.org\n> https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<niklas.soderlund@ragnatech.se>","Received":["from mail-lj1-x231.google.com (mail-lj1-x231.google.com\n\t[IPv6:2a00:1450:4864:20::231])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 1C936600FD\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 27 Mar 2019 01:42:02 +0100 (CET)","by mail-lj1-x231.google.com with SMTP id t13so12758080lji.2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 26 Mar 2019 17:42:02 -0700 (PDT)","from localhost (89-233-230-99.cust.bredband2.com. [89.233.230.99])\n\tby smtp.gmail.com with ESMTPSA id\n\tl21sm4214699lfh.30.2019.03.26.17.42.00\n\t(version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256);\n\tTue, 26 Mar 2019 17:42:00 -0700 (PDT)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=ragnatech-se.20150623.gappssmtp.com; s=20150623;\n\th=date:from:to:cc:subject:message-id:references:mime-version\n\t:content-disposition:content-transfer-encoding:in-reply-to\n\t:user-agent; bh=Y4A+X/WKh6DJiWAu3gcvaRCcGFi2hF60FxbzQN5g1Uc=;\n\tb=jkNKuzQTPjxBVa0Vx521ztp54aAbLjIK8XzzYLcXRjapCg8ihS3/5rRTgrFd3TfQYF\n\tR0Qv934gW9O1CWJnzSTBgC9EBOXS6fv5V+Ksuqljw9OwE61lwDnwqiEAbcpQsVODp+0Q\n\tsWJOAR9BfJjyPtrZz5eWzjdL7qnykFw+In5EfimCkopZLtxJ4ma521jgX+iKsdnDmonB\n\tzDXERra/9qhf+2O/r4YhNEwN3rN9GM1o6k7IdRLCQ2dxh+Cw9BvUE25v6iwQtGvW0Du1\n\tCeTTfGHKhUY+Wmnn7/yaVSMICPFkv9FBfGXQEaQdg09mzuSAr/XPn46gF603fjdljlgk\n\tADSg==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20161025;\n\th=x-gm-message-state:date:from:to:cc:subject:message-id:references\n\t:mime-version:content-disposition:content-transfer-encoding\n\t:in-reply-to:user-agent;\n\tbh=Y4A+X/WKh6DJiWAu3gcvaRCcGFi2hF60FxbzQN5g1Uc=;\n\tb=glK+lKBPJ0LGbmIu/H724InvvfqzlX3ceGNZboKb/CA4VoiUTsAWm+1oncwY7uK7Q1\n\ttqxxuIlPdaa343gwNEmm2aTBpogbwZ+2wJg0bnqFd1a20z1Jo0pi5uNfLzXkaXluSJEY\n\t24OI5uf+oQiWD4PG0VVjn/hFVFjTSFan/5GiHNj4GPTdIrRlh8ONehBpEItiBX4TIX6X\n\tJQNU9CFVt4MXcBP2aUDUwtUp80hR0Eee0dkb7xpoG4/oHk6vMhfLPd3R3BILhRXpgU7s\n\t4I/576rVpddYLq0MLM2EgYOCkv+XcYKQKqSeIDQWanDEBpLnzAKoRZxixWIEFXXhXvmI\n\tIXLw==","X-Gm-Message-State":"APjAAAV26Kz2L0UkULidWlFjPv7lmkB6rpYs4eylRnrFzSjOBdyO/r4N\n\thWkxhXeDp65IwAZdzwN19HyHl6Bsbfc=","X-Google-Smtp-Source":"APXvYqwPuMJI3tdF8LqA5VoWqe5FJ/nwcERJLaVvuJQNbRQ084Rv+PCrtRnx8ChgT/D/EChuOLN36A==","X-Received":"by 2002:a2e:85d2:: with SMTP id\n\th18mr17486594ljj.128.1553647321365; \n\tTue, 26 Mar 2019 17:42:01 -0700 (PDT)","Date":"Wed, 27 Mar 2019 01:42:00 +0100","From":"Niklas =?iso-8859-1?q?S=F6derlund?= <niklas.soderlund@ragnatech.se>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20190327004200.GH9119@bigcity.dyn.berto.se>","References":"<20190323073125.25497-1-laurent.pinchart@ideasonboard.com>\n\t<20190323073125.25497-3-laurent.pinchart@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=iso-8859-1","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<20190323073125.25497-3-laurent.pinchart@ideasonboard.com>","User-Agent":"Mutt/1.11.3 (2019-02-01)","Subject":"Re: [libcamera-devel] [PATCH 2/2] qcam: Add Qt-based GUI application","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.23","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Wed, 27 Mar 2019 00:42:02 -0000"}},{"id":1153,"web_url":"https://patchwork.libcamera.org/comment/1153/","msgid":"<20190329145016.GC4934@pendragon.ideasonboard.com>","date":"2019-03-29T14:50:16","subject":"Re: [libcamera-devel] [PATCH 2/2] qcam: Add Qt-based GUI application","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Niklas,\n\nOn Wed, Mar 27, 2019 at 01:42:00AM +0100, Niklas Söderlund wrote:\n> On 2019-03-23 09:31:25 +0200, Laurent Pinchart wrote:\n> > qcam is a sample camera GUI application based on Qt. It demonstrates\n> > integration of the Qt event loop with libcamera.\n> > \n> > The application lets the user select a camera through the GUI, and then\n> > captures a single stream from the camera and displays it in a window.\n> \n> Maybe it's worth mentioning in the commit message that qcam only works \n> for YVYU pixelformats ?\n\nI'll do so.\n\n> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> > ---\n> >  src/meson.build                  |   1 +\n> >  src/qcam/format_converter.cpp    |  99 ++++++++++++++\n> >  src/qcam/format_converter.h      |  14 ++\n> >  src/qcam/main.cpp                |  75 +++++++++++\n> >  src/qcam/main_window.cpp         | 223 +++++++++++++++++++++++++++++++\n> >  src/qcam/main_window.h           |  54 ++++++++\n> >  src/qcam/meson.build             |  19 +++\n> >  src/qcam/qt_event_dispatcher.cpp | 145 ++++++++++++++++++++\n> >  src/qcam/qt_event_dispatcher.h   |  62 +++++++++\n> >  src/qcam/viewfinder.cpp          |  38 ++++++\n> >  src/qcam/viewfinder.h            |  31 +++++\n> >  11 files changed, 761 insertions(+)\n> >  create mode 100644 src/qcam/format_converter.cpp\n> >  create mode 100644 src/qcam/format_converter.h\n> >  create mode 100644 src/qcam/main.cpp\n> >  create mode 100644 src/qcam/main_window.cpp\n> >  create mode 100644 src/qcam/main_window.h\n> >  create mode 100644 src/qcam/meson.build\n> >  create mode 100644 src/qcam/qt_event_dispatcher.cpp\n> >  create mode 100644 src/qcam/qt_event_dispatcher.h\n> >  create mode 100644 src/qcam/viewfinder.cpp\n> >  create mode 100644 src/qcam/viewfinder.h\n\n[snip]\n\n> > diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp\n> > new file mode 100644\n> > index 000000000000..d39dead76b56\n> > --- /dev/null\n> > +++ b/src/qcam/main_window.cpp\n> > @@ -0,0 +1,223 @@\n> > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > +/*\n> > + * Copyright (C) 2019, Google Inc.\n> > + *\n> > + * main_window.cpp - qcam - Main application window\n> > + */\n> > +\n> > +#include <iomanip>\n> > +#include <iostream>\n> > +#include <string>\n> > +\n> > +#include <QCoreApplication>\n> > +#include <QInputDialog>\n> > +#include <QTimer>\n> > +\n> > +#include <libcamera/camera_manager.h>\n> > +\n> > +#include \"main_window.h\"\n> > +#include \"viewfinder.h\"\n> > +\n> > +using namespace libcamera;\n> > +\n> > +MainWindow::MainWindow(const OptionsParser::Options &options)\n> > +\t: options_(options), isCapturing_(false)\n> > +{\n> > +\tint ret;\n> > +\n> > +\tviewfinder_ = new ViewFinder(this);\n> > +\tsetCentralWidget(viewfinder_);\n> > +\tviewfinder_->setFixedSize(500, 500);\n> > +\tadjustSize();\n> > +\n> > +\tret = openCamera();\n> > +\tif (!ret)\n> > +\t\tret = startCapture();\n> > +\n> > +\tif (ret < 0)\n> > +\t\tQTimer::singleShot(0, QCoreApplication::instance(),\n> > +\t\t\t\t   &QCoreApplication::quit);\n> > +}\n> > +\n> > +MainWindow::~MainWindow()\n> > +{\n> > +\tif (camera_) {\n> > +\t\tstopCapture();\n> > +\t\tcamera_->release();\n> > +\t\tcamera_.reset();\n> \n> I know both release() and reset() is needed but it looks but ugly :-P\n\nI agree. Any proposal to improve this ? :-)\n\n> > +\t}\n> > +\n> > +\tCameraManager::instance()->stop();\n> > +}\n> > +\n> > +int MainWindow::openCamera()\n> > +{\n> > +\tCameraManager *cm = CameraManager::instance();\n> > +\tstd::string cameraName;\n> > +\n> > +\tif (!options_.isSet(OptCamera)) {\n> > +\t\tQStringList cameras;\n> > +\t\tbool result;\n> > +\n> > +\t\tfor (const std::shared_ptr<Camera> &cam : cm->cameras())\n> > +\t\t\tcameras.append(QString::fromStdString(cam->name()));\n> > +\n> > +\t\tQString name = QInputDialog::getItem(this, \"Select Camera\",\n> > +\t\t\t\t\t\t     \"Camera:\", cameras, 0,\n> > +\t\t\t\t\t\t     false, &result);\n> > +\t\tif (!result)\n> > +\t\t\treturn -EINVAL;\n> > +\n> > +\t\tcameraName = name.toStdString();\n> > +\t} else {\n> > +\t\tcameraName = static_cast<std::string>(options_[OptCamera]);\n> \n> Is this static_cast really needed? We have context here so the compiler \n> should do the right thing and use the operator std::string, right? The \n> only time I found my self the need to cast this is when redirecting it \n> to std::c{err,out} as no context exists so it picks operator int.\n\nThe compiler otherwise complains about an ambiguous overload for\noperator=.\n\n> > +\t}\n> > +\n> > +\tcamera_ = cm->get(cameraName);\n> > +\tif (!camera_) {\n> > +\t\tstd::cout << \"Camera \" << cameraName << \" not found\"\n> > +\t\t\t  << std::endl;\n> > +\t\treturn -ENODEV;\n> > +\t}\n> > +\n> > +\tif (camera_->acquire()) {\n> > +\t\tstd::cout << \"Failed to acquire camera\" << std::endl;\n> > +\t\tcamera_.reset();\n> > +\t\treturn -EBUSY;\n> > +\t}\n> > +\n> > +\tstd::cout << \"Using camera \" << camera_->name() << std::endl;\n> > +\n> > +\tcamera_->requestCompleted.connect(this, &MainWindow::requestComplete);\n> > +\n> > +\treturn 0;\n> > +}\n> > +\n> > +int MainWindow::startCapture()\n> > +{\n> > +\tint ret;\n> > +\n> > +\tStream *stream = *camera_->streams().begin();\n> > +\tstd::set<Stream *> streams{ stream };\n> > +\tconfig_ = camera_->streamConfiguration(streams);\n> \n> This will conflict with my upcoming work to add stream usage hints, but \n> it should be easy to handle. Please scratch my fears discussed on IRC I \n> think we should merge qcam as soon as it's ready.\n\nOK.\n\n> > +\tret = camera_->configureStreams(config_);\n> > +\tif (ret < 0) {\n> > +\t\tstd::cout << \"Failed to configure camera\" << std::endl;\n> > +\t\treturn ret;\n> > +\t}\n> > +\n> > +\tconst StreamConfiguration &sconf = config_[stream];\n> > +\tviewfinder_->setFormat(sconf.pixelFormat, sconf.width, sconf.height);\n> > +\tadjustSize();\n> > +\n> > +\tret = camera_->allocateBuffers();\n> > +\tif (ret) {\n> > +\t\tstd::cerr << \"Failed to allocate buffers\"\n> > +\t\t\t  << std::endl;\n> > +\t\treturn ret;\n> > +\t}\n> > +\n> > +\tBufferPool &pool = stream->bufferPool();\n> > +\tstd::vector<Request *> requests;\n> > +\n> > +\tfor (Buffer &buffer : pool.buffers()) {\n> > +\t\tRequest *request = camera_->createRequest();\n> > +\t\tif (!request) {\n> > +\t\t\tstd::cerr << \"Can't create request\" << std::endl;\n> > +\t\t\tret = -ENOMEM;\n> > +\t\t\tgoto error;\n> > +\t\t}\n> > +\n> > +\t\tstd::map<Stream *, Buffer *> map;\n> > +\t\tmap[stream] = &buffer;\n> > +\t\tret = request->setBuffers(map);\n> > +\t\tif (ret < 0) {\n> > +\t\t\tstd::cerr << \"Can't set buffers for request\" << std::endl;\n> > +\t\t\tgoto error;\n> > +\t\t}\n> > +\n> > +\t\trequests.push_back(request);\n> > +\t}\n> > +\n> > +\tret = camera_->start();\n> > +\tif (ret) {\n> > +\t\tstd::cout << \"Failed to start capture\" << std::endl;\n> > +\t\tgoto error;\n> > +\t}\n> > +\n> > +\tfor (Request *request : requests) {\n> > +\t\tret = camera_->queueRequest(request);\n> > +\t\tif (ret < 0) {\n> > +\t\t\tstd::cerr << \"Can't queue request\" << std::endl;\n> > +\t\t\tgoto error;\n> > +\t\t}\n> > +\t}\n> > +\n> > +\tisCapturing_ = true;\n> > +\treturn 0;\n> > +\n> > +error:\n> > +\tfor (Request *request : requests)\n> > +\t\tdelete request;\n> > +\n> > +\tcamera_->freeBuffers();\n> > +\treturn ret;\n> > +}\n> > +\n> > +void MainWindow::stopCapture()\n> > +{\n> > +\tif (!isCapturing_)\n> > +\t\treturn;\n> > +\n> > +\tint ret = camera_->stop();\n> > +\tif (ret)\n> > +\t\tstd::cout << \"Failed to stop capture\" << std::endl;\n> > +\n> > +\tcamera_->freeBuffers();\n> > +\tisCapturing_ = false;\n> > +}\n> > +\n> > +void MainWindow::requestComplete(Request *request,\n> > +\t\t\t\t const std::map<Stream *, Buffer *> &buffers)\n> > +{\n> > +\tstatic uint64_t last = 0;\n> > +\n> > +\tif (request->status() == Request::RequestCancelled)\n> > +\t\treturn;\n> > +\n> > +\tBuffer *buffer = buffers.begin()->second;\n> > +\n> > +\tdouble fps = buffer->timestamp() - last;\n> > +\tfps = last && fps ? 1000000000.0 / fps : 0.0;\n> > +\tlast = buffer->timestamp();\n> > +\n> > +\tstd::cout << \"seq: \" << std::setw(6) << std::setfill('0') << buffer->sequence()\n> > +\t\t  << \" buf: \" << buffer->index()\n> > +\t\t  << \" bytesused: \" << buffer->bytesused()\n> > +\t\t  << \" timestamp: \" << buffer->timestamp()\n> > +\t\t  << \" fps: \" << std::fixed << std::setprecision(2) << fps\n> > +\t\t  << std::endl;\n> > +\n> > +\tdisplay(buffer);\n> > +\n> > +\trequest = camera_->createRequest();\n> > +\tif (!request) {\n> > +\t\tstd::cerr << \"Can't create request\" << std::endl;\n> > +\t\treturn;\n> > +\t}\n> > +\n> > +\trequest->setBuffers(buffers);\n> > +\tcamera_->queueRequest(request);\n> > +}\n> > +\n> > +int MainWindow::display(Buffer *buffer)\n> > +{\n> > +\tif (buffer->planes().size() != 1)\n> > +\t\treturn -EINVAL;\n> > +\n> > +\tPlane &plane = buffer->planes().front();\n> > +\tunsigned char *raw = static_cast<unsigned char *>(plane.mem());\n> > +\tviewfinder_->display(raw);\n> > +\n> > +\treturn 0;\n> > +}\n\n[snip]\n\n> > diff --git a/src/qcam/viewfinder.cpp b/src/qcam/viewfinder.cpp\n> > new file mode 100644\n> > index 000000000000..5db55ebbf098\n> > --- /dev/null\n> > +++ b/src/qcam/viewfinder.cpp\n> > @@ -0,0 +1,38 @@\n> > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > +/*\n> > + * Copyright (C) 2019, Google Inc.\n> > + *\n> > + * viewfinder.cpp - qcam - Viewfinder\n> > + */\n> > +\n> > +#include <QImage>\n> > +#include <QPixmap>\n> > +\n> > +#include \"format_converter.h\"\n> > +#include \"viewfinder.h\"\n> > +\n> > +ViewFinder::ViewFinder(QWidget *parent)\n> > +\t: QLabel(parent), format_(0), width_(0), height_(0), image_(nullptr)\n> > +{\n> > +}\n> > +\n> > +void ViewFinder::display(unsigned char *raw)\n> > +{\n> > +\tbuffer_to_rgb32(raw, format_, width_, height_, image_->bits());\n> \n> You should check the return value here as if a none YVYU format is used \n> it will return -EINVAL and no image will be displayed which might \n> confuse the user.\n\nI will instead do so in MainWindow::startCapture() and not start the\nstream if YUYV is not supported.\n\n> > +\n> > +\tQPixmap pixmap = QPixmap::fromImage(*image_);\n> > +\tsetPixmap(pixmap);\n> > +}\n> > +\n> > +void ViewFinder::setFormat(unsigned int format, unsigned int width,\n> > +\t\t\t   unsigned int height)\n> > +{\n> > +\tformat_ = format;\n> > +\twidth_ = width;\n> > +\theight_ = height;\n> > +\n> > +\tsetFixedSize(width, height);\n> > +\n> > +\tdelete image_;\n> > +\timage_ = new QImage(width, height, QImage::Format_RGB32);\n> > +}\n> > diff --git a/src/qcam/viewfinder.h b/src/qcam/viewfinder.h\n> > new file mode 100644\n> > index 000000000000..ecae290a5043\n> > --- /dev/null\n> > +++ b/src/qcam/viewfinder.h\n> > @@ -0,0 +1,31 @@\n> > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > +/*\n> > + * Copyright (C) 2019, Google Inc.\n> > + *\n> > + * viewfinder.h - qcam - Viewfinder\n> > + */\n> > +#ifndef __QCAM_VIEWFINDER_H__\n> > +#define __QCAM_VIEWFINDER_H__\n> > +\n> > +#include <QLabel>\n> > +\n> > +class QImage;\n> > +\n> > +class ViewFinder : public QLabel\n> > +{\n> > +public:\n> > +\tViewFinder(QWidget *parent);\n> > +\n> > +\tvoid setFormat(unsigned int format, unsigned int width,\n> > +\t\t       unsigned int height);\n> > +\tvoid display(unsigned char *rgb);\n> > +\n> > +private:\n> > +\tunsigned int format_;\n> > +\tunsigned int width_;\n> > +\tunsigned int height_;\n> > +\n> > +\tQImage *image_;\n> > +};\n> > +\n> > +#endif /* __QCAM_VIEWFINDER__ */","headers":{"Return-Path":"<laurent.pinchart@ideasonboard.com>","Received":["from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id DE653600FD\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 29 Mar 2019 15:50:26 +0100 (CET)","from pendragon.ideasonboard.com\n\t(dfj612yhrgyx302h3jwwy-3.rev.dnainternet.fi\n\t[IPv6:2001:14ba:21f5:5b00:ce28:277f:58d7:3ca4])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 5D8E49A3;\n\tFri, 29 Mar 2019 15:50:26 +0100 (CET)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1553871026;\n\tbh=0vCWy5XU1ubhdapBge5Ll/9lft1an4iyNawI/MBMfVg=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=Olikww2Urjtft7FpjEoW97BLkSBeaF0SNaK+heG5xz4gAZ86xnoLi5Fky5wQHdakE\n\tuqyl/mjzrcAFh+6tOv0oxGDPYcsa1oUNteQxnaYrTulm1M1xICf/aSr8j6fkCeQSic\n\tNKfn9EliNavmGNOF1iq8IJP8hXvk1SELqq5Y3ZbY=","Date":"Fri, 29 Mar 2019 16:50:16 +0200","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Niklas =?utf-8?q?S=C3=B6derlund?= <niklas.soderlund@ragnatech.se>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20190329145016.GC4934@pendragon.ideasonboard.com>","References":"<20190323073125.25497-1-laurent.pinchart@ideasonboard.com>\n\t<20190323073125.25497-3-laurent.pinchart@ideasonboard.com>\n\t<20190327004200.GH9119@bigcity.dyn.berto.se>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<20190327004200.GH9119@bigcity.dyn.berto.se>","User-Agent":"Mutt/1.10.1 (2018-07-13)","Subject":"Re: [libcamera-devel] [PATCH 2/2] qcam: Add Qt-based GUI application","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.23","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Fri, 29 Mar 2019 14:50:27 -0000"}}]