Message ID | 20200323142205.28342-11-laurent.pinchart@ideasonboard.com |
---|---|
State | Superseded |
Headers | show |
Series |
|
Related | show |
Hi Laurent, On 23/03/2020 14:21, Laurent Pinchart wrote: > 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> Sure, ship it. Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > --- > src/qcam/main_window.cpp | 69 +++++++++++++++++++++++++++++++++++++++- > src/qcam/main_window.h | 24 ++++++++------ > 2 files changed, 82 insertions(+), 11 deletions(-) > > diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp > index 7aaa73e9a709..21a8fcfe711d 100644 > --- a/src/qcam/main_window.cpp > +++ b/src/qcam/main_window.cpp > @@ -32,6 +32,9 @@ > > using namespace libcamera; > > +/** > + * \brief Custom QEvent to signal capture completion > + */ > class CaptureEvent : public QEvent > { > public: > @@ -52,6 +55,10 @@ MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options) > { > int ret; > > + /* > + * Initialize the UI: Create the toolbar, set the window title and > + * create the viewfinder widget. > + */ > createToolbars(); > > title_ = "QCam " + QString::fromStdString(CameraManager::version()); > @@ -62,6 +69,7 @@ MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options) > setCentralWidget(viewfinder_); > adjustSize(); > > + /* Open the camera and start capture. */ > ret = openCamera(); > if (ret < 0) > quit(); > @@ -97,13 +105,14 @@ int MainWindow::createToolbars() > /* 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 selection. */ > + /* Camera selector. */ Poh Tay Toe, Poe Tah Toe. > QComboBox *cameraCombo = new QComboBox(); > connect(cameraCombo, QOverload<int>::of(&QComboBox::activated), > this, &MainWindow::switchCamera); > @@ -115,6 +124,7 @@ int MainWindow::createToolbars() > > toolbar_->addSeparator(); > > + /* Start/Stop action. */ > action = toolbar_->addAction(QIcon::fromTheme("media-playback-start", > QIcon(":play-circle.svg")), > "Start Capture"); > @@ -123,6 +133,7 @@ int MainWindow::createToolbars() > 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..."); > @@ -140,6 +151,7 @@ void MainWindow::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; > @@ -151,8 +163,13 @@ void MainWindow::updateTitle() > 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<unsigned int>(index) >= cameras.size()) > return; > @@ -166,6 +183,10 @@ void MainWindow::switchCamera(int index) > > 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); > > camera_->release(); > @@ -179,9 +200,11 @@ 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]->name(); > > + /* Present a dialog box to pick a camera. */ > for (const std::shared_ptr<Camera> &cam : cm_->cameras()) > cameras.append(QString::fromStdString(cam->name())); > > @@ -198,6 +221,10 @@ 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 > @@ -206,6 +233,7 @@ int MainWindow::openCamera() > if (cameraName == "") > return -EINVAL; > > + /* Get and acquire the camera. */ > camera_ = cm_->get(cameraName); > if (!camera_) { > std::cout << "Camera " << cameraName << " not found" > @@ -224,6 +252,10 @@ int MainWindow::openCamera() > return 0; > } > > +/* ----------------------------------------------------------------------------- > + * Capture Start & Stop > + */ > + > void MainWindow::toggleCapture(bool start) > { > if (start) { > @@ -235,10 +267,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 ret; > > + /* Configure the camera. */ > config_ = camera_->generateConfiguration({ StreamRole::Viewfinder }); > > StreamConfiguration &cfg = config_->at(0); > @@ -275,6 +313,7 @@ int MainWindow::startCapture() > return ret; > } > > + /* Configure the viewfinder. */ > Stream *stream = cfg.stream(); > ret = viewfinder_->setFormat(cfg.pixelFormat, > QSize(cfg.size.width, cfg.size.height)); > @@ -285,6 +324,7 @@ int MainWindow::startCapture() > > adjustSize(); > > + /* Allocate buffers and requests. */ > allocator_ = new FrameBufferAllocator(camera_); > ret = allocator_->allocate(stream); > if (ret < 0) { > @@ -317,6 +357,7 @@ int MainWindow::startCapture() > std::make_pair(memory, plane.length); > } > > + /* Start the title timer and the camera. */ > titleTimer_.start(2000); > frameRateInterval_.start(); > previousFrames_ = 0; > @@ -331,6 +372,7 @@ int MainWindow::startCapture() > > camera_->requestCompleted.connect(this, &MainWindow::requestComplete); > > + /* Queue all requests. */ > for (Request *request : requests) { > ret = camera_->queueRequest(request); > if (ret < 0) { > @@ -364,6 +406,12 @@ error: > 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_) > @@ -399,6 +447,10 @@ void MainWindow::stopCapture() > setWindowTitle(title_); > } > > +/* ----------------------------------------------------------------------------- > + * Image Save > + */ > + > void MainWindow::saveImageAs() > { > QImage image = viewfinder_->getCurrentImage(); > @@ -414,11 +466,20 @@ void MainWindow::saveImageAs() > writer.write(image); > } > > +/* ----------------------------------------------------------------------------- > + * 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. > + */ > const std::map<Stream *, FrameBuffer *> &buffers = request->buffers(); > FrameBuffer *buffer = buffers.begin()->second; > > @@ -432,6 +493,11 @@ void MainWindow::requestComplete(Request *request) > > 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; > { > QMutexLocker locker(&mutex_); > @@ -455,6 +521,7 @@ void MainWindow::processCapture() > << " fps: " << std::fixed << std::setprecision(2) << fps > << std::endl; > > + /* Display the buffer and requeue it to the camera. */ > display(buffer); > > queueRequest(buffer); > diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h > index 3d8d02b44696..fcde88c14f77 100644 > --- a/src/qcam/main_window.h > +++ b/src/qcam/main_window.h > @@ -56,6 +56,7 @@ private Q_SLOTS: > > private: > int createToolbars(); > + > std::string chooseCamera(); > int openCamera(); > > @@ -67,31 +68,34 @@ private: > int display(FrameBuffer *buffer); > void queueRequest(FrameBuffer *buffer); > > + /* UI elements */ > + QToolBar *toolbar_; > + QAction *startStopAction_; > + ViewFinder *viewfinder_; > + > QString title_; > QTimer titleTimer_; > > + /* Options */ > const OptionsParser::Options &options_; > > + /* Camera manager, camera, configuration and buffers */ > CameraManager *cm_; > std::shared_ptr<Camera> camera_; > FrameBufferAllocator *allocator_; > > - bool isCapturing_; > std::unique_ptr<CameraConfiguration> config_; > + std::map<int, std::pair<void *, unsigned int>> mappedBuffers_; > > - uint64_t lastBufferTime_; > + /* Capture state, buffers queue and statistics */ > + bool isCapturing_; > + QQueue<FrameBuffer *> doneQueue_; > + QMutex mutex_; /* Protects doneQueue_ */ > > + uint64_t lastBufferTime_; > QElapsedTimer frameRateInterval_; > uint32_t previousFrames_; > 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__ */ >
diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp index 7aaa73e9a709..21a8fcfe711d 100644 --- a/src/qcam/main_window.cpp +++ b/src/qcam/main_window.cpp @@ -32,6 +32,9 @@ using namespace libcamera; +/** + * \brief Custom QEvent to signal capture completion + */ class CaptureEvent : public QEvent { public: @@ -52,6 +55,10 @@ MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options) { int ret; + /* + * Initialize the UI: Create the toolbar, set the window title and + * create the viewfinder widget. + */ createToolbars(); title_ = "QCam " + QString::fromStdString(CameraManager::version()); @@ -62,6 +69,7 @@ MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options) setCentralWidget(viewfinder_); adjustSize(); + /* Open the camera and start capture. */ ret = openCamera(); if (ret < 0) quit(); @@ -97,13 +105,14 @@ int MainWindow::createToolbars() /* 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 selection. */ + /* Camera selector. */ QComboBox *cameraCombo = new QComboBox(); connect(cameraCombo, QOverload<int>::of(&QComboBox::activated), this, &MainWindow::switchCamera); @@ -115,6 +124,7 @@ int MainWindow::createToolbars() toolbar_->addSeparator(); + /* Start/Stop action. */ action = toolbar_->addAction(QIcon::fromTheme("media-playback-start", QIcon(":play-circle.svg")), "Start Capture"); @@ -123,6 +133,7 @@ int MainWindow::createToolbars() 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..."); @@ -140,6 +151,7 @@ void MainWindow::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; @@ -151,8 +163,13 @@ void MainWindow::updateTitle() 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<unsigned int>(index) >= cameras.size()) return; @@ -166,6 +183,10 @@ void MainWindow::switchCamera(int index) 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); camera_->release(); @@ -179,9 +200,11 @@ 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]->name(); + /* Present a dialog box to pick a camera. */ for (const std::shared_ptr<Camera> &cam : cm_->cameras()) cameras.append(QString::fromStdString(cam->name())); @@ -198,6 +221,10 @@ 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 @@ -206,6 +233,7 @@ int MainWindow::openCamera() if (cameraName == "") return -EINVAL; + /* Get and acquire the camera. */ camera_ = cm_->get(cameraName); if (!camera_) { std::cout << "Camera " << cameraName << " not found" @@ -224,6 +252,10 @@ int MainWindow::openCamera() return 0; } +/* ----------------------------------------------------------------------------- + * Capture Start & Stop + */ + void MainWindow::toggleCapture(bool start) { if (start) { @@ -235,10 +267,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 ret; + /* Configure the camera. */ config_ = camera_->generateConfiguration({ StreamRole::Viewfinder }); StreamConfiguration &cfg = config_->at(0); @@ -275,6 +313,7 @@ int MainWindow::startCapture() return ret; } + /* Configure the viewfinder. */ Stream *stream = cfg.stream(); ret = viewfinder_->setFormat(cfg.pixelFormat, QSize(cfg.size.width, cfg.size.height)); @@ -285,6 +324,7 @@ int MainWindow::startCapture() adjustSize(); + /* Allocate buffers and requests. */ allocator_ = new FrameBufferAllocator(camera_); ret = allocator_->allocate(stream); if (ret < 0) { @@ -317,6 +357,7 @@ int MainWindow::startCapture() std::make_pair(memory, plane.length); } + /* Start the title timer and the camera. */ titleTimer_.start(2000); frameRateInterval_.start(); previousFrames_ = 0; @@ -331,6 +372,7 @@ int MainWindow::startCapture() camera_->requestCompleted.connect(this, &MainWindow::requestComplete); + /* Queue all requests. */ for (Request *request : requests) { ret = camera_->queueRequest(request); if (ret < 0) { @@ -364,6 +406,12 @@ error: 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_) @@ -399,6 +447,10 @@ void MainWindow::stopCapture() setWindowTitle(title_); } +/* ----------------------------------------------------------------------------- + * Image Save + */ + void MainWindow::saveImageAs() { QImage image = viewfinder_->getCurrentImage(); @@ -414,11 +466,20 @@ void MainWindow::saveImageAs() writer.write(image); } +/* ----------------------------------------------------------------------------- + * 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. + */ const std::map<Stream *, FrameBuffer *> &buffers = request->buffers(); FrameBuffer *buffer = buffers.begin()->second; @@ -432,6 +493,11 @@ void MainWindow::requestComplete(Request *request) 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; { QMutexLocker locker(&mutex_); @@ -455,6 +521,7 @@ void MainWindow::processCapture() << " fps: " << std::fixed << std::setprecision(2) << fps << std::endl; + /* Display the buffer and requeue it to the camera. */ display(buffer); queueRequest(buffer); diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h index 3d8d02b44696..fcde88c14f77 100644 --- a/src/qcam/main_window.h +++ b/src/qcam/main_window.h @@ -56,6 +56,7 @@ private Q_SLOTS: private: int createToolbars(); + std::string chooseCamera(); int openCamera(); @@ -67,31 +68,34 @@ private: int display(FrameBuffer *buffer); void queueRequest(FrameBuffer *buffer); + /* UI elements */ + QToolBar *toolbar_; + QAction *startStopAction_; + ViewFinder *viewfinder_; + QString title_; QTimer titleTimer_; + /* Options */ const OptionsParser::Options &options_; + /* Camera manager, camera, configuration and buffers */ CameraManager *cm_; std::shared_ptr<Camera> camera_; FrameBufferAllocator *allocator_; - bool isCapturing_; std::unique_ptr<CameraConfiguration> config_; + std::map<int, std::pair<void *, unsigned int>> mappedBuffers_; - uint64_t lastBufferTime_; + /* Capture state, buffers queue and statistics */ + bool isCapturing_; + QQueue<FrameBuffer *> doneQueue_; + QMutex mutex_; /* Protects doneQueue_ */ + uint64_t lastBufferTime_; QElapsedTimer frameRateInterval_; uint32_t previousFrames_; 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__ */
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> --- src/qcam/main_window.cpp | 69 +++++++++++++++++++++++++++++++++++++++- src/qcam/main_window.h | 24 ++++++++------ 2 files changed, 82 insertions(+), 11 deletions(-)