diff options
Diffstat (limited to 'src/qcam/main_window.cpp')
-rw-r--r-- | src/qcam/main_window.cpp | 790 |
1 files changed, 0 insertions, 790 deletions
diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp deleted file mode 100644 index f553ccb0..00000000 --- a/src/qcam/main_window.cpp +++ /dev/null @@ -1,790 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (C) 2019, Google Inc. - * - * main_window.cpp - qcam - Main application window - */ - -#include "main_window.h" - -#include <assert.h> -#include <iomanip> -#include <string> - -#include <libcamera/camera_manager.h> -#include <libcamera/version.h> - -#include <QCoreApplication> -#include <QFileDialog> -#include <QImage> -#include <QImageWriter> -#include <QMutexLocker> -#include <QStandardPaths> -#include <QStringList> -#include <QTimer> -#include <QToolBar> -#include <QToolButton> -#include <QtDebug> - -#include "../cam/dng_writer.h" -#include "../cam/image.h" - -#include "cam_select_dialog.h" -#ifndef QT_NO_OPENGL -#include "viewfinder_gl.h" -#endif -#include "viewfinder_qt.h" - -using namespace libcamera; - -#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) -/* - * Qt::fixed was introduced in v5.14, and ::fixed deprecated in v5.15. Allow - * usage of Qt::fixed unconditionally. - */ -namespace Qt { -constexpr auto fixed = ::fixed; -} /* namespace Qt */ -#endif - -/** - * \brief Custom QEvent to signal capture completion - */ -class CaptureEvent : public QEvent -{ -public: - CaptureEvent() - : QEvent(type()) - { - } - - static Type type() - { - static int type = QEvent::registerEventType(); - return static_cast<Type>(type); - } -}; - -/** - * \brief Custom QEvent to signal hotplug or unplug - */ -class HotplugEvent : public QEvent -{ -public: - enum PlugEvent { - HotPlug, - HotUnplug - }; - - HotplugEvent(std::shared_ptr<Camera> camera, PlugEvent event) - : QEvent(type()), camera_(std::move(camera)), plugEvent_(event) - { - } - - static Type type() - { - static int type = QEvent::registerEventType(); - return static_cast<Type>(type); - } - - PlugEvent hotplugEvent() const { return plugEvent_; } - Camera *camera() const { return camera_.get(); } - -private: - std::shared_ptr<Camera> camera_; - PlugEvent plugEvent_; -}; - -MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options) - : saveRaw_(nullptr), options_(options), cm_(cm), allocator_(nullptr), - isCapturing_(false), captureRaw_(false) -{ - int ret; - - /* - * Initialize the UI: Create the toolbar, set the window title and - * create the viewfinder widget. - */ - createToolbars(); - - title_ = "QCam " + QString::fromStdString(CameraManager::version()); - setWindowTitle(title_); - connect(&titleTimer_, SIGNAL(timeout()), this, SLOT(updateTitle())); - - /* Renderer type Qt or GLES, select Qt by default. */ - std::string renderType = "qt"; - if (options_.isSet(OptRenderer)) - renderType = options_[OptRenderer].toString(); - - if (renderType == "qt") { - ViewFinderQt *viewfinder = new ViewFinderQt(this); - connect(viewfinder, &ViewFinderQt::renderComplete, - this, &MainWindow::renderComplete); - viewfinder_ = viewfinder; - setCentralWidget(viewfinder); -#ifndef QT_NO_OPENGL - } else if (renderType == "gles") { - ViewFinderGL *viewfinder = new ViewFinderGL(this); - connect(viewfinder, &ViewFinderGL::renderComplete, - this, &MainWindow::renderComplete); - viewfinder_ = viewfinder; - setCentralWidget(viewfinder); -#endif - } else { - qWarning() << "Invalid render type" - << QString::fromStdString(renderType); - quit(); - return; - } - - adjustSize(); - - /* Hotplug/unplug support */ - cm_->cameraAdded.connect(this, &MainWindow::addCamera); - cm_->cameraRemoved.connect(this, &MainWindow::removeCamera); - - cameraSelectorDialog_ = new CameraSelectorDialog(cm_, this); - - /* Open the camera and start capture. */ - ret = openCamera(); - if (ret < 0) { - quit(); - return; - } - - startStopAction_->setChecked(true); -} - -MainWindow::~MainWindow() -{ - if (camera_) { - stopCapture(); - camera_->release(); - camera_.reset(); - } -} - -bool MainWindow::event(QEvent *e) -{ - if (e->type() == CaptureEvent::type()) { - processCapture(); - return true; - } else if (e->type() == HotplugEvent::type()) { - processHotplug(static_cast<HotplugEvent *>(e)); - return true; - } - - return QMainWindow::event(e); -} - -int MainWindow::createToolbars() -{ - QAction *action; - - toolbar_ = addToolBar("Main"); - - /* Disable right click context menu. */ - toolbar_->setContextMenuPolicy(Qt::PreventContextMenu); - - /* Quit action. */ - action = toolbar_->addAction(QIcon::fromTheme("application-exit", - QIcon(":x-circle.svg")), - "Quit"); - action->setShortcut(Qt::CTRL | Qt::Key_Q); - connect(action, &QAction::triggered, this, &MainWindow::quit); - - /* Camera selector. */ - cameraSelectButton_ = new QPushButton; - connect(cameraSelectButton_, &QPushButton::clicked, - this, &MainWindow::switchCamera); - - toolbar_->addWidget(cameraSelectButton_); - - toolbar_->addSeparator(); - - /* Start/Stop action. */ - iconPlay_ = QIcon::fromTheme("media-playback-start", - QIcon(":play-circle.svg")); - iconStop_ = QIcon::fromTheme("media-playback-stop", - QIcon(":stop-circle.svg")); - - action = toolbar_->addAction(iconPlay_, "Start Capture"); - action->setCheckable(true); - action->setShortcut(Qt::Key_Space); - connect(action, &QAction::toggled, this, &MainWindow::toggleCapture); - startStopAction_ = action; - - /* Save As... action. */ - action = toolbar_->addAction(QIcon::fromTheme("document-save-as", - QIcon(":save.svg")), - "Save As..."); - action->setShortcut(QKeySequence::SaveAs); - connect(action, &QAction::triggered, this, &MainWindow::saveImageAs); - -#ifdef HAVE_DNG - /* Save Raw action. */ - action = toolbar_->addAction(QIcon::fromTheme("camera-photo", - QIcon(":aperture.svg")), - "Save Raw"); - action->setEnabled(false); - connect(action, &QAction::triggered, this, &MainWindow::captureRaw); - saveRaw_ = action; -#endif - - return 0; -} - -void MainWindow::quit() -{ - QTimer::singleShot(0, QCoreApplication::instance(), - &QCoreApplication::quit); -} - -void MainWindow::updateTitle() -{ - /* Calculate the average frame rate over the last period. */ - unsigned int duration = frameRateInterval_.elapsed(); - unsigned int frames = framesCaptured_ - previousFrames_; - double fps = frames * 1000.0 / duration; - - /* Restart counters. */ - frameRateInterval_.start(); - previousFrames_ = framesCaptured_; - - setWindowTitle(title_ + " : " + QString::number(fps, 'f', 2) + " fps"); -} - -/* ----------------------------------------------------------------------------- - * Camera Selection - */ - -void MainWindow::switchCamera() -{ - /* Get and acquire the new camera. */ - std::string newCameraId = chooseCamera(); - - if (newCameraId.empty()) - return; - - if (camera_ && newCameraId == camera_->id()) - return; - - const std::shared_ptr<Camera> &cam = cm_->get(newCameraId); - - if (cam->acquire()) { - qInfo() << "Failed to acquire camera" << cam->id().c_str(); - return; - } - - qInfo() << "Switching to camera" << cam->id().c_str(); - - /* - * Stop the capture session, release the current camera, replace it with - * the new camera and start a new capture session. - */ - startStopAction_->setChecked(false); - - if (camera_) - camera_->release(); - - camera_ = cam; - - startStopAction_->setChecked(true); - - /* Display the current cameraId in the toolbar .*/ - cameraSelectButton_->setText(QString::fromStdString(newCameraId)); -} - -std::string MainWindow::chooseCamera() -{ - if (cameraSelectorDialog_->exec() != QDialog::Accepted) - return std::string(); - - return cameraSelectorDialog_->getCameraId(); -} - -int MainWindow::openCamera() -{ - std::string cameraName; - - /* - * Use the camera specified on the command line, if any, or display the - * camera selection dialog box otherwise. - */ - if (options_.isSet(OptCamera)) - cameraName = static_cast<std::string>(options_[OptCamera]); - else - cameraName = chooseCamera(); - - if (cameraName == "") - return -EINVAL; - - /* Get and acquire the camera. */ - camera_ = cm_->get(cameraName); - if (!camera_) { - qInfo() << "Camera" << cameraName.c_str() << "not found"; - return -ENODEV; - } - - if (camera_->acquire()) { - qInfo() << "Failed to acquire camera"; - camera_.reset(); - return -EBUSY; - } - - /* Set the camera switch button with the currently selected Camera id. */ - cameraSelectButton_->setText(QString::fromStdString(cameraName)); - - return 0; -} - -/* ----------------------------------------------------------------------------- - * Capture Start & Stop - */ - -void MainWindow::toggleCapture(bool start) -{ - if (start) { - startCapture(); - startStopAction_->setIcon(iconStop_); - startStopAction_->setText("Stop Capture"); - } else { - stopCapture(); - startStopAction_->setIcon(iconPlay_); - startStopAction_->setText("Start Capture"); - } -} - -/** - * \brief Start capture with the current camera - * - * This function shall not be called directly, use toggleCapture() instead. - */ -int MainWindow::startCapture() -{ - StreamRoles roles = StreamKeyValueParser::roles(options_[OptStream]); - int ret; - - /* Verify roles are supported. */ - switch (roles.size()) { - case 1: - if (roles[0] != StreamRole::Viewfinder) { - qWarning() << "Only viewfinder supported for single stream"; - return -EINVAL; - } - break; - case 2: - if (roles[0] != StreamRole::Viewfinder || - roles[1] != StreamRole::Raw) { - qWarning() << "Only viewfinder + raw supported for dual streams"; - return -EINVAL; - } - break; - default: - if (roles.size() != 1) { - qWarning() << "Unsupported stream configuration"; - return -EINVAL; - } - break; - } - - /* Configure the camera. */ - config_ = camera_->generateConfiguration(roles); - if (!config_) { - qWarning() << "Failed to generate configuration from roles"; - return -EINVAL; - } - - StreamConfiguration &vfConfig = config_->at(0); - - /* Use a format supported by the viewfinder if available. */ - std::vector<PixelFormat> formats = vfConfig.formats().pixelformats(); - for (const PixelFormat &format : viewfinder_->nativeFormats()) { - auto match = std::find_if(formats.begin(), formats.end(), - [&](const PixelFormat &f) { - return f == format; - }); - if (match != formats.end()) { - vfConfig.pixelFormat = format; - break; - } - } - - /* Allow user to override configuration. */ - if (StreamKeyValueParser::updateConfiguration(config_.get(), - options_[OptStream])) { - qWarning() << "Failed to update configuration"; - return -EINVAL; - } - - CameraConfiguration::Status validation = config_->validate(); - if (validation == CameraConfiguration::Invalid) { - qWarning() << "Failed to create valid camera configuration"; - return -EINVAL; - } - - if (validation == CameraConfiguration::Adjusted) - qInfo() << "Stream configuration adjusted to " - << vfConfig.toString().c_str(); - - ret = camera_->configure(config_.get()); - if (ret < 0) { - qInfo() << "Failed to configure camera"; - return ret; - } - - /* Store stream allocation. */ - vfStream_ = config_->at(0).stream(); - if (config_->size() == 2) - rawStream_ = config_->at(1).stream(); - else - rawStream_ = nullptr; - - /* - * Configure the viewfinder. If no color space is reported, default to - * sYCC. - */ - ret = viewfinder_->setFormat(vfConfig.pixelFormat, - QSize(vfConfig.size.width, vfConfig.size.height), - vfConfig.colorSpace.value_or(ColorSpace::Sycc), - vfConfig.stride); - if (ret < 0) { - qInfo() << "Failed to set viewfinder format"; - return ret; - } - - adjustSize(); - - /* Configure the raw capture button. */ - if (saveRaw_) - saveRaw_->setEnabled(config_->size() == 2); - - /* Allocate and map buffers. */ - allocator_ = new FrameBufferAllocator(camera_); - for (StreamConfiguration &config : *config_) { - Stream *stream = config.stream(); - - ret = allocator_->allocate(stream); - if (ret < 0) { - qWarning() << "Failed to allocate capture buffers"; - goto error; - } - - for (const std::unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) { - /* Map memory buffers and cache the mappings. */ - std::unique_ptr<Image> image = - Image::fromFrameBuffer(buffer.get(), Image::MapMode::ReadOnly); - assert(image != nullptr); - mappedBuffers_[buffer.get()] = std::move(image); - - /* Store buffers on the free list. */ - freeBuffers_[stream].enqueue(buffer.get()); - } - } - - /* Create requests and fill them with buffers from the viewfinder. */ - while (!freeBuffers_[vfStream_].isEmpty()) { - FrameBuffer *buffer = freeBuffers_[vfStream_].dequeue(); - - std::unique_ptr<Request> request = camera_->createRequest(); - if (!request) { - qWarning() << "Can't create request"; - ret = -ENOMEM; - goto error; - } - - ret = request->addBuffer(vfStream_, buffer); - if (ret < 0) { - qWarning() << "Can't set buffer for request"; - goto error; - } - - requests_.push_back(std::move(request)); - } - - /* Start the title timer and the camera. */ - titleTimer_.start(2000); - frameRateInterval_.start(); - previousFrames_ = 0; - framesCaptured_ = 0; - lastBufferTime_ = 0; - - ret = camera_->start(); - if (ret) { - qInfo() << "Failed to start capture"; - goto error; - } - - camera_->requestCompleted.connect(this, &MainWindow::requestComplete); - - /* Queue all requests. */ - for (std::unique_ptr<Request> &request : requests_) { - ret = queueRequest(request.get()); - if (ret < 0) { - qWarning() << "Can't queue request"; - goto error_disconnect; - } - } - - isCapturing_ = true; - - return 0; - -error_disconnect: - camera_->requestCompleted.disconnect(this); - camera_->stop(); - -error: - requests_.clear(); - - mappedBuffers_.clear(); - - freeBuffers_.clear(); - - delete allocator_; - allocator_ = nullptr; - - return ret; -} - -/** - * \brief Stop ongoing capture - * - * This function may be called directly when tearing down the MainWindow. Use - * toggleCapture() instead in all other cases. - */ -void MainWindow::stopCapture() -{ - if (!isCapturing_) - return; - - viewfinder_->stop(); - if (saveRaw_) - saveRaw_->setEnabled(false); - captureRaw_ = false; - - int ret = camera_->stop(); - if (ret) - qInfo() << "Failed to stop capture"; - - camera_->requestCompleted.disconnect(this); - - mappedBuffers_.clear(); - - requests_.clear(); - freeQueue_.clear(); - - delete allocator_; - - isCapturing_ = false; - - config_.reset(); - - /* - * A CaptureEvent may have been posted before we stopped the camera, - * but not processed yet. Clear the queue of done buffers to avoid - * racing with the event handler. - */ - freeBuffers_.clear(); - doneQueue_.clear(); - - titleTimer_.stop(); - setWindowTitle(title_); -} - -/* ----------------------------------------------------------------------------- - * Camera hotplugging support - */ - -void MainWindow::processHotplug(HotplugEvent *e) -{ - Camera *camera = e->camera(); - QString cameraId = QString::fromStdString(camera->id()); - HotplugEvent::PlugEvent event = e->hotplugEvent(); - - if (event == HotplugEvent::HotPlug) { - cameraSelectorDialog_->addCamera(cameraId); - } else if (event == HotplugEvent::HotUnplug) { - /* Check if the currently-streaming camera is removed. */ - if (camera == camera_.get()) { - toggleCapture(false); - camera_->release(); - camera_.reset(); - } - - cameraSelectorDialog_->removeCamera(cameraId); - } -} - -void MainWindow::addCamera(std::shared_ptr<Camera> camera) -{ - qInfo() << "Adding new camera:" << camera->id().c_str(); - QCoreApplication::postEvent(this, - new HotplugEvent(std::move(camera), - HotplugEvent::HotPlug)); -} - -void MainWindow::removeCamera(std::shared_ptr<Camera> camera) -{ - qInfo() << "Removing camera:" << camera->id().c_str(); - QCoreApplication::postEvent(this, - new HotplugEvent(std::move(camera), - HotplugEvent::HotUnplug)); -} - -/* ----------------------------------------------------------------------------- - * Image Save - */ - -void MainWindow::saveImageAs() -{ - QImage image = viewfinder_->getCurrentImage(); - QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); - - QString filename = QFileDialog::getSaveFileName(this, "Save Image", defaultPath, - "Image Files (*.png *.jpg *.jpeg)"); - if (filename.isEmpty()) - return; - - QImageWriter writer(filename); - writer.setQuality(95); - writer.write(image); -} - -void MainWindow::captureRaw() -{ - captureRaw_ = true; -} - -void MainWindow::processRaw(FrameBuffer *buffer, - [[maybe_unused]] const ControlList &metadata) -{ -#ifdef HAVE_DNG - QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); - QString filename = QFileDialog::getSaveFileName(this, "Save DNG", defaultPath, - "DNG Files (*.dng)"); - - if (!filename.isEmpty()) { - uint8_t *memory = mappedBuffers_[buffer]->data(0).data(); - DNGWriter::write(filename.toStdString().c_str(), camera_.get(), - rawStream_->configuration(), metadata, buffer, - memory); - } -#endif - - { - QMutexLocker locker(&mutex_); - freeBuffers_[rawStream_].enqueue(buffer); - } -} - -/* ----------------------------------------------------------------------------- - * Request Completion Handling - */ - -void MainWindow::requestComplete(Request *request) -{ - if (request->status() == Request::RequestCancelled) - return; - - /* - * We're running in the libcamera thread context, expensive operations - * are not allowed. Add the buffer to the done queue and post a - * CaptureEvent for the application thread to handle. - */ - { - QMutexLocker locker(&mutex_); - doneQueue_.enqueue(request); - } - - QCoreApplication::postEvent(this, new CaptureEvent); -} - -void MainWindow::processCapture() -{ - /* - * Retrieve the next buffer from the done queue. The queue may be empty - * if stopCapture() has been called while a CaptureEvent was posted but - * not processed yet. Return immediately in that case. - */ - Request *request; - { - QMutexLocker locker(&mutex_); - if (doneQueue_.isEmpty()) - return; - - request = doneQueue_.dequeue(); - } - - /* Process buffers. */ - if (request->buffers().count(vfStream_)) - processViewfinder(request->buffers().at(vfStream_)); - - if (request->buffers().count(rawStream_)) - processRaw(request->buffers().at(rawStream_), request->metadata()); - - request->reuse(); - QMutexLocker locker(&mutex_); - freeQueue_.enqueue(request); -} - -void MainWindow::processViewfinder(FrameBuffer *buffer) -{ - framesCaptured_++; - - const FrameMetadata &metadata = buffer->metadata(); - - double fps = metadata.timestamp - lastBufferTime_; - fps = lastBufferTime_ && fps ? 1000000000.0 / fps : 0.0; - lastBufferTime_ = metadata.timestamp; - - QStringList bytesused; - for (const FrameMetadata::Plane &plane : metadata.planes()) - bytesused << QString::number(plane.bytesused); - - qDebug().noquote() - << QString("seq: %1").arg(metadata.sequence, 6, 10, QLatin1Char('0')) - << "bytesused: {" << bytesused.join(", ") - << "} timestamp:" << metadata.timestamp - << "fps:" << Qt::fixed << qSetRealNumberPrecision(2) << fps; - - /* Render the frame on the viewfinder. */ - viewfinder_->render(buffer, mappedBuffers_[buffer].get()); -} - -void MainWindow::renderComplete(FrameBuffer *buffer) -{ - Request *request; - { - QMutexLocker locker(&mutex_); - if (freeQueue_.isEmpty()) - return; - - request = freeQueue_.dequeue(); - } - - request->addBuffer(vfStream_, buffer); - - if (captureRaw_) { - FrameBuffer *rawBuffer = nullptr; - - { - QMutexLocker locker(&mutex_); - if (!freeBuffers_[rawStream_].isEmpty()) - rawBuffer = freeBuffers_[rawStream_].dequeue(); - } - - if (rawBuffer) { - request->addBuffer(rawStream_, rawBuffer); - captureRaw_ = false; - } else { - qWarning() << "No free buffer available for RAW capture"; - } - } - queueRequest(request); -} - -int MainWindow::queueRequest(Request *request) -{ - return camera_->queueRequest(request); -} |