/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dng_writer.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); } }; /** * \brief Custom QEvent to signal hotplug or unplug */ class HotplugEvent : public QEvent { public: enum PlugEvent { HotPlug, HotUnplug }; HotplugEvent(std::shared_ptr camera, PlugEvent event) : QEvent(type()), camera_(std::move(camera)), plugEvent_(event) { } static Type type() { static int type = QEvent::registerEventType(); return static_cast(type); } PlugEvent hotplugEvent() const { return plugEvent_; } Camera *camera() const { return camera_.get(); } private: std::shared_ptr 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::queueRequest); viewfinder_ = viewfinder; setCentralWidget(viewfinder); #ifndef QT_NO_OPENGL } else if (renderType == "gles") { ViewFinderGL *viewfinder = new ViewFinderGL(this); connect(viewfinder, &ViewFinderGL::renderComplete, this, &MainWindow::queueRequest); 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); /* 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(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. */ cameraCombo_ = new QComboBox(); connect(cameraCombo_, QOverload::of(&QComboBox::activated), this, &MainWindow::switchCamera); for (const std::shared_ptr &cam : cm_->cameras()) cameraCombo_->addItem(QString::fromStdString(cam->id())); toolbar_->addWidget(cameraCombo_); 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(int index) { /* Get and acquire the new camera. */ const auto &cameras = cm_->cameras(); if (static_cast(index) >= cameras.size()) return; const std::shared_ptr &cam = cameras[index]; 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); camera_->release(); camera_ = cam; startStopAction_->setChecked(true); } std::string MainWindow::chooseCamera() { QStringList cameras; bool result; /* If only one camera is available, use it automatically. */ if (cm_->cameras().size() == 1) return cm_->cameras()[0]->id(); /* Present a dialog box to pick a camera. */ for (const std::shared_ptr &cam : cm_->cameras()) cameras.append(QString::fromStdString(cam->id())); QString id = QInputDialog::getItem(this, "Select Camera", "Camera:", cameras, 0, false, &result); if (!result) return std::string(); return id.toStdString(); } 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(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 combo-box entry with the currently selected Camera. */ cameraCombo_->setCurrentText(QString::fromStdString(cameraName)); return 0; } /* --------------------------------------------------/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2019, Raspberry Pi Ltd * * agc.h - AGC/AEC control algorithm */ #pragma once #include <vector> #include <mutex> #include <libcamera/base/utils.h> #include "../agc_algorithm.h" #include "../agc_status.h" #include "../pwl.h" /* This is our implementation of AGC. */ /* * This is the number actually set up by the firmware, not the maximum possible * number (which is 16). */ constexpr unsigned int AgcStatsSize = 15; namespace RPiController { struct AgcMeteringMode { double weights[AgcStatsSize]; int read(const libcamera::YamlObject &params); }; struct AgcExposureMode { std::vector<libcamera::utils::Duration> shutter; std::vector<double> gain; int read(const libcamera::YamlObject &params); }; struct AgcConstraint { enum class Bound { LOWER = 0, UPPER = 1 }; Bound bound; double qLo; double qHi; Pwl yTarget; int read(const libcamera::YamlObject &params); }; typedef std::vector<AgcConstraint> AgcConstraintMode; struct AgcConfig { int read(const libcamera::YamlObject &params); std::map<std::string, AgcMeteringMode> meteringModes; std::map<std::string, AgcExposureMode> exposureModes; std::map<std::string, AgcConstraintMode> constraintModes; Pwl yTarget; double speed; uint16_t startupFrames; unsigned int convergenceFrames; double maxChange; double minChange; double fastReduceThreshold; double speedUpThreshold; std::string defaultMeteringMode; std::string defaultExposureMode; std::string defaultConstraintMode; double baseEv; libcamera::utils::Duration defaultExposureTime; double defaultAnalogueGain; }; class Agc : public AgcAlgorithm { public: Agc(Controller *controller); char const *name() const override; int read(const libcamera::YamlObject &params) override; /* AGC handles "pausing" for itself. */ bool isPaused() const override; void pause() override; void resume() override; unsigned int getConvergenceFrames() const override; void setEv(double ev) override; void setFlickerPeriod(libcamera::utils::Duration flickerPeriod) override; void setMaxShutter(libcamera::utils::Duration maxShutter) override; void setFixedShutter(libcamera::utils::Duration fixedShutter) override; void setFixedAnalogueGain(double fixedAnalogueGain) override; void setMeteringMode(std::string const &meteringModeName) override; void setExposureMode(std::string const &exposureModeName) override; void setConstraintMode(std::string const &contraintModeName) override; void switchMode(CameraMode const &cameraMode, Metadata *metadata) override; void prepare(Metadata *imageMetadata) override; void process(StatisticsPtr &stats, Metadata *imageMetadata) override; private: void updateLockStatus(DeviceStatus const &deviceStatus); AgcConfig config_; void housekeepConfig(); void fetchCurrentExposure(Metadata *imageMetadata); void fetchAwbStatus(Metadata *imageMetadata); void computeGain(bcm2835_isp_stats *statistics, Metadata *imageMetadata, double &gain, double &targetY); void computeTargetExposure(double gain); bool applyDigitalGain(double gain, double targetY); void filterExposure(bool desaturate); void divideUpExposure(); void writeAndFinish(Metadata *imageMetadata, bool desaturate); libcamera::utils::Duration clipShutter(libcamera::utils::Duration shutter); AgcMeteringMode *meteringMode_; AgcExposureMode *exposureMode_; AgcConstraintMode *constraintMode_; uint64_t frameCount_; AwbStatus awb_; struct ExposureValues { ExposureValues(); libcamera::utils::Duration shutter; double analogueGain; libcamera::utils::Duration totalExposure; libcamera::utils::Duration totalExposureNoDG; /* without digital gain */ }; ExposureValues current_; /* values for the current frame */ ExposureValues target_; /* calculate the values we want here */ ExposureValues filtered_; /* these values are filtered towards target */ AgcStatus status_; int lockCount_; DeviceStatus lastDeviceStatus_; libcamera::utils::Duration lastTargetExposure_; double lastSensitivity_; /* sensitivity of the previous camera mode */ /* Below here the "settings" that applications can change. */ std::string meteringModeName_; std::string exposureModeName_; std::string constraintModeName_; double ev_; libcamera::utils::Duration flickerPeriod_; libcamera::utils::Duration maxShutter_; libcamera::utils::Duration fixedShutter_; double fixedAnalogueGain_; }; } /* namespace RPiController */