Switch to the FrameBuffer interface where all buffers are treated as external buffers and are allocated outside the camera. Applications allocating buffers using libcamera are switched to use the FrameBufferAllocator helper. Follow-up changes to this one will finalize the transition to the new FrameBuffer interface by removing code that is left unused after this change. Signed-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
312 lines
6.9 KiB
C++
312 lines
6.9 KiB
C++
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
/*
|
|
* Copyright (C) 2019, Google Inc.
|
|
*
|
|
* main_window.cpp - qcam - Main application window
|
|
*/
|
|
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <sys/mman.h>
|
|
|
|
#include <QCoreApplication>
|
|
#include <QInputDialog>
|
|
#include <QTimer>
|
|
|
|
#include <libcamera/camera_manager.h>
|
|
#include <libcamera/version.h>
|
|
|
|
#include "main_window.h"
|
|
#include "viewfinder.h"
|
|
|
|
using namespace libcamera;
|
|
|
|
MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options)
|
|
: options_(options), allocator_(nullptr), isCapturing_(false)
|
|
{
|
|
int ret;
|
|
|
|
title_ = "QCam " + QString::fromStdString(CameraManager::version());
|
|
setWindowTitle(title_);
|
|
connect(&titleTimer_, SIGNAL(timeout()), this, SLOT(updateTitle()));
|
|
|
|
viewfinder_ = new ViewFinder(this);
|
|
setCentralWidget(viewfinder_);
|
|
viewfinder_->setFixedSize(500, 500);
|
|
adjustSize();
|
|
|
|
ret = openCamera(cm);
|
|
if (!ret) {
|
|
allocator_ = FrameBufferAllocator::create(camera_);
|
|
ret = startCapture();
|
|
}
|
|
|
|
if (ret < 0)
|
|
QTimer::singleShot(0, QCoreApplication::instance(),
|
|
&QCoreApplication::quit);
|
|
}
|
|
|
|
MainWindow::~MainWindow()
|
|
{
|
|
if (camera_) {
|
|
stopCapture();
|
|
delete allocator_;
|
|
camera_->release();
|
|
camera_.reset();
|
|
}
|
|
}
|
|
|
|
void MainWindow::updateTitle()
|
|
{
|
|
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");
|
|
}
|
|
|
|
std::string MainWindow::chooseCamera(CameraManager *cm)
|
|
{
|
|
QStringList cameras;
|
|
bool result;
|
|
|
|
if (cm->cameras().size() == 1)
|
|
return cm->cameras()[0]->name();
|
|
|
|
for (const std::shared_ptr<Camera> &cam : cm->cameras())
|
|
cameras.append(QString::fromStdString(cam->name()));
|
|
|
|
QString name = QInputDialog::getItem(this, "Select Camera",
|
|
"Camera:", cameras, 0,
|
|
false, &result);
|
|
if (!result)
|
|
return std::string();
|
|
|
|
return name.toStdString();
|
|
}
|
|
|
|
int MainWindow::openCamera(CameraManager *cm)
|
|
{
|
|
std::string cameraName;
|
|
|
|
if (options_.isSet(OptCamera))
|
|
cameraName = static_cast<std::string>(options_[OptCamera]);
|
|
else
|
|
cameraName = chooseCamera(cm);
|
|
|
|
if (cameraName == "")
|
|
return -EINVAL;
|
|
|
|
camera_ = cm->get(cameraName);
|
|
if (!camera_) {
|
|
std::cout << "Camera " << cameraName << " not found"
|
|
<< std::endl;
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (camera_->acquire()) {
|
|
std::cout << "Failed to acquire camera" << std::endl;
|
|
camera_.reset();
|
|
return -EBUSY;
|
|
}
|
|
|
|
std::cout << "Using camera " << camera_->name() << std::endl;
|
|
|
|
camera_->requestCompleted.connect(this, &MainWindow::requestComplete);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int MainWindow::startCapture()
|
|
{
|
|
int ret;
|
|
|
|
config_ = camera_->generateConfiguration({ StreamRole::VideoRecording });
|
|
|
|
StreamConfiguration &cfg = config_->at(0);
|
|
if (options_.isSet(OptSize)) {
|
|
const std::vector<OptionValue> &sizeOptions =
|
|
options_[OptSize].toArray();
|
|
|
|
/* Set desired stream size if requested. */
|
|
for (const auto &value : sizeOptions) {
|
|
KeyValueParser::Options opt = value.toKeyValues();
|
|
|
|
if (opt.isSet("width"))
|
|
cfg.size.width = opt["width"];
|
|
|
|
if (opt.isSet("height"))
|
|
cfg.size.height = opt["height"];
|
|
}
|
|
}
|
|
|
|
CameraConfiguration::Status validation = config_->validate();
|
|
if (validation == CameraConfiguration::Invalid) {
|
|
std::cerr << "Failed to create valid camera configuration";
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (validation == CameraConfiguration::Adjusted) {
|
|
std::cout << "Stream size adjusted to "
|
|
<< cfg.size.toString() << std::endl;
|
|
}
|
|
|
|
ret = camera_->configure(config_.get());
|
|
if (ret < 0) {
|
|
std::cout << "Failed to configure camera" << std::endl;
|
|
return ret;
|
|
}
|
|
|
|
Stream *stream = cfg.stream();
|
|
ret = viewfinder_->setFormat(cfg.pixelFormat, cfg.size.width,
|
|
cfg.size.height);
|
|
if (ret < 0) {
|
|
std::cout << "Failed to set viewfinder format" << std::endl;
|
|
return ret;
|
|
}
|
|
|
|
adjustSize();
|
|
|
|
ret = camera_->allocateBuffers();
|
|
if (ret) {
|
|
std::cerr << "Failed to allocate buffers"
|
|
<< std::endl;
|
|
return ret;
|
|
}
|
|
|
|
ret = allocator_->allocate(stream);
|
|
if (ret < 0) {
|
|
std::cerr << "Failed to allocate capture buffers" << std::endl;
|
|
return ret;
|
|
}
|
|
|
|
std::vector<Request *> requests;
|
|
for (const std::unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) {
|
|
Request *request = camera_->createRequest();
|
|
if (!request) {
|
|
std::cerr << "Can't create request" << std::endl;
|
|
ret = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
ret = request->addBuffer(stream, buffer.get());
|
|
if (ret < 0) {
|
|
std::cerr << "Can't set buffer for request" << std::endl;
|
|
goto error;
|
|
}
|
|
|
|
requests.push_back(request);
|
|
}
|
|
|
|
titleTimer_.start(2000);
|
|
frameRateInterval_.start();
|
|
previousFrames_ = 0;
|
|
framesCaptured_ = 0;
|
|
lastBufferTime_ = 0;
|
|
|
|
ret = camera_->start();
|
|
if (ret) {
|
|
std::cout << "Failed to start capture" << std::endl;
|
|
goto error;
|
|
}
|
|
|
|
for (Request *request : requests) {
|
|
ret = camera_->queueRequest(request);
|
|
if (ret < 0) {
|
|
std::cerr << "Can't queue request" << std::endl;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
isCapturing_ = true;
|
|
return 0;
|
|
|
|
error:
|
|
for (Request *request : requests)
|
|
delete request;
|
|
|
|
camera_->freeBuffers();
|
|
return ret;
|
|
}
|
|
|
|
void MainWindow::stopCapture()
|
|
{
|
|
if (!isCapturing_)
|
|
return;
|
|
|
|
int ret = camera_->stop();
|
|
if (ret)
|
|
std::cout << "Failed to stop capture" << std::endl;
|
|
|
|
camera_->freeBuffers();
|
|
isCapturing_ = false;
|
|
|
|
config_.reset();
|
|
|
|
titleTimer_.stop();
|
|
setWindowTitle(title_);
|
|
}
|
|
|
|
void MainWindow::requestComplete(Request *request)
|
|
{
|
|
if (request->status() == Request::RequestCancelled)
|
|
return;
|
|
|
|
const std::map<Stream *, FrameBuffer *> &buffers = request->buffers();
|
|
|
|
framesCaptured_++;
|
|
|
|
FrameBuffer *buffer = buffers.begin()->second;
|
|
const FrameMetadata &metadata = buffer->metadata();
|
|
|
|
double fps = metadata.timestamp - lastBufferTime_;
|
|
fps = lastBufferTime_ && fps ? 1000000000.0 / fps : 0.0;
|
|
lastBufferTime_ = metadata.timestamp;
|
|
|
|
std::cout << "seq: " << std::setw(6) << std::setfill('0') << metadata.sequence
|
|
<< " bytesused: " << metadata.planes[0].bytesused
|
|
<< " timestamp: " << metadata.timestamp
|
|
<< " fps: " << std::fixed << std::setprecision(2) << fps
|
|
<< std::endl;
|
|
|
|
display(buffer);
|
|
|
|
request = camera_->createRequest();
|
|
if (!request) {
|
|
std::cerr << "Can't create request" << std::endl;
|
|
return;
|
|
}
|
|
|
|
for (auto it = buffers.begin(); it != buffers.end(); ++it) {
|
|
Stream *stream = it->first;
|
|
FrameBuffer *buffer = it->second;
|
|
|
|
request->addBuffer(stream, buffer);
|
|
}
|
|
|
|
camera_->queueRequest(request);
|
|
}
|
|
|
|
int MainWindow::display(FrameBuffer *buffer)
|
|
{
|
|
if (buffer->planes().size() != 1)
|
|
return -EINVAL;
|
|
|
|
/* \todo Once the FrameBuffer is done cache mapped memory. */
|
|
const FrameBuffer::Plane &plane = buffer->planes().front();
|
|
void *memory = mmap(NULL, plane.length, PROT_READ, MAP_SHARED,
|
|
plane.fd.fd(), 0);
|
|
|
|
unsigned char *raw = static_cast<unsigned char *>(memory);
|
|
viewfinder_->display(raw, buffer->metadata().planes[0].bytesused);
|
|
|
|
munmap(memory, plane.length);
|
|
|
|
return 0;
|
|
}
|