From patchwork Sat May 2 02:10:43 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: 3658 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 A5AAA61459 for ; Sat, 2 May 2020 04:11:22 +0200 (CEST) X-Halon-ID: 36ed2066-8c1a-11ea-aeed-005056917f90 Authorized-sender: niklas@soderlund.pp.se Received: from bismarck.berto.se (p4fca2392.dip0.t-ipconnect.de [79.202.35.146]) by bin-vsp-out-02.atm.binero.net (Halon) with ESMTPA id 36ed2066-8c1a-11ea-aeed-005056917f90; Sat, 02 May 2020 04:11:20 +0200 (CEST) From: =?utf-8?q?Niklas_S=C3=B6derlund?= To: libcamera-devel@lists.libcamera.org Date: Sat, 2 May 2020 04:10:43 +0200 Message-Id: <20200502021045.785979-2-niklas.soderlund@ragnatech.se> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200502021045.785979-1-niklas.soderlund@ragnatech.se> References: <20200502021045.785979-1-niklas.soderlund@ragnatech.se> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 1/3] 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: Sat, 02 May 2020 02:11:22 -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 wishes to capture a raw frame. Signed-off-by: Niklas Söderlund Reviewed-by: Laurent Pinchart --- * Changes since v1 - Keep doneQueue_ as a QQueue and keep the original processing granularity. * Changes since v2 - Update a comment. --- src/qcam/main_window.cpp | 110 ++++++++++++++++++++++++++------------- src/qcam/main_window.h | 8 ++- 2 files changed, 79 insertions(+), 39 deletions(-) diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp index b9348111dfa28914..0bd9f3583ea4f6d4 100644 --- a/src/qcam/main_window.cpp +++ b/src/qcam/main_window.cpp @@ -281,17 +281,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) { - qCritical() << "Only one stream supported"; - return -EINVAL; - } - - if (roles[0] != StreamRole::Viewfinder) { - qCritical() << "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. */ @@ -301,17 +314,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; } } @@ -331,7 +344,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) { @@ -339,10 +352,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; @@ -350,16 +369,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 them 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"; @@ -367,19 +403,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. */ @@ -424,6 +454,8 @@ error: } mappedBuffers_.clear(); + freeBuffers_.clear(); + delete allocator_; allocator_ = nullptr; @@ -466,6 +498,7 @@ void MainWindow::stopCapture() * but not processed yet. Clear the queue of done buffers to avoid * racing with the event handler. */ + freeBuffers_.clear(); doneQueue_.clear(); titleTimer_.stop(); @@ -505,12 +538,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); + doneQueue_.enqueue(request->buffers()); } QCoreApplication::postEvent(this, new CaptureEvent); @@ -523,16 +553,23 @@ 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::map buffers; { QMutexLocker locker(&mutex_); if (doneQueue_.isEmpty()) return; - buffer = doneQueue_.dequeue(); + buffers = doneQueue_.dequeue(); } + /* Process buffers. */ + if (buffers.count(vfStream_)) + processViewfinder(buffers[vfStream_]); +} + +void MainWindow::processViewfinder(FrameBuffer *buffer) +{ framesCaptured_++; const FrameMetadata &metadata = buffer->metadata(); @@ -559,8 +596,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 aea1f1dee20fcbb6..4856ecc10729159c 100644 --- a/src/qcam/main_window.h +++ b/src/qcam/main_window.h @@ -69,6 +69,7 @@ private: void requestComplete(Request *request); void processCapture(); + void processViewfinder(FrameBuffer *buffer); /* UI elements */ QToolBar *toolbar_; @@ -95,8 +96,11 @@ private: /* Capture state, buffers queue and statistics */ bool isCapturing_; - QQueue doneQueue_; - QMutex mutex_; /* Protects doneQueue_ */ + Stream *vfStream_; + Stream *rawStream_; + std::map> freeBuffers_; + QQueue> doneQueue_; + QMutex mutex_; /* Protects freeBuffers_ and doneQueue_ */ uint64_t lastBufferTime_; QElapsedTimer frameRateInterval_; From patchwork Sat May 2 02:10:44 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: 3659 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 A440A61618 for ; Sat, 2 May 2020 04:11:23 +0200 (CEST) X-Halon-ID: 37742134-8c1a-11ea-aeed-005056917f90 Authorized-sender: niklas@soderlund.pp.se Received: from bismarck.berto.se (p4fca2392.dip0.t-ipconnect.de [79.202.35.146]) by bin-vsp-out-02.atm.binero.net (Halon) with ESMTPA id 37742134-8c1a-11ea-aeed-005056917f90; Sat, 02 May 2020 04:11:21 +0200 (CEST) From: =?utf-8?q?Niklas_S=C3=B6derlund?= To: libcamera-devel@lists.libcamera.org Date: Sat, 2 May 2020 04:10:44 +0200 Message-Id: <20200502021045.785979-3-niklas.soderlund@ragnatech.se> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200502021045.785979-1-niklas.soderlund@ragnatech.se> References: <20200502021045.785979-1-niklas.soderlund@ragnatech.se> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 2/3] qcam: Add DNGWriter 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: Sat, 02 May 2020 02:11:24 -0000 Add an initial DNG file writer. The writer can only deal with a small set of pixelformats. The generated file is consumable by standard tools. The writer needs to be extended to write more metadata to the generated file. Signed-off-by: Niklas Söderlund Reviewed-by: Laurent Pinchart --- * Changes since v2 - Mark input arg as const - Fold getFormatInfo() into only caller - Male DNGWriter::write() static and take filename as argument - Make DNGWriter::write() take StreamConfiguraiton instead of Stream - Optimize scanline loop - Make libtiff dependecy optional - Add 12 byte formats from Laurent - Reformat table from Laurent --- src/qcam/dng_writer.cpp | 165 ++++++++++++++++++++++++++++++++++++++++ src/qcam/dng_writer.h | 24 ++++++ src/qcam/meson.build | 21 ++++- 3 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 src/qcam/dng_writer.cpp create mode 100644 src/qcam/dng_writer.h diff --git a/src/qcam/dng_writer.cpp b/src/qcam/dng_writer.cpp new file mode 100644 index 0000000000000000..ed8f276741a954f9 --- /dev/null +++ b/src/qcam/dng_writer.cpp @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Raspberry Pi (Trading) Ltd. + * + * dng_writer.cpp - DNG writer + */ + +#include "dng_writer.h" + +#include +#include + +#include + +using namespace libcamera; + +enum CFAPatternColour : uint8_t { + CFAPatternRed = 0, + CFAPatternGreen = 1, + CFAPatternBlue = 2, +}; + +struct FormatInfo { + uint8_t bitsPerSample; + CFAPatternColour pattern[4]; + void (*packScanline)(void *, const void *, unsigned int); +}; + +void packScanlineSBGGR10P(void *output, const void *input, unsigned int width) +{ + const uint8_t *in = static_cast(input); + uint8_t *out = static_cast(output); + + /* \todo Can this be made more efficient? */ + for (unsigned int x = 0; x < width; x += 4) { + *out++ = in[0]; + *out++ = (in[4] & 0x03) << 6 | in[1] >> 2; + *out++ = (in[1] & 0x03) << 6 | (in[4] & 0x0c) << 2 | in[2] >> 4; + *out++ = (in[2] & 0x0f) << 4 | (in[4] & 0x30) >> 2 | in[3] >> 6; + *out++ = (in[3] & 0x3f) << 2 | (in[4] & 0xc0) >> 6; + in += 5; + } +} + +void packScanlineSBGGR12P(void *output, const void *input, unsigned int width) +{ + const uint8_t *in = static_cast(input); + uint8_t *out = static_cast(output); + + /* \todo: Can this be made more efficient? */ + for (unsigned int i = 0; i < width; i += 2) { + *out++ = in[0]; + *out++ = (in[2] & 0x0f) << 4 | in[1] >> 4; + *out++ = (in[1] & 0x0f) << 4 | in[2] >> 4; + in += 3; + } +} + +static const std::map formatInfo = { + { PixelFormat(DRM_FORMAT_SBGGR10, MIPI_FORMAT_MOD_CSI2_PACKED), { + .bitsPerSample = 10, + .pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed }, + .packScanline = packScanlineSBGGR10P, + } }, + { PixelFormat(DRM_FORMAT_SGBRG10, MIPI_FORMAT_MOD_CSI2_PACKED), { + .bitsPerSample = 10, + .pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen }, + packScanlineSBGGR10P, + } }, + { PixelFormat(DRM_FORMAT_SGRBG10, MIPI_FORMAT_MOD_CSI2_PACKED), { + .bitsPerSample = 10, + .pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen }, + .packScanline = packScanlineSBGGR10P, + } }, + { PixelFormat(DRM_FORMAT_SRGGB10, MIPI_FORMAT_MOD_CSI2_PACKED), { + .bitsPerSample = 10, + .pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue }, + .packScanline = packScanlineSBGGR10P, + } }, + { PixelFormat(DRM_FORMAT_SBGGR12, MIPI_FORMAT_MOD_CSI2_PACKED), { + .bitsPerSample = 12, + .pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed }, + .packScanline = packScanlineSBGGR12P, + } }, + { PixelFormat(DRM_FORMAT_SGBRG12, MIPI_FORMAT_MOD_CSI2_PACKED), { + .bitsPerSample = 12, + .pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen }, + .packScanline = packScanlineSBGGR12P, + } }, + { PixelFormat(DRM_FORMAT_SGRBG12, MIPI_FORMAT_MOD_CSI2_PACKED), { + .bitsPerSample = 12, + .pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen }, + .packScanline = packScanlineSBGGR12P, + } }, + { PixelFormat(DRM_FORMAT_SRGGB12, MIPI_FORMAT_MOD_CSI2_PACKED), { + .bitsPerSample = 12, + .pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue }, + .packScanline = packScanlineSBGGR12P, + } }, +}; + +int DNGWriter::write(const char *filename, const Camera *camera, + const StreamConfiguration &config, + FrameBuffer *buffer, void *data) +{ + const auto it = formatInfo.find(config.pixelFormat); + if (it == formatInfo.cend()) { + std::cerr << "Unsupported pixel format" << std::endl; + return -EINVAL; + } + const FormatInfo *info = &it->second; + + TIFF *tif = TIFFOpen(filename, "w"); + if (!tif) { + std::cerr << "Failed to open tiff file" << std::endl; + return -EINVAL; + } + + const uint8_t version[] = { 1, 2, 0, 0 }; + const uint16_t cfa_repeatpatterndim[] = { 2, 2 }; + TIFFSetField(tif, TIFFTAG_DNGVERSION, version); + TIFFSetField(tif, TIFFTAG_DNGBACKWARDVERSION, version); + TIFFSetField(tif, TIFFTAG_SUBFILETYPE, 0); + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, config.size.width); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, config.size.height); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, info->bitsPerSample); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CFA); + TIFFSetField(tif, TIFFTAG_FILLORDER, FILLORDER_MSB2LSB); + TIFFSetField(tif, TIFFTAG_MAKE, "libcamera"); + TIFFSetField(tif, TIFFTAG_MODEL, camera->name().c_str()); + TIFFSetField(tif, TIFFTAG_UNIQUECAMERAMODEL, camera->name().c_str()); + TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_SOFTWARE, "qcam"); + TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT); + TIFFSetField(tif, TIFFTAG_CFAREPEATPATTERNDIM, cfa_repeatpatterndim); + TIFFSetField(tif, TIFFTAG_CFAPATTERN, info->pattern); + TIFFSetField(tif, TIFFTAG_CFAPLANECOLOR, 3, "\00\01\02"); + TIFFSetField(tif, TIFFTAG_CFALAYOUT, 1); + + /* \todo Add more EXIF fields to output. */ + + /* Write RAW content */ + uint8_t *row = static_cast(data); + uint8_t scanline[config.size.width * info->bitsPerSample]; + for (unsigned int y = 0; y < config.size.height; y++) { + info->packScanline(&scanline, row, config.size.width); + + if (TIFFWriteScanline(tif, &scanline, y, 0) != 1) { + std::cerr << "Failed to write scanline" << std::endl; + TIFFClose(tif); + return -EINVAL; + } + + row += config.stride; + } + + TIFFWriteDirectory(tif); + + TIFFClose(tif); + + return 0; +} diff --git a/src/qcam/dng_writer.h b/src/qcam/dng_writer.h new file mode 100644 index 0000000000000000..f0e69adb38c6f115 --- /dev/null +++ b/src/qcam/dng_writer.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Raspberry Pi (Trading) Ltd. + * + * dng_writer.h - DNG writer + */ +#ifndef __LIBCAMERA_DNG_WRITER_H__ +#define __LIBCAMERA_DNG_WRITER_H__ + +#include +#include +#include + +using namespace libcamera; + +class DNGWriter +{ +public: + static int write(const char *filename, const Camera *camera, + const StreamConfiguration &config, FrameBuffer *buffer, + void *data); +}; + +#endif /* __LIBCAMERA_DNG_WRITER_H__ */ diff --git a/src/qcam/meson.build b/src/qcam/meson.build index 895264be4a3388f4..426462ed5aebe964 100644 --- a/src/qcam/meson.build +++ b/src/qcam/meson.build @@ -1,9 +1,9 @@ qcam_sources = files([ + '../cam/options.cpp', + '../cam/stream_options.cpp', 'format_converter.cpp', 'main.cpp', 'main_window.cpp', - '../cam/options.cpp', - '../cam/stream_options.cpp', 'viewfinder.cpp', ]) @@ -23,8 +23,23 @@ qt5_dep = dependency('qt5', required : false) if qt5_dep.found() + qcam_deps = [ + libcamera_dep, + qt5_dep, + ] + qt5_cpp_args = [ '-DQT_NO_KEYWORDS' ] + tiff_dep = dependency('libtiff-4', required : false) + if tiff_dep.found() + qt5_cpp_args += [ '-DHAVE_TIFF' ] + qcam_deps += [ tiff_dep ] + qcam_sources += files([ + 'dng_writer.cpp', + ]) + endif + + # gcc 9 introduced a deprecated-copy warning that is triggered by Qt until # Qt 5.13. clang 10 introduced the same warning, but detects more issues # that are not fixed in Qt yet. Disable the warning manually in both cases. @@ -40,6 +55,6 @@ if qt5_dep.found() qcam = executable('qcam', qcam_sources, resources, install : true, - dependencies : [libcamera_dep, qt5_dep], + dependencies : qcam_deps, cpp_args : qt5_cpp_args) endif From patchwork Sat May 2 02:10:45 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: 3660 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 361CF61459 for ; Sat, 2 May 2020 04:11:24 +0200 (CEST) X-Halon-ID: 3806cd68-8c1a-11ea-aeed-005056917f90 Authorized-sender: niklas@soderlund.pp.se Received: from bismarck.berto.se (p4fca2392.dip0.t-ipconnect.de [79.202.35.146]) by bin-vsp-out-02.atm.binero.net (Halon) with ESMTPA id 3806cd68-8c1a-11ea-aeed-005056917f90; Sat, 02 May 2020 04:11:22 +0200 (CEST) From: =?utf-8?q?Niklas_S=C3=B6derlund?= To: libcamera-devel@lists.libcamera.org Date: Sat, 2 May 2020 04:10:45 +0200 Message-Id: <20200502021045.785979-4-niklas.soderlund@ragnatech.se> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200502021045.785979-1-niklas.soderlund@ragnatech.se> References: <20200502021045.785979-1-niklas.soderlund@ragnatech.se> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 3/3] 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: Sat, 02 May 2020 02:11:25 -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 Reviewed-by: Laurent Pinchart --- * Changes since v2 - Use a file dialog - Make DNG optional depending on libtiff --- src/qcam/assets/feathericons/feathericons.qrc | 1 + src/qcam/main_window.cpp | 64 ++++++++++++++++++- src/qcam/main_window.h | 4 ++ 3 files changed, 68 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 0bd9f3583ea4f6d4..458da479a9b21b73 100644 --- a/src/qcam/main_window.cpp +++ b/src/qcam/main_window.cpp @@ -27,6 +27,10 @@ #include #include +#if HAVE_TIFF +#include "dng_writer.h" +#endif + using namespace libcamera; /** @@ -48,7 +52,8 @@ public: }; MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options) - : options_(options), cm_(cm), allocator_(nullptr), isCapturing_(false) + : saveRaw_(nullptr), options_(options), cm_(cm), allocator_(nullptr), + isCapturing_(false), captureRaw_(false) { int ret; @@ -144,6 +149,16 @@ int MainWindow::createToolbars() action->setShortcut(QKeySequence::SaveAs); connect(action, &QAction::triggered, this, &MainWindow::saveImageAs); +#if HAVE_TIFF + /* 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; } @@ -369,6 +384,10 @@ int MainWindow::startCapture() 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_) { @@ -474,6 +493,9 @@ void MainWindow::stopCapture() return; viewfinder_->stop(); + if (saveRaw_) + saveRaw_->setEnabled(false); + captureRaw_ = false; int ret = camera_->stop(); if (ret) @@ -524,6 +546,31 @@ void MainWindow::saveImageAs() writer.write(image); } +void MainWindow::captureRaw() +{ + captureRaw_ = true; +} + +void MainWindow::processRaw(FrameBuffer *buffer) +{ +#if HAVE_TIFF + QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); + QString filename = QFileDialog::getSaveFileName(this, "Save DNG", defaultPath, + "DNG Files (*.dng)"); + + if (filename != "") { + const MappedBuffer &mapped = mappedBuffers_[buffer]; + DNGWriter::write(filename.toStdString().c_str(), camera_.get(), + rawStream_->configuration(), buffer, + mapped.memory); + } +#endif + { + QMutexLocker locker(&mutex_); + freeBuffers_[rawStream_].enqueue(buffer); + } +} + /* ----------------------------------------------------------------------------- * Request Completion Handling */ @@ -566,6 +613,9 @@ void MainWindow::processCapture() /* Process buffers. */ if (buffers.count(vfStream_)) processViewfinder(buffers[vfStream_]); + + if (buffers.count(rawStream_)) + processRaw(buffers[rawStream_]); } void MainWindow::processViewfinder(FrameBuffer *buffer) @@ -598,5 +648,17 @@ void MainWindow::queueRequest(FrameBuffer *buffer) request->addBuffer(vfStream_, buffer); + if (captureRaw_) { + QMutexLocker locker(&mutex_); + + if (!freeBuffers_[rawStream_].isEmpty()) { + request->addBuffer(rawStream_, + freeBuffers_[rawStream_].dequeue()); + captureRaw_ = false; + } else { + qWarning() << "No free buffer available for RAW capture"; + } + } + camera_->queueRequest(request); } diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h index 4856ecc10729159c..295ecc537e9d45bf 100644 --- a/src/qcam/main_window.h +++ b/src/qcam/main_window.h @@ -55,6 +55,8 @@ private Q_SLOTS: void toggleCapture(bool start); void saveImageAs(); + void captureRaw(); + void processRaw(FrameBuffer *buffer); void queueRequest(FrameBuffer *buffer); @@ -75,6 +77,7 @@ private: QToolBar *toolbar_; QAction *startStopAction_; QComboBox *cameraCombo_; + QAction *saveRaw_; ViewFinder *viewfinder_; QIcon iconPlay_; @@ -96,6 +99,7 @@ private: /* Capture state, buffers queue and statistics */ bool isCapturing_; + bool captureRaw_; Stream *vfStream_; Stream *rawStream_; std::map> freeBuffers_;