qcam: main_window: Document functions and reorganize member data

The qcam application is our reference implementation of a libcamera GUI
application. Document the code of the MainWindow class to make it easier
to follow, and reorganize the member data in logical groups for clarity.
No code change.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
This commit is contained in:
Laurent Pinchart 2020-03-22 21:30:04 +02:00
parent 0420a14d75
commit 53dd3594d5
2 changed files with 82 additions and 11 deletions

View file

@ -31,6 +31,9 @@
using namespace libcamera; using namespace libcamera;
/**
* \brief Custom QEvent to signal capture completion
*/
class CaptureEvent : public QEvent class CaptureEvent : public QEvent
{ {
public: public:
@ -51,6 +54,10 @@ MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options)
{ {
int ret; int ret;
/*
* Initialize the UI: Create the toolbar, set the window title and
* create the viewfinder widget.
*/
createToolbars(); createToolbars();
title_ = "QCam " + QString::fromStdString(CameraManager::version()); title_ = "QCam " + QString::fromStdString(CameraManager::version());
@ -61,6 +68,7 @@ MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options)
setCentralWidget(viewfinder_); setCentralWidget(viewfinder_);
adjustSize(); adjustSize();
/* Open the camera and start capture. */
ret = openCamera(); ret = openCamera();
if (ret < 0) if (ret < 0)
quit(); quit();
@ -96,13 +104,14 @@ int MainWindow::createToolbars()
/* Disable right click context menu. */ /* Disable right click context menu. */
toolbar_->setContextMenuPolicy(Qt::PreventContextMenu); toolbar_->setContextMenuPolicy(Qt::PreventContextMenu);
/* Quit action. */
action = toolbar_->addAction(QIcon::fromTheme("application-exit", action = toolbar_->addAction(QIcon::fromTheme("application-exit",
QIcon(":x-circle.svg")), QIcon(":x-circle.svg")),
"Quit"); "Quit");
action->setShortcut(Qt::CTRL | Qt::Key_Q); action->setShortcut(Qt::CTRL | Qt::Key_Q);
connect(action, &QAction::triggered, this, &MainWindow::quit); connect(action, &QAction::triggered, this, &MainWindow::quit);
/* Camera selection. */ /* Camera selector. */
QComboBox *cameraCombo = new QComboBox(); QComboBox *cameraCombo = new QComboBox();
connect(cameraCombo, QOverload<int>::of(&QComboBox::activated), connect(cameraCombo, QOverload<int>::of(&QComboBox::activated),
this, &MainWindow::switchCamera); this, &MainWindow::switchCamera);
@ -114,6 +123,7 @@ int MainWindow::createToolbars()
toolbar_->addSeparator(); toolbar_->addSeparator();
/* Start/Stop action. */
iconPlay_ = QIcon::fromTheme("media-playback-start", iconPlay_ = QIcon::fromTheme("media-playback-start",
QIcon(":play-circle.svg")); QIcon(":play-circle.svg"));
iconStop_ = QIcon::fromTheme("media-playback-stop", iconStop_ = QIcon::fromTheme("media-playback-stop",
@ -125,6 +135,7 @@ int MainWindow::createToolbars()
connect(action, &QAction::toggled, this, &MainWindow::toggleCapture); connect(action, &QAction::toggled, this, &MainWindow::toggleCapture);
startStopAction_ = action; startStopAction_ = action;
/* Save As... action. */
action = toolbar_->addAction(QIcon::fromTheme("document-save-as", action = toolbar_->addAction(QIcon::fromTheme("document-save-as",
QIcon(":save.svg")), QIcon(":save.svg")),
"Save As..."); "Save As...");
@ -142,6 +153,7 @@ void MainWindow::quit()
void MainWindow::updateTitle() void MainWindow::updateTitle()
{ {
/* Calculate the average frame rate over the last period. */
unsigned int duration = frameRateInterval_.elapsed(); unsigned int duration = frameRateInterval_.elapsed();
unsigned int frames = framesCaptured_ - previousFrames_; unsigned int frames = framesCaptured_ - previousFrames_;
double fps = frames * 1000.0 / duration; double fps = frames * 1000.0 / duration;
@ -153,8 +165,13 @@ void MainWindow::updateTitle()
setWindowTitle(title_ + " : " + QString::number(fps, 'f', 2) + " fps"); setWindowTitle(title_ + " : " + QString::number(fps, 'f', 2) + " fps");
} }
/* -----------------------------------------------------------------------------
* Camera Selection
*/
void MainWindow::switchCamera(int index) void MainWindow::switchCamera(int index)
{ {
/* Get and acquire the new camera. */
const auto &cameras = cm_->cameras(); const auto &cameras = cm_->cameras();
if (static_cast<unsigned int>(index) >= cameras.size()) if (static_cast<unsigned int>(index) >= cameras.size())
return; return;
@ -168,6 +185,10 @@ void MainWindow::switchCamera(int index)
std::cout << "Switching to camera " << cam->name() << std::endl; std::cout << "Switching to camera " << cam->name() << std::endl;
/*
* Stop the capture session, release the current camera, replace it with
* the new camera and start a new capture session.
*/
startStopAction_->setChecked(false); startStopAction_->setChecked(false);
camera_->release(); camera_->release();
@ -181,9 +202,11 @@ std::string MainWindow::chooseCamera()
QStringList cameras; QStringList cameras;
bool result; bool result;
/* If only one camera is available, use it automatically. */
if (cm_->cameras().size() == 1) if (cm_->cameras().size() == 1)
return cm_->cameras()[0]->name(); return cm_->cameras()[0]->name();
/* Present a dialog box to pick a camera. */
for (const std::shared_ptr<Camera> &cam : cm_->cameras()) for (const std::shared_ptr<Camera> &cam : cm_->cameras())
cameras.append(QString::fromStdString(cam->name())); cameras.append(QString::fromStdString(cam->name()));
@ -200,6 +223,10 @@ int MainWindow::openCamera()
{ {
std::string cameraName; 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)) if (options_.isSet(OptCamera))
cameraName = static_cast<std::string>(options_[OptCamera]); cameraName = static_cast<std::string>(options_[OptCamera]);
else else
@ -208,6 +235,7 @@ int MainWindow::openCamera()
if (cameraName == "") if (cameraName == "")
return -EINVAL; return -EINVAL;
/* Get and acquire the camera. */
camera_ = cm_->get(cameraName); camera_ = cm_->get(cameraName);
if (!camera_) { if (!camera_) {
std::cout << "Camera " << cameraName << " not found" std::cout << "Camera " << cameraName << " not found"
@ -226,6 +254,10 @@ int MainWindow::openCamera()
return 0; return 0;
} }
/* -----------------------------------------------------------------------------
* Capture Start & Stop
*/
void MainWindow::toggleCapture(bool start) void MainWindow::toggleCapture(bool start)
{ {
if (start) { if (start) {
@ -239,10 +271,16 @@ void MainWindow::toggleCapture(bool start)
} }
} }
/**
* \brief Start capture with the current camera
*
* This function shall not be called directly, use toggleCapture() instead.
*/
int MainWindow::startCapture() int MainWindow::startCapture()
{ {
int ret; int ret;
/* Configure the camera. */
config_ = camera_->generateConfiguration({ StreamRole::Viewfinder }); config_ = camera_->generateConfiguration({ StreamRole::Viewfinder });
StreamConfiguration &cfg = config_->at(0); StreamConfiguration &cfg = config_->at(0);
@ -279,6 +317,7 @@ int MainWindow::startCapture()
return ret; return ret;
} }
/* Configure the viewfinder. */
Stream *stream = cfg.stream(); Stream *stream = cfg.stream();
ret = viewfinder_->setFormat(cfg.pixelFormat, ret = viewfinder_->setFormat(cfg.pixelFormat,
QSize(cfg.size.width, cfg.size.height)); QSize(cfg.size.width, cfg.size.height));
@ -289,6 +328,7 @@ int MainWindow::startCapture()
adjustSize(); adjustSize();
/* Allocate buffers and requests. */
allocator_ = new FrameBufferAllocator(camera_); allocator_ = new FrameBufferAllocator(camera_);
ret = allocator_->allocate(stream); ret = allocator_->allocate(stream);
if (ret < 0) { if (ret < 0) {
@ -321,6 +361,7 @@ int MainWindow::startCapture()
std::make_pair(memory, plane.length); std::make_pair(memory, plane.length);
} }
/* Start the title timer and the camera. */
titleTimer_.start(2000); titleTimer_.start(2000);
frameRateInterval_.start(); frameRateInterval_.start();
previousFrames_ = 0; previousFrames_ = 0;
@ -335,6 +376,7 @@ int MainWindow::startCapture()
camera_->requestCompleted.connect(this, &MainWindow::requestComplete); camera_->requestCompleted.connect(this, &MainWindow::requestComplete);
/* Queue all requests. */
for (Request *request : requests) { for (Request *request : requests) {
ret = camera_->queueRequest(request); ret = camera_->queueRequest(request);
if (ret < 0) { if (ret < 0) {
@ -368,6 +410,12 @@ error:
return ret; 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() void MainWindow::stopCapture()
{ {
if (!isCapturing_) if (!isCapturing_)
@ -403,6 +451,10 @@ void MainWindow::stopCapture()
setWindowTitle(title_); setWindowTitle(title_);
} }
/* -----------------------------------------------------------------------------
* Image Save
*/
void MainWindow::saveImageAs() void MainWindow::saveImageAs()
{ {
QImage image = viewfinder_->getCurrentImage(); QImage image = viewfinder_->getCurrentImage();
@ -418,11 +470,20 @@ void MainWindow::saveImageAs()
writer.write(image); writer.write(image);
} }
/* -----------------------------------------------------------------------------
* Request Completion Handling
*/
void MainWindow::requestComplete(Request *request) void MainWindow::requestComplete(Request *request)
{ {
if (request->status() == Request::RequestCancelled) if (request->status() == Request::RequestCancelled)
return; 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.
*/
const std::map<Stream *, FrameBuffer *> &buffers = request->buffers(); const std::map<Stream *, FrameBuffer *> &buffers = request->buffers();
FrameBuffer *buffer = buffers.begin()->second; FrameBuffer *buffer = buffers.begin()->second;
@ -436,6 +497,11 @@ void MainWindow::requestComplete(Request *request)
void MainWindow::processCapture() 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.
*/
FrameBuffer *buffer; FrameBuffer *buffer;
{ {
@ -460,6 +526,7 @@ void MainWindow::processCapture()
<< " fps: " << std::fixed << std::setprecision(2) << fps << " fps: " << std::fixed << std::setprecision(2) << fps
<< std::endl; << std::endl;
/* Display the buffer and requeue it to the camera. */
display(buffer); display(buffer);
queueRequest(buffer); queueRequest(buffer);

View file

@ -57,6 +57,7 @@ private Q_SLOTS:
private: private:
int createToolbars(); int createToolbars();
std::string chooseCamera(); std::string chooseCamera();
int openCamera(); int openCamera();
@ -68,34 +69,37 @@ private:
int display(FrameBuffer *buffer); int display(FrameBuffer *buffer);
void queueRequest(FrameBuffer *buffer); void queueRequest(FrameBuffer *buffer);
/* UI elements */
QToolBar *toolbar_;
QAction *startStopAction_;
ViewFinder *viewfinder_;
QIcon iconPlay_; QIcon iconPlay_;
QIcon iconStop_; QIcon iconStop_;
QString title_; QString title_;
QTimer titleTimer_; QTimer titleTimer_;
/* Options */
const OptionsParser::Options &options_; const OptionsParser::Options &options_;
/* Camera manager, camera, configuration and buffers */
CameraManager *cm_; CameraManager *cm_;
std::shared_ptr<Camera> camera_; std::shared_ptr<Camera> camera_;
FrameBufferAllocator *allocator_; FrameBufferAllocator *allocator_;
bool isCapturing_;
std::unique_ptr<CameraConfiguration> config_; std::unique_ptr<CameraConfiguration> config_;
std::map<int, std::pair<void *, unsigned int>> mappedBuffers_;
/* Capture state, buffers queue and statistics */
bool isCapturing_;
QQueue<FrameBuffer *> doneQueue_;
QMutex mutex_; /* Protects doneQueue_ */
uint64_t lastBufferTime_; uint64_t lastBufferTime_;
QElapsedTimer frameRateInterval_; QElapsedTimer frameRateInterval_;
uint32_t previousFrames_; uint32_t previousFrames_;
uint32_t framesCaptured_; uint32_t framesCaptured_;
QMutex mutex_;
QQueue<FrameBuffer *> doneQueue_;
QToolBar *toolbar_;
QAction *startStopAction_;
ViewFinder *viewfinder_;
std::map<int, std::pair<void *, unsigned int>> mappedBuffers_;
}; };
#endif /* __QCAM_MAIN_WINDOW__ */ #endif /* __QCAM_MAIN_WINDOW__ */