From patchwork Thu Apr 30 00:36:00 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Niklas_S=C3=B6derlund?= X-Patchwork-Id: 3614 Return-Path: Received: from vsp-unauthed02.binero.net (vsp-unauthed02.binero.net [195.74.38.227]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id BCDDA6041C for ; Thu, 30 Apr 2020 02:36:36 +0200 (CEST) X-Halon-ID: 8687aed3-8a7a-11ea-89d0-0050569116f7 Authorized-sender: niklas@soderlund.pp.se Received: from bismarck.berto.se (p4fca2392.dip0.t-ipconnect.de [79.202.35.146]) by bin-vsp-out-03.atm.binero.net (Halon) with ESMTPA id 8687aed3-8a7a-11ea-89d0-0050569116f7; Thu, 30 Apr 2020 02:35:43 +0200 (CEST) From: =?utf-8?q?Niklas_S=C3=B6derlund?= To: libcamera-devel@lists.libcamera.org Date: Thu, 30 Apr 2020 02:36:00 +0200 Message-Id: <20200430003604.2423018-2-niklas.soderlund@ragnatech.se> X-Mailer: git-send-email 2.26.0 In-Reply-To: <20200430003604.2423018-1-niklas.soderlund@ragnatech.se> References: <20200430003604.2423018-1-niklas.soderlund@ragnatech.se> MIME-Version: 1.0 Subject: [libcamera-devel] [RFC/PATCH 1/5] qcam: Check that camera can generate configuration from roles X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 30 Apr 2020 00:36:37 -0000 If the camera can not generate a configuration from the requested roles it returns a nullptr which leads to a nullptr dereference. Fix this by adding a check that the camera generated a configuration before trying to access it. Signed-off-by: Niklas Söderlund Reviewed-by: Laurent Pinchart --- src/qcam/main_window.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp index ee779728fc630da8..b683c2e00d317307 100644 --- a/src/qcam/main_window.cpp +++ b/src/qcam/main_window.cpp @@ -293,6 +293,10 @@ int MainWindow::startCapture() /* Configure the camera. */ config_ = camera_->generateConfiguration(roles); + if (!config_) { + qWarning() << "Failed to generate configuration from roles"; + return -EINVAL; + } StreamConfiguration &cfg = config_->at(0); From patchwork Thu Apr 30 00:36:01 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Niklas_S=C3=B6derlund?= X-Patchwork-Id: 3616 Return-Path: Received: from bin-mail-out-05.binero.net (bin-mail-out-05.binero.net [195.74.38.228]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1732460AF5 for ; Thu, 30 Apr 2020 02:36:37 +0200 (CEST) X-Halon-ID: 86bd1769-8a7a-11ea-89d0-0050569116f7 Authorized-sender: niklas@soderlund.pp.se Received: from bismarck.berto.se (p4fca2392.dip0.t-ipconnect.de [79.202.35.146]) by bin-vsp-out-03.atm.binero.net (Halon) with ESMTPA id 86bd1769-8a7a-11ea-89d0-0050569116f7; Thu, 30 Apr 2020 02:35:44 +0200 (CEST) From: =?utf-8?q?Niklas_S=C3=B6derlund?= To: libcamera-devel@lists.libcamera.org Date: Thu, 30 Apr 2020 02:36:01 +0200 Message-Id: <20200430003604.2423018-3-niklas.soderlund@ragnatech.se> X-Mailer: git-send-email 2.26.0 In-Reply-To: <20200430003604.2423018-1-niklas.soderlund@ragnatech.se> References: <20200430003604.2423018-1-niklas.soderlund@ragnatech.se> MIME-Version: 1.0 Subject: [libcamera-devel] [RFC/PATCH 2/5] qcam: Allow for a second raw stream to be configured X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 30 Apr 2020 00:36:37 -0000 Allow a second stream to be configured for raw capture. This change only adds support for configuring and allocating buffers for the second stream. Later changes are needed to queue the allocated buffers to the camera when the user wish to capture a raw frame. Signed-off-by: Niklas Söderlund --- src/qcam/main_window.cpp | 133 +++++++++++++++++++++++++++------------ src/qcam/main_window.h | 8 ++- 2 files changed, 99 insertions(+), 42 deletions(-) diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp index b683c2e00d317307..e77bc01df8f3edfe 100644 --- a/src/qcam/main_window.cpp +++ b/src/qcam/main_window.cpp @@ -278,17 +278,30 @@ void MainWindow::toggleCapture(bool start) int MainWindow::startCapture() { StreamRoles roles = StreamKeyValueParser::roles(options_[OptStream]); + std::vector requests; int ret; /* Verify roles are supported. */ - if (roles.size() != 1) { - qWarning() << "Only one stream supported"; - return -EINVAL; - } - - if (roles[0] != StreamRole::Viewfinder) { - qWarning() << "Only viewfinder supported"; - return -EINVAL; + 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::StillCaptureRaw) { + qWarning() << "Only viewfinder + raw supported for dual streams"; + return -EINVAL; + } + break; + default: + if (roles.size() != 1) { + qWarning() << "Unsuported stream configuration"; + return -EINVAL; + } + break; } /* Configure the camera. */ @@ -298,17 +311,17 @@ int MainWindow::startCapture() return -EINVAL; } - StreamConfiguration &cfg = config_->at(0); + StreamConfiguration &vfConfig = config_->at(0); /* Use a format supported by the viewfinder if available. */ - std::vector formats = cfg.formats().pixelformats(); + std::vector 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()) { - cfg.pixelFormat = format; + vfConfig.pixelFormat = format; break; } } @@ -326,7 +339,7 @@ int MainWindow::startCapture() if (validation == CameraConfiguration::Adjusted) qInfo() << "Stream configuration adjusted to " - << cfg.toString().c_str(); + << vfConfig.toString().c_str(); ret = camera_->configure(config_.get()); if (ret < 0) { @@ -334,10 +347,16 @@ int MainWindow::startCapture() 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. */ - Stream *stream = cfg.stream(); - ret = viewfinder_->setFormat(cfg.pixelFormat, - QSize(cfg.size.width, cfg.size.height)); + ret = viewfinder_->setFormat(vfConfig.pixelFormat, + QSize(vfConfig.size.width, vfConfig.size.height)); if (ret < 0) { qInfo() << "Failed to set viewfinder format"; return ret; @@ -345,16 +364,33 @@ int MainWindow::startCapture() adjustSize(); - /* Allocate buffers and requests. */ + /* Allocate and map buffers. */ allocator_ = new FrameBufferAllocator(camera_); - ret = allocator_->allocate(stream); - if (ret < 0) { - qWarning() << "Failed to allocate capture buffers"; - return ret; + 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 &buffer : allocator_->buffers(stream)) { + /* Map memory buffers and cache the mappings. */ + const FrameBuffer::Plane &plane = buffer->planes().front(); + void *memory = mmap(NULL, plane.length, PROT_READ, MAP_SHARED, + plane.fd.fd(), 0); + mappedBuffers_[buffer.get()] = { memory, plane.length }; + + /* Store buffers on the free list. */ + freeBuffers_[stream].enqueue(buffer.get()); + } } - std::vector requests; - for (const std::unique_ptr &buffer : allocator_->buffers(stream)) { + /* Create requests and fill it with buffers from the viewfinder. */ + while (!freeBuffers_[vfStream_].isEmpty()) { + FrameBuffer *buffer = freeBuffers_[vfStream_].dequeue(); + Request *request = camera_->createRequest(); if (!request) { qWarning() << "Can't create request"; @@ -362,19 +398,13 @@ int MainWindow::startCapture() goto error; } - ret = request->addBuffer(stream, buffer.get()); + ret = request->addBuffer(vfStream_, buffer); if (ret < 0) { qWarning() << "Can't set buffer for request"; goto error; } requests.push_back(request); - - /* Map memory buffers and cache the mappings. */ - const FrameBuffer::Plane &plane = buffer->planes().front(); - void *memory = mmap(NULL, plane.length, PROT_READ, MAP_SHARED, - plane.fd.fd(), 0); - mappedBuffers_[buffer.get()] = { memory, plane.length }; } /* Start the title timer and the camera. */ @@ -419,6 +449,8 @@ error: } mappedBuffers_.clear(); + freeBuffers_.clear(); + delete allocator_; allocator_ = nullptr; @@ -461,7 +493,8 @@ void MainWindow::stopCapture() * but not processed yet. Clear the queue of done buffers to avoid * racing with the event handler. */ - doneQueue_.clear(); + freeBuffers_.clear(); + doneBuffers_.clear(); titleTimer_.stop(); setWindowTitle(title_); @@ -500,12 +533,9 @@ void MainWindow::requestComplete(Request *request) * are not allowed. Add the buffer to the done queue and post a * CaptureEvent for the application thread to handle. */ - const std::map &buffers = request->buffers(); - FrameBuffer *buffer = buffers.begin()->second; - { QMutexLocker locker(&mutex_); - doneQueue_.enqueue(buffer); + doneBuffers_.push_back(request->buffers()); } QCoreApplication::postEvent(this, new CaptureEvent); @@ -518,16 +548,40 @@ void MainWindow::processCapture() * if stopCapture() has been called while a CaptureEvent was posted but * not processed yet. Return immediately in that case. */ - FrameBuffer *buffer; - + std::vector> doneBuffers; { QMutexLocker locker(&mutex_); - if (doneQueue_.isEmpty()) + if (doneBuffers_.empty()) return; - buffer = doneQueue_.dequeue(); + doneBuffers = std::move(doneBuffers_); + doneBuffers_.clear(); } + /* Process buffers. */ + for (std::map &buffers : doneBuffers) { + if (buffers.count(vfStream_)) + processViewfinder(buffers[vfStream_]); + + /* + * Return buffers so they can be reused. No processing involving + * a buffer can happen after they are returned to the free list. + */ + for (auto &it : buffers) { + Stream *stream = it.first; + FrameBuffer *buffer = it.second; + + /* The ViewFinder manages the viewfinder buffers. */ + if (stream == vfStream_) + continue; + + freeBuffers_[stream].enqueue(buffer); + } + } +} + +void MainWindow::processViewfinder(FrameBuffer *buffer) +{ framesCaptured_++; const FrameMetadata &metadata = buffer->metadata(); @@ -554,8 +608,7 @@ void MainWindow::queueRequest(FrameBuffer *buffer) return; } - Stream *stream = config_->at(0).stream(); - request->addBuffer(stream, buffer); + request->addBuffer(vfStream_, buffer); camera_->queueRequest(request); } diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h index 5e9d9b8d9c6b2d6d..c2040c0ebcd61bfa 100644 --- a/src/qcam/main_window.h +++ b/src/qcam/main_window.h @@ -68,6 +68,7 @@ private: void requestComplete(Request *request); void processCapture(); + void processViewfinder(FrameBuffer *buffer); /* UI elements */ QToolBar *toolbar_; @@ -93,8 +94,11 @@ private: /* Capture state, buffers queue and statistics */ bool isCapturing_; - QQueue doneQueue_; - QMutex mutex_; /* Protects doneQueue_ */ + Stream *vfStream_; + Stream *rawStream_; + std::map> freeBuffers_; + std::vector> doneBuffers_; + QMutex mutex_; /* Protects freeBuffers_ and doneBuffers_ */ uint64_t lastBufferTime_; QElapsedTimer frameRateInterval_; From patchwork Thu Apr 30 00:36:02 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Niklas_S=C3=B6derlund?= X-Patchwork-Id: 3617 Return-Path: Received: from bin-mail-out-06.binero.net (bin-mail-out-06.binero.net [195.74.38.229]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8194260AF5 for ; Thu, 30 Apr 2020 02:36:37 +0200 (CEST) X-Halon-ID: 872db93e-8a7a-11ea-89d0-0050569116f7 Authorized-sender: niklas@soderlund.pp.se Received: from bismarck.berto.se (p4fca2392.dip0.t-ipconnect.de [79.202.35.146]) by bin-vsp-out-03.atm.binero.net (Halon) with ESMTPA id 872db93e-8a7a-11ea-89d0-0050569116f7; Thu, 30 Apr 2020 02:35:44 +0200 (CEST) From: =?utf-8?q?Niklas_S=C3=B6derlund?= To: libcamera-devel@lists.libcamera.org Date: Thu, 30 Apr 2020 02:36:02 +0200 Message-Id: <20200430003604.2423018-4-niklas.soderlund@ragnatech.se> X-Mailer: git-send-email 2.26.0 In-Reply-To: <20200430003604.2423018-1-niklas.soderlund@ragnatech.se> References: <20200430003604.2423018-1-niklas.soderlund@ragnatech.se> MIME-Version: 1.0 Subject: [libcamera-devel] [RFC/PATCH 3/5] qcam: Add an option to allow specifying output path X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 30 Apr 2020 00:36:37 -0000 Add a command line option to specify an output path for files captured. This will become more useful when JPEG capture is turned into an quick operation instead of using a QFileDialog and when RAW capture support is added. Signed-off-by: Niklas Söderlund Reviewed-by: Laurent Pinchart --- src/qcam/main.cpp | 3 +++ src/qcam/main_window.cpp | 8 ++++++-- src/qcam/main_window.h | 2 ++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/qcam/main.cpp b/src/qcam/main.cpp index cd73fa764614e7e7..2999e4959d8ecff8 100644 --- a/src/qcam/main.cpp +++ b/src/qcam/main.cpp @@ -34,6 +34,9 @@ OptionsParser::Options parseOptions(int argc, char *argv[]) "help"); parser.addOption(OptStream, &streamKeyValue, "Set configuration of a camera stream", "stream", true); + parser.addOption(OptSavePath, OptionString, + "Path to qcam picture storage", + "output-dir", ArgumentRequired, "path"); OptionsParser::Options options = parser.parse(argc, argv); if (options.isSet(OptHelp)) diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp index e77bc01df8f3edfe..470f78162cabffe5 100644 --- a/src/qcam/main_window.cpp +++ b/src/qcam/main_window.cpp @@ -75,6 +75,11 @@ MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options) return; } + if (options_.isSet(OptSavePath)) + defaultPath_ = QString::fromUtf8(options_[OptSavePath].toString().c_str()); + else + defaultPath_ = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); + startStopAction_->setChecked(true); } @@ -507,9 +512,8 @@ void MainWindow::stopCapture() void MainWindow::saveImageAs() { QImage image = viewfinder_->getCurrentImage(); - QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); - QString filename = QFileDialog::getSaveFileName(this, "Save Image", defaultPath, + QString filename = QFileDialog::getSaveFileName(this, "Save Image", defaultPath_, "Image Files (*.png *.jpg *.jpeg)"); if (filename.isEmpty()) return; diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h index c2040c0ebcd61bfa..7b8a185511e17a8f 100644 --- a/src/qcam/main_window.h +++ b/src/qcam/main_window.h @@ -33,6 +33,7 @@ class QAction; enum { OptCamera = 'c', OptHelp = 'h', + OptSavePath = 'o', OptStream = 's', }; @@ -83,6 +84,7 @@ private: /* Options */ const OptionsParser::Options &options_; + QString defaultPath_; /* Camera manager, camera, configuration and buffers */ CameraManager *cm_; From patchwork Thu Apr 30 00:36:03 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Niklas_S=C3=B6derlund?= X-Patchwork-Id: 3618 Return-Path: Received: from bin-mail-out-06.binero.net (bin-mail-out-06.binero.net [195.74.38.229]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id F1C5660AF5 for ; Thu, 30 Apr 2020 02:36:37 +0200 (CEST) X-Halon-ID: 8773b20a-8a7a-11ea-89d0-0050569116f7 Authorized-sender: niklas@soderlund.pp.se Received: from bismarck.berto.se (p4fca2392.dip0.t-ipconnect.de [79.202.35.146]) by bin-vsp-out-03.atm.binero.net (Halon) with ESMTPA id 8773b20a-8a7a-11ea-89d0-0050569116f7; Thu, 30 Apr 2020 02:35:45 +0200 (CEST) From: =?utf-8?q?Niklas_S=C3=B6derlund?= To: libcamera-devel@lists.libcamera.org Date: Thu, 30 Apr 2020 02:36:03 +0200 Message-Id: <20200430003604.2423018-5-niklas.soderlund@ragnatech.se> X-Mailer: git-send-email 2.26.0 In-Reply-To: <20200430003604.2423018-1-niklas.soderlund@ragnatech.se> References: <20200430003604.2423018-1-niklas.soderlund@ragnatech.se> MIME-Version: 1.0 Subject: [libcamera-devel] [RFC/PATCH 4/5] qcam: Remove QFileDialog from JPEG capture X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 30 Apr 2020 00:36:38 -0000 Instead of popping up a dialog to enter a filename to save use the sequence number and the default output directory to generate a path. Signed-off-by: Niklas Söderlund --- src/qcam/main_window.cpp | 40 +++++++++++++++++++++++++++------------- src/qcam/main_window.h | 3 ++- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp index 470f78162cabffe5..f57aaf4a27e5f4ca 100644 --- a/src/qcam/main_window.cpp +++ b/src/qcam/main_window.cpp @@ -8,6 +8,7 @@ #include "main_window.h" #include +#include #include #include @@ -146,8 +147,7 @@ int MainWindow::createToolbars() action = toolbar_->addAction(QIcon::fromTheme("document-save-as", QIcon(":save.svg")), "Save As..."); - action->setShortcut(QKeySequence::SaveAs); - connect(action, &QAction::triggered, this, &MainWindow::saveImageAs); + connect(action, &QAction::triggered, this, &MainWindow::saveViewfinder); return 0; } @@ -509,18 +509,9 @@ void MainWindow::stopCapture() * Image Save */ -void MainWindow::saveImageAs() +void MainWindow::saveViewfinder() { - QImage image = viewfinder_->getCurrentImage(); - - 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); + saveViewfinder_ = true; } /* ----------------------------------------------------------------------------- @@ -602,6 +593,29 @@ void MainWindow::processViewfinder(FrameBuffer *buffer) /* Render the frame on the viewfinder. */ viewfinder_->render(buffer, &mappedBuffers_[buffer]); + + /* Save viewfinder to file if requested. */ + if (saveViewfinder_) { + std::string filename = defaultPath_.toStdString() + "/picture-#.jpeg"; + size_t pos; + + pos = filename.find_first_of('#'); + if (pos != std::string::npos) { + std::stringstream ss; + ss << std::setw(6) << std::setfill('0') + << buffer->metadata().sequence; + filename.replace(pos, 1, ss.str()); + } + + qInfo() << "Saving viewfinder to" << filename.c_str(); + + QImage image = viewfinder_->getCurrentImage(); + QImageWriter writer(QString::fromUtf8(filename.c_str())); + writer.setQuality(95); + writer.write(image); + + saveViewfinder_ = false; + } } void MainWindow::queueRequest(FrameBuffer *buffer) diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h index 7b8a185511e17a8f..580bcac146fabe07 100644 --- a/src/qcam/main_window.h +++ b/src/qcam/main_window.h @@ -54,7 +54,7 @@ private Q_SLOTS: void switchCamera(int index); void toggleCapture(bool start); - void saveImageAs(); + void saveViewfinder(); void queueRequest(FrameBuffer *buffer); @@ -96,6 +96,7 @@ private: /* Capture state, buffers queue and statistics */ bool isCapturing_; + bool saveViewfinder_; Stream *vfStream_; Stream *rawStream_; std::map> freeBuffers_; From patchwork Thu Apr 30 00:36:04 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Niklas_S=C3=B6derlund?= X-Patchwork-Id: 3619 Return-Path: Received: from bin-mail-out-06.binero.net (bin-mail-out-06.binero.net [195.74.38.229]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id A11A7613C1 for ; Thu, 30 Apr 2020 02:36:38 +0200 (CEST) X-Halon-ID: 87c5111f-8a7a-11ea-89d0-0050569116f7 Authorized-sender: niklas@soderlund.pp.se Received: from bismarck.berto.se (p4fca2392.dip0.t-ipconnect.de [79.202.35.146]) by bin-vsp-out-03.atm.binero.net (Halon) with ESMTPA id 87c5111f-8a7a-11ea-89d0-0050569116f7; Thu, 30 Apr 2020 02:35:45 +0200 (CEST) From: =?utf-8?q?Niklas_S=C3=B6derlund?= To: libcamera-devel@lists.libcamera.org Date: Thu, 30 Apr 2020 02:36:04 +0200 Message-Id: <20200430003604.2423018-6-niklas.soderlund@ragnatech.se> X-Mailer: git-send-email 2.26.0 In-Reply-To: <20200430003604.2423018-1-niklas.soderlund@ragnatech.se> References: <20200430003604.2423018-1-niklas.soderlund@ragnatech.se> MIME-Version: 1.0 Subject: [libcamera-devel] [RFC/PATCH 5/5] qcam: Add RAW capture support X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 30 Apr 2020 00:36:40 -0000 Add a toolbar button that captures RAW data to disk. The button is only enabled if the camera is configured to provide a raw stream to the application. Only when the capture action is triggered will a request with a raw buffer be queued to the camera. Signed-off-by: Niklas Söderlund --- src/qcam/assets/feathericons/feathericons.qrc | 1 + src/qcam/main_window.cpp | 94 ++++++++++++++++++- src/qcam/main_window.h | 4 + 3 files changed, 98 insertions(+), 1 deletion(-) diff --git a/src/qcam/assets/feathericons/feathericons.qrc b/src/qcam/assets/feathericons/feathericons.qrc index c4eb7a0be6884373..fc8213928ece70ea 100644 --- a/src/qcam/assets/feathericons/feathericons.qrc +++ b/src/qcam/assets/feathericons/feathericons.qrc @@ -1,5 +1,6 @@ +./aperture.svg ./camera-off.svg ./play-circle.svg ./save.svg diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp index f57aaf4a27e5f4ca..535fa53d6705a1a9 100644 --- a/src/qcam/main_window.cpp +++ b/src/qcam/main_window.cpp @@ -7,10 +7,13 @@ #include "main_window.h" +#include #include #include #include #include +#include +#include #include #include @@ -49,7 +52,8 @@ public: }; MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options) - : options_(options), cm_(cm), allocator_(nullptr), isCapturing_(false) + : options_(options), cm_(cm), allocator_(nullptr), isCapturing_(false), + captureRaw_(false) { int ret; @@ -149,6 +153,14 @@ int MainWindow::createToolbars() "Save As..."); connect(action, &QAction::triggered, this, &MainWindow::saveViewfinder); + /* Save Raw action. */ + action = toolbar_->addAction(QIcon::fromTheme("document-save-as", + QIcon(":aperture.svg")), + "Save Raw"); + action->setEnabled(false); + connect(action, &QAction::triggered, this, &MainWindow::saveRaw); + saveRaw_ = action; + return 0; } @@ -369,6 +381,9 @@ int MainWindow::startCapture() adjustSize(); + /* Configure the raw capture button. */ + saveRaw_->setEnabled(config_->size() == 2); + /* Allocate and map buffers. */ allocator_ = new FrameBufferAllocator(camera_); for (StreamConfiguration &config : *config_) { @@ -474,6 +489,7 @@ void MainWindow::stopCapture() return; viewfinder_->stop(); + saveRaw_->setEnabled(false); int ret = camera_->stop(); if (ret) @@ -514,6 +530,11 @@ void MainWindow::saveViewfinder() saveViewfinder_ = true; } +void MainWindow::saveRaw() +{ + captureRaw_ = true; +} + /* ----------------------------------------------------------------------------- * Request Completion Handling */ @@ -558,6 +579,9 @@ void MainWindow::processCapture() if (buffers.count(vfStream_)) processViewfinder(buffers[vfStream_]); + if (buffers.count(rawStream_)) + processRaw(buffers[rawStream_]); + /* * Return buffers so they can be reused. No processing involving * a buffer can happen after they are returned to the free list. @@ -618,6 +642,61 @@ void MainWindow::processViewfinder(FrameBuffer *buffer) } } +void MainWindow::processRaw(FrameBuffer *raw) +{ + /* TODO: Should write a DNG file instead of a .raw and .jpeg file. */ + + unsigned int seq = raw->metadata().sequence; + std::string filename; + int fd, ret = 0; + size_t pos; + + /* Write .raw */ + filename = defaultPath_.toStdString() + "/raw-#.raw"; + pos = filename.find_first_of('#'); + if (pos != std::string::npos) { + std::stringstream ss; + ss << std::setw(6) << std::setfill('0') << seq; + filename.replace(pos, 1, ss.str()); + } + qInfo() << "Saving" << filename.c_str(); + + fd = open(filename.c_str(), O_CREAT | O_WRONLY | + (pos == std::string::npos ? O_APPEND : O_TRUNC), + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if (fd == -1) { + qWarning() << "Failed to open raw output file"; + return; + } + + const MappedBuffer &info = mappedBuffers_[raw]; + ret = ::write(fd, info.memory, info.size); + if (ret < 0) { + ret = -errno; + qWarning() << "write error: " << strerror(-ret); + } else if (ret != (int)info.size) { + qWarning() << "write error: only " << ret + << " bytes written instead of " << info.size; + } + + ::close(fd); + + /* Write scaled thumbnail .jpeg */ + filename = defaultPath_.toStdString() + "/raw-#.jpeg"; + pos = filename.find_first_of('#'); + if (pos != std::string::npos) { + std::stringstream ss; + ss << std::setw(6) << std::setfill('0') << seq; + filename.replace(pos, 1, ss.str()); + } + qInfo() << "Saving" << filename.c_str(); + + QImage image = viewfinder_->getCurrentImage().scaledToHeight(640); + QImageWriter writer(QString::fromUtf8(filename.c_str())); + writer.setQuality(95); + writer.write(image); +} + void MainWindow::queueRequest(FrameBuffer *buffer) { Request *request = camera_->createRequest(); @@ -628,5 +707,18 @@ void MainWindow::queueRequest(FrameBuffer *buffer) request->addBuffer(vfStream_, buffer); + if (captureRaw_) { + QMutexLocker locker(&mutex_); + + if (freeBuffers_[rawStream_].isEmpty()) { + qWarning() << "Raw stream buffer empty"; + return; + } + + request->addBuffer(rawStream_, + freeBuffers_[rawStream_].dequeue()); + captureRaw_ = false; + } + camera_->queueRequest(request); } diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h index 580bcac146fabe07..37a1d95351e144e0 100644 --- a/src/qcam/main_window.h +++ b/src/qcam/main_window.h @@ -55,6 +55,7 @@ private Q_SLOTS: void toggleCapture(bool start); void saveViewfinder(); + void saveRaw(); void queueRequest(FrameBuffer *buffer); @@ -70,10 +71,12 @@ private: void requestComplete(Request *request); void processCapture(); void processViewfinder(FrameBuffer *buffer); + void processRaw(FrameBuffer *raw); /* UI elements */ QToolBar *toolbar_; QAction *startStopAction_; + QAction *saveRaw_; ViewFinder *viewfinder_; QIcon iconPlay_; @@ -97,6 +100,7 @@ private: /* Capture state, buffers queue and statistics */ bool isCapturing_; bool saveViewfinder_; + bool captureRaw_; Stream *vfStream_; Stream *rawStream_; std::map> freeBuffers_;