Move test applications to src/apps/
The cam and qcam test application share code, currently through a crude hack that references the cam source files directly from the qcam meson.build file. To prepare for the introduction of hosting that code in a static library, move all applications to src/apps/. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
This commit is contained in:
parent
daf3f4b59f
commit
84ad104499
350 changed files with 7 additions and 4 deletions
790
src/apps/qcam/main_window.cpp
Normal file
790
src/apps/qcam/main_window.cpp
Normal file
|
@ -0,0 +1,790 @@
|
|||
/* 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);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue