From patchwork Tue Oct 27 21:24:45 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Umang Jain X-Patchwork-Id: 10270 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 5EF58C3B5C for ; Tue, 27 Oct 2020 21:25:01 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2EFC562230; Tue, 27 Oct 2020 22:25:01 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=uajain.com header.i=@uajain.com header.b="g4zx2o5Q"; dkim-atps=neutral Received: from mail.uajain.com (static.126.159.217.95.clients.your-server.de [95.217.159.126]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id EE7AF6205F for ; Tue, 27 Oct 2020 22:24:59 +0100 (CET) From: Umang Jain DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=uajain.com; s=mail; t=1603833899; bh=GbH+9TPuv/EdjiwNPJXY2dWcOMqZNJcKqFJJt+3Rd3U=; h=From:To:Cc:Subject:In-Reply-To:References; b=g4zx2o5QBq2xT9CWdhiqxIdjZ3eA6KqDOEAjC9Y2rOLugjqjkyXytqEjwvAcsjtkZ BJNpTk2qicmdt9a0EpY5RpcKGW8ukkMImQnYBuN4sR6hZ57fKhrOhdxpmptB55YUFl RXG5UHsyjAMZ54HuhctqTFaNnTern2iEYUtaARMMuJB7zggFSftKB/t66e7tttbkJD tF3eJ7AqRdatvr73sgBv9/VMH9zTlmSMbXfyNFcvWxd8gZGHpD0jHOfe0tA3Ve2c7z j+y2197pKb0EsQGCeC9ASccazvWozBcnJH/ogAZKRHjo5e0m2vBq+FtnB7Jq1iUXDt kxutukq4Vtg1w== To: libcamera-devel@lists.libcamera.org Date: Wed, 28 Oct 2020 02:54:45 +0530 Message-Id: <20201027212447.131431-2-email@uajain.com> In-Reply-To: <20201027212447.131431-1-email@uajain.com> References: <20201027212447.131431-1-email@uajain.com> Mime-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 1/3] android: jpeg: encoder_libjpeg: Allow encoding raw frame bytes 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: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Allow encoding frames which are directly handed over to the encoder via a span or vector i.e. a raw frame bytes. Introduce an overloaded EncoderLibJpeg::encode() with libcamera::Span source parameter to achieve this functionality. This makes the libjpeg-encoder a bit flexible for use case such as compressing a thumbnail generated for Exif. Signed-off-by: Umang Jain Reviewed-by: Laurent Pinchart Reviewed-by: Kieran Bingham --- src/android/jpeg/encoder_libjpeg.cpp | 18 ++++++++++++------ src/android/jpeg/encoder_libjpeg.h | 7 +++++-- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/android/jpeg/encoder_libjpeg.cpp b/src/android/jpeg/encoder_libjpeg.cpp index cfa5332..aed919b 100644 --- a/src/android/jpeg/encoder_libjpeg.cpp +++ b/src/android/jpeg/encoder_libjpeg.cpp @@ -104,9 +104,9 @@ int EncoderLibJpeg::configure(const StreamConfiguration &cfg) return 0; } -void EncoderLibJpeg::compressRGB(const MappedBuffer *frame) +void EncoderLibJpeg::compressRGB(Span frame) { - unsigned char *src = static_cast(frame->maps()[0].data()); + unsigned char *src = const_cast(frame.data()); /* \todo Stride information should come from buffer configuration. */ unsigned int stride = pixelFormatInfo_->stride(compress_.image_width, 0); @@ -122,7 +122,7 @@ void EncoderLibJpeg::compressRGB(const MappedBuffer *frame) * Compress the incoming buffer from a supported NV format. * This naively unpacks the semi-planar NV12 to a YUV888 format for libjpeg. */ -void EncoderLibJpeg::compressNV(const MappedBuffer *frame) +void EncoderLibJpeg::compressNV(Span frame) { uint8_t tmprowbuf[compress_.image_width * 3]; @@ -144,7 +144,7 @@ void EncoderLibJpeg::compressNV(const MappedBuffer *frame) unsigned int cb_pos = nvSwap_ ? 1 : 0; unsigned int cr_pos = nvSwap_ ? 0 : 1; - const unsigned char *src = static_cast(frame->maps()[0].data()); + const unsigned char *src = frame.data(); const unsigned char *src_c = src + y_stride * compress_.image_height; JSAMPROW row_pointer[1]; @@ -189,6 +189,12 @@ int EncoderLibJpeg::encode(const FrameBuffer &source, Span dest, return frame.error(); } + return encode(frame.maps()[0], dest, exifData); +} + +int EncoderLibJpeg::encode(Span src, Span dest, + Span exifData) +{ unsigned char *destination = dest.data(); unsigned long size = dest.size(); @@ -214,9 +220,9 @@ int EncoderLibJpeg::encode(const FrameBuffer &source, Span dest, << "x" << compress_.image_height; if (nv_) - compressNV(&frame); + compressNV(src); else - compressRGB(&frame); + compressRGB(src); jpeg_finish_compress(&compress_); diff --git a/src/android/jpeg/encoder_libjpeg.h b/src/android/jpeg/encoder_libjpeg.h index 40505dd..070f56f 100644 --- a/src/android/jpeg/encoder_libjpeg.h +++ b/src/android/jpeg/encoder_libjpeg.h @@ -24,10 +24,13 @@ public: int encode(const libcamera::FrameBuffer &source, libcamera::Span destination, libcamera::Span exifData) override; + int encode(libcamera::Span source, + libcamera::Span destination, + libcamera::Span exifData); private: - void compressRGB(const libcamera::MappedBuffer *frame); - void compressNV(const libcamera::MappedBuffer *frame); + void compressRGB(libcamera::Span frame); + void compressNV(libcamera::Span frame); struct jpeg_compress_struct compress_; struct jpeg_error_mgr jerr_; From patchwork Tue Oct 27 21:24:46 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Umang Jain X-Patchwork-Id: 10271 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 07AA6C3B5C for ; Tue, 27 Oct 2020 21:25:05 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C833362230; Tue, 27 Oct 2020 22:25:04 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=uajain.com header.i=@uajain.com header.b="fAk7YyWS"; dkim-atps=neutral Received: from mail.uajain.com (static.126.159.217.95.clients.your-server.de [95.217.159.126]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 011936216C for ; Tue, 27 Oct 2020 22:25:03 +0100 (CET) From: Umang Jain DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=uajain.com; s=mail; t=1603833902; bh=T/LjeiBLl++5Y/Z66rdwcekpyjy3ENs3epqqIei3wNY=; h=From:To:Cc:Subject:In-Reply-To:References; b=fAk7YyWSNmesOUoSjrV8gG1f+go0UABgt8knpbQfSoz/RVrQ3jAGktwFhj7S7PgAU Wdi2b0gChxsb3tlU8+WMWxUTs4+FOL5lENbYxxEqkDmKC7qU3yEHRrqulbPZuw7ATo /CFSoYuEptXIus78AXIcXNHuVozCS/EcV2kGFlnI57MwU9LXPkVi9Tg+IE0X4uV/Qy heBcFoxzbBlcGEi88CELy+QL8efuTohVltRqZscn2aAD7b+6u7VPS+kIaavC1tk6g9 fB+3ROUQDLKaTL0fCe7halDYr4sTZBIxbGZUB5e3/ymqHt+fjDZrcnHpQhRNmbCMR1 Drrq/xQgPBrjw== To: libcamera-devel@lists.libcamera.org Date: Wed, 28 Oct 2020 02:54:46 +0530 Message-Id: <20201027212447.131431-3-email@uajain.com> In-Reply-To: <20201027212447.131431-1-email@uajain.com> References: <20201027212447.131431-1-email@uajain.com> Mime-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 2/3] android: jpeg: Introduce a simple image thumbnailer 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: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a basic image Thumbnailer class for the frames being captured. Currently, the thumbnailer can scale NV12 frames. It shall be used to generate a thumbnail image for EXIF metadata, in the subsequent commit. Signed-off-by: Umang Jain Reviewed-by: Laurent Pinchart Reviewed-by: Kieran Bingham --- src/android/jpeg/thumbnailer.cpp | 113 +++++++++++++++++++++++++++++++ src/android/jpeg/thumbnailer.h | 36 ++++++++++ src/android/meson.build | 1 + 3 files changed, 150 insertions(+) create mode 100644 src/android/jpeg/thumbnailer.cpp create mode 100644 src/android/jpeg/thumbnailer.h diff --git a/src/android/jpeg/thumbnailer.cpp b/src/android/jpeg/thumbnailer.cpp new file mode 100644 index 0000000..2e3c478 --- /dev/null +++ b/src/android/jpeg/thumbnailer.cpp @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * thumbnailer.cpp - Simple image thumbnailer + */ + +#include "thumbnailer.h" + +#include + +#include "libcamera/internal/log.h" + +using namespace libcamera; + +LOG_DEFINE_CATEGORY(Thumbnailer) + +Thumbnailer::Thumbnailer() + : valid_(false) +{ +} + +void Thumbnailer::configure(const Size &sourceSize, PixelFormat pixelFormat) +{ + sourceSize_ = sourceSize; + pixelFormat_ = pixelFormat; + + if (pixelFormat_ != formats::NV12) { + LOG (Thumbnailer, Error) + << "Failed to configure: Pixel Format " + << pixelFormat_.toString() << " unsupported."; + return; + } + + targetSize_ = computeThumbnailSize(); + + valid_ = true; +} + +/* + * The Exif specification recommends the width of the thumbnail to be a + * multiple of 16 (section 4.8.1). Hence, compute the corresponding height + * keeping the aspect ratio same as of the source. + */ +Size Thumbnailer::computeThumbnailSize() const +{ + unsigned int targetHeight; + constexpr unsigned int kTargetWidth = 160; + + targetHeight = kTargetWidth * sourceSize_.height / sourceSize_.width; + + if (targetHeight & 1) + targetHeight++; + + return Size(kTargetWidth, targetHeight); +} + +void +Thumbnailer::createThumbnail(const FrameBuffer &source, + std::vector *destination) +{ + MappedFrameBuffer frame(&source, PROT_READ); + if (!frame.isValid()) { + LOG(Thumbnailer, Error) + << "Failed to map FrameBuffer : " + << strerror(frame.error()); + return; + } + + if (!valid_) { + LOG(Thumbnailer, Error) << "Config is unconfigured or invalid."; + return; + } + + const unsigned int sw = sourceSize_.width; + const unsigned int sh = sourceSize_.height; + const unsigned int tw = targetSize_.width; + const unsigned int th = targetSize_.height; + + ASSERT(tw % 2 == 0 && th % 2 == 0); + + /* Image scaling block implementing nearest-neighbour algorithm. */ + unsigned char *src = static_cast(frame.maps()[0].data()); + unsigned char *srcC = src + sh * sw; + unsigned char *srcCb, *srcCr; + unsigned char *dstY, *srcY; + + size_t dstSize = (th * tw) + ((th / 2) * tw); + destination->resize(dstSize); + unsigned char *dst = destination->data(); + unsigned char *dstC = dst + th * tw; + + for (unsigned int y = 0; y < th; y += 2) { + unsigned int sourceY = (sh * y + th / 2) / th; + + dstY = dst + y * tw; + srcY = src + sw * sourceY; + srcCb = srcC + (sourceY / 2) * sw + 0; + srcCr = srcC + (sourceY / 2) * sw + 1; + + for (unsigned int x = 0; x < tw; x += 2) { + unsigned int sourceX = (sw * x + tw / 2) / tw; + + dstY[x] = srcY[sourceX]; + dstY[tw + x] = srcY[sw + sourceX]; + dstY[x + 1] = srcY[sourceX + 1]; + dstY[tw + x + 1] = srcY[sw + sourceX + 1]; + + dstC[(y / 2) * tw + x + 0] = srcCb[(sourceX / 2) * 2]; + dstC[(y / 2) * tw + x + 1] = srcCr[(sourceX / 2) * 2]; + } + } +} diff --git a/src/android/jpeg/thumbnailer.h b/src/android/jpeg/thumbnailer.h new file mode 100644 index 0000000..16e6951 --- /dev/null +++ b/src/android/jpeg/thumbnailer.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * thumbnailer.h - Simple image thumbnailer + */ +#ifndef __ANDROID_JPEG_THUMBNAILER_H__ +#define __ANDROID_JPEG_THUMBNAILER_H__ + +#include + +#include "libcamera/internal/buffer.h" +#include "libcamera/internal/formats.h" + +class Thumbnailer +{ +public: + Thumbnailer(); + + void configure(const libcamera::Size &sourceSize, + libcamera::PixelFormat pixelFormat); + void createThumbnail(const libcamera::FrameBuffer &source, + std::vector *dest); + const libcamera::Size& size() const { return targetSize_; } + +private: + libcamera::Size computeThumbnailSize() const; + + libcamera::PixelFormat pixelFormat_; + libcamera::Size sourceSize_; + libcamera::Size targetSize_; + + bool valid_; +}; + +#endif /* __ANDROID_JPEG_THUMBNAILER_H__ */ diff --git a/src/android/meson.build b/src/android/meson.build index f72376a..3d4d3be 100644 --- a/src/android/meson.build +++ b/src/android/meson.build @@ -25,6 +25,7 @@ android_hal_sources = files([ 'jpeg/encoder_libjpeg.cpp', 'jpeg/exif.cpp', 'jpeg/post_processor_jpeg.cpp', + 'jpeg/thumbnailer.cpp', ]) android_camera_metadata_sources = files([ From patchwork Tue Oct 27 21:24:47 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Umang Jain X-Patchwork-Id: 10272 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 7F615C3B5C for ; Tue, 27 Oct 2020 21:25:07 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4C28A62217; Tue, 27 Oct 2020 22:25:07 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=uajain.com header.i=@uajain.com header.b="H9Kgq3pn"; dkim-atps=neutral Received: from mail.uajain.com (static.126.159.217.95.clients.your-server.de [95.217.159.126]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7BA4F6216C for ; Tue, 27 Oct 2020 22:25:05 +0100 (CET) From: Umang Jain DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=uajain.com; s=mail; t=1603833905; bh=zZN38HVOZfCYW8HQqMLkBMj1+5ocwg133e5pxkpY7RU=; h=From:To:Cc:Subject:In-Reply-To:References; b=H9Kgq3pnC28ioRBVLBRs6vsy5BuTnER2dYtroYMQ5nCjIheKRHqfZaeX88bmn/fQb QJPzrq5ftM17T/s6cXwsWZ69uXe81KJlO86qPE1fewbCqNWEeJonBGQSdp9OJGzwEK tl8K7zL9HPB0R+mmJcgE+M8RqOZmedsZgsjrDyeUzvShTwxPmnJJFdRMgqsXSEFpad LHMFUBJ/v4/8RYM2l3XBsnOs01srfd2WJVkE/eCBRXFNsTMydRHjSHI/+1jMCzgCOw CDFG0BXjnFjMiiupXv+Tmgok6TuZ1koPkQyQNChft81SFuefbMIOgL7WPzTFM2Kvfv 9FPkJPdt9c2OA== To: libcamera-devel@lists.libcamera.org Date: Wed, 28 Oct 2020 02:54:47 +0530 Message-Id: <20201027212447.131431-4-email@uajain.com> In-Reply-To: <20201027212447.131431-1-email@uajain.com> References: <20201027212447.131431-1-email@uajain.com> Mime-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 3/3] android: jpeg: post_processor_jpeg: Embed thumbnail into Exif metadata 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: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Embed a Jpeg-encoded thumbnail into Exif metadata using the Thumbnailer class that got introduced. Introduce a helper function in Exif class for setting the thumbnail data. Set the EXIF_TAG_COMPRESSION to '6' to denote that the thumbnail is jpeg-compressed, as mentioned in Exif v2.31. Signed-off-by: Umang Jain Reviewed-by: Laurent Pinchart --- src/android/jpeg/exif.cpp | 24 ++++++++++++++++- src/android/jpeg/exif.h | 2 ++ src/android/jpeg/post_processor_jpeg.cpp | 34 ++++++++++++++++++++++++ src/android/jpeg/post_processor_jpeg.h | 8 +++++- 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/android/jpeg/exif.cpp b/src/android/jpeg/exif.cpp index d21534a..6ac52c6 100644 --- a/src/android/jpeg/exif.cpp +++ b/src/android/jpeg/exif.cpp @@ -75,8 +75,16 @@ Exif::~Exif() if (exifData_) free(exifData_); - if (data_) + if (data_) { + /* + * Reset thumbnail data to avoid getting double-freed by + * libexif. It is owned by the caller (i.e. PostProcessorJpeg). + */ + data_->data = nullptr; + data_->size = 0; + exif_data_unref(data_); + } if (mem_) exif_mem_unref(mem_); @@ -268,6 +276,20 @@ void Exif::setOrientation(int orientation) setShort(EXIF_IFD_0, EXIF_TAG_ORIENTATION, value); } +/* + * The thumbnail data should remain valid until the Exif object is destroyed. + * Failing to do so, might result in no thumbnail data being set even after a + * call to Exif::setThumbnail(). + */ +void Exif::setThumbnail(Span thumbnail, + uint16_t compression) +{ + data_->data = const_cast(thumbnail.data()); + data_->size = thumbnail.size(); + + setShort(EXIF_IFD_0, EXIF_TAG_COMPRESSION, compression); +} + [[nodiscard]] int Exif::generate() { if (exifData_) { diff --git a/src/android/jpeg/exif.h b/src/android/jpeg/exif.h index 12c27b6..6987b31 100644 --- a/src/android/jpeg/exif.h +++ b/src/android/jpeg/exif.h @@ -26,6 +26,8 @@ public: void setOrientation(int orientation); void setSize(const libcamera::Size &size); + void setThumbnail(libcamera::Span thumbnail, + uint16_t compression); void setTimestamp(time_t timestamp); libcamera::Span data() const { return { exifData_, size_ }; } diff --git a/src/android/jpeg/post_processor_jpeg.cpp b/src/android/jpeg/post_processor_jpeg.cpp index c56f1b2..a0db793 100644 --- a/src/android/jpeg/post_processor_jpeg.cpp +++ b/src/android/jpeg/post_processor_jpeg.cpp @@ -39,11 +39,41 @@ int PostProcessorJpeg::configure(const StreamConfiguration &inCfg, } streamSize_ = outCfg.size; + + thumbnailer_.configure(inCfg.size, inCfg.pixelFormat); + StreamConfiguration thCfg = inCfg; + thCfg.size = thumbnailer_.size(); + if (thumbnailEncoder_.configure(thCfg) != 0) { + LOG(JPEG, Error) << "Failed to configure thumbnail encoder"; + return -EINVAL; + } + encoder_ = std::make_unique(); return encoder_->configure(inCfg); } +void PostProcessorJpeg::generateThumbnail(const FrameBuffer &source, + std::vector *thumbnail) +{ + /* Stores the raw scaled-down thumbnail bytes. */ + std::vector rawThumbnail; + + thumbnailer_.createThumbnail(source, &rawThumbnail); + + if (!rawThumbnail.empty()) { + thumbnail->resize(rawThumbnail.size()); + + int jpeg_size = thumbnailEncoder_.encode(rawThumbnail, + *thumbnail, { }); + thumbnail->resize(jpeg_size); + + LOG(JPEG, Debug) + << "Thumbnail compress returned " + << jpeg_size << " bytes"; + } +} + int PostProcessorJpeg::process(const FrameBuffer &source, Span destination, CameraMetadata *metadata) @@ -64,6 +94,10 @@ int PostProcessorJpeg::process(const FrameBuffer &source, * second, it is good enough. */ exif.setTimestamp(std::time(nullptr)); + std::vector thumbnail; + generateThumbnail(source, &thumbnail); + if(!thumbnail.empty()) + exif.setThumbnail(thumbnail, 6); if (exif.generate() != 0) LOG(JPEG, Error) << "Failed to generate valid EXIF data"; diff --git a/src/android/jpeg/post_processor_jpeg.h b/src/android/jpeg/post_processor_jpeg.h index 3706cec..5afa831 100644 --- a/src/android/jpeg/post_processor_jpeg.h +++ b/src/android/jpeg/post_processor_jpeg.h @@ -8,12 +8,13 @@ #define __ANDROID_POST_PROCESSOR_JPEG_H__ #include "../post_processor.h" +#include "encoder_libjpeg.h" +#include "thumbnailer.h" #include #include "libcamera/internal/buffer.h" -class Encoder; class CameraDevice; class PostProcessorJpeg : public PostProcessor @@ -28,9 +29,14 @@ public: CameraMetadata *metadata) override; private: + void generateThumbnail(const libcamera::FrameBuffer &source, + std::vector *thumbnail); + CameraDevice *const cameraDevice_; std::unique_ptr encoder_; libcamera::Size streamSize_; + EncoderLibJpeg thumbnailEncoder_; + Thumbnailer thumbnailer_; }; #endif /* __ANDROID_POST_PROCESSOR_JPEG_H__ */