[{"id":11827,"web_url":"https://patchwork.libcamera.org/comment/11827/","msgid":"<20200804123421.fujaixgpojtgdk5t@uno.localdomain>","date":"2020-08-04T12:34:21","subject":"Re: [libcamera-devel] [PATCH v2 11/12] android: Introduce JPEG\n\tcompression","submitter":{"id":3,"url":"https://patchwork.libcamera.org/api/people/3/","name":"Jacopo Mondi","email":"jacopo@jmondi.org"},"content":"Hi Kieran,\n   a few minor comments below\n\nOn Mon, Aug 03, 2020 at 05:18:15PM +0100, Kieran Bingham wrote:\n> Provide a compressor interface and implement a JPEG compressor using libjpeg.\n>\n> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n>\n> ---\n> v2:\n>\n>  - Convert to use the libcamera format information rather than duplicating it ourselves.\n>    - Not easy to get the horizontal subsampling\n>    - not easy to determine if we have an nvSwap...\n>\n>\n> Possible Todos:\n>\n>   - Convert to RAW api if possible.\n>   - Add custom JPEG error handler\n\nDo we want to record these, even if Doxygen does not parse this\ncomponent ?\n\n>\n>  src/android/jpeg/compressor.h        |  28 ++++\n>  src/android/jpeg/compressor_jpeg.cpp | 215 +++++++++++++++++++++++++++\n>  src/android/jpeg/compressor_jpeg.h   |  46 ++++++\n>  src/android/meson.build              |   1 +\n>  src/libcamera/meson.build            |   2 +\n>  5 files changed, 292 insertions(+)\n>  create mode 100644 src/android/jpeg/compressor.h\n>  create mode 100644 src/android/jpeg/compressor_jpeg.cpp\n>  create mode 100644 src/android/jpeg/compressor_jpeg.h\n>\n> diff --git a/src/android/jpeg/compressor.h b/src/android/jpeg/compressor.h\n> new file mode 100644\n> index 000000000000..18d8f65eba02\n> --- /dev/null\n> +++ b/src/android/jpeg/compressor.h\n> @@ -0,0 +1,28 @@\n> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> +/*\n> + * Copyright (C) 2020, Google Inc.\n> + *\n> + * compressor.h - Image compression interface\n> + */\n> +#ifndef __ANDROID_JPEG_COMPRESSOR_H__\n> +#define __ANDROID_JPEG_COMPRESSOR_H__\n> +\n> +#include <libcamera/buffer.h>\n> +#include <libcamera/stream.h>\n> +\n> +struct CompressedImage {\n> +\tunsigned char *data;\n> +\tunsigned long length;\n> +};\n> +\n> +class Compressor\n> +{\n> +public:\n> +\tvirtual ~Compressor(){};\n> +\n> +\tvirtual int configure(const libcamera::StreamConfiguration &cfg) = 0;\n> +\tvirtual int compress(const libcamera::FrameBuffer *source, CompressedImage *image) = 0;\n> +\tvirtual void free(CompressedImage *image) = 0;\n> +};\n> +\n> +#endif /* __ANDROID_JPEG_COMPRESSOR_H__ */\n> diff --git a/src/android/jpeg/compressor_jpeg.cpp b/src/android/jpeg/compressor_jpeg.cpp\n> new file mode 100644\n> index 000000000000..83149417878d\n> --- /dev/null\n> +++ b/src/android/jpeg/compressor_jpeg.cpp\n> @@ -0,0 +1,215 @@\n> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> +/*\n> + * Copyright (C) 2020, Google Inc.\n> + *\n> + * compressor_jpeg.cpp - JPEG compression using libjpeg native API\n> + */\n> +\n> +#include \"compressor_jpeg.h\"\n> +\n> +#include <fcntl.h>\n> +#include <iomanip>\n> +#include <iostream>\n> +#include <sstream>\n> +#include <string.h>\n> +#include <sys/mman.h>\n> +#include <unistd.h>\n> +#include <vector>\n> +\n> +#include <libcamera/camera.h>\n> +#include <libcamera/formats.h>\n> +#include <libcamera/pixel_format.h>\n> +\n> +#include \"libcamera/internal/formats.h\"\n> +#include \"libcamera/internal/log.h\"\n> +\n> +using namespace libcamera;\n> +\n> +LOG_DEFINE_CATEGORY(JPEG)\n> +\n> +namespace {\n> +\n> +struct JPEGPixelFormatInfo {\n> +\tJ_COLOR_SPACE colorSpace;\n> +\tconst PixelFormatInfo &pixelFormatInfo;\n> +\tbool nvSwap;\n> +};\n> +\n> +std::map<PixelFormat, JPEGPixelFormatInfo> pixelInfo{\n\nconst ?\n\n> +\t{ formats::R8, { JCS_GRAYSCALE, PixelFormatInfo::info(formats::R8), false } },\n> +\n> +\t{ formats::RGB888, { JCS_EXT_BGR, PixelFormatInfo::info(formats::RGB888), false } },\n> +\t{ formats::BGR888, { JCS_EXT_RGB, PixelFormatInfo::info(formats::BGR888), false } },\n> +\n> +\t{ formats::NV12, { JCS_YCbCr, PixelFormatInfo::info(formats::NV12), false } },\n> +\t{ formats::NV21, { JCS_YCbCr, PixelFormatInfo::info(formats::NV21), true } },\n> +\t{ formats::NV16, { JCS_YCbCr, PixelFormatInfo::info(formats::NV16), false } },\n> +\t{ formats::NV61, { JCS_YCbCr, PixelFormatInfo::info(formats::NV61), true } },\n> +\t{ formats::NV24, { JCS_YCbCr, PixelFormatInfo::info(formats::NV24), false } },\n> +\t{ formats::NV42, { JCS_YCbCr, PixelFormatInfo::info(formats::NV42), true } },\n> +};\n> +\n> +const struct JPEGPixelFormatInfo &findPixelInfo(const PixelFormat &format)\n> +{\n> +\tstatic const struct JPEGPixelFormatInfo invalidPixelFormat {\n> +\t\tJCS_UNKNOWN, PixelFormatInfo(), false\n> +\t};\n> +\n> +\tconst auto iter = pixelInfo.find(format);\n> +\tif (iter == pixelInfo.end()) {\n> +\t\tLOG(JPEG, Error) << \"Unsupported pixel format for JPEG compressor: \"\n> +\t\t\t\t << format.toString();\n> +\t\treturn invalidPixelFormat;\n> +\t}\n> +\n> +\treturn iter->second;\n> +}\n> +\n> +} /* namespace */\n> +\n> +CompressorJPEG::CompressorJPEG()\n> +\t: quality_(95)\n> +{\n> +\t/* \\todo: Expand error handling coverage. */\n> +\tcompress_.err = jpeg_std_error(&jerr_);\n> +\n> +\tjpeg_create_compress(&compress_);\n> +}\n> +\n> +CompressorJPEG::~CompressorJPEG()\n> +{\n> +\tjpeg_destroy_compress(&compress_);\n> +}\n> +\n> +int CompressorJPEG::configure(const StreamConfiguration &cfg)\n> +{\n> +\tconst struct JPEGPixelFormatInfo info = findPixelInfo(cfg.pixelFormat);\n> +\tif (info.colorSpace == JCS_UNKNOWN)\n> +\t\treturn -ENOTSUP;\n> +\n> +\tcompress_.image_width = cfg.size.width;\n> +\tcompress_.image_height = cfg.size.height;\n> +\tcompress_.in_color_space = info.colorSpace;\n> +\n> +\tcompress_.input_components = info.colorSpace == JCS_GRAYSCALE ? 1 : 3;\n> +\n> +\tjpeg_set_defaults(&compress_);\n> +\tjpeg_set_quality(&compress_, quality_, TRUE);\n> +\n> +\tpixelFormatInfo = &info.pixelFormatInfo;\n> +\n> +\tnv_ = pixelFormatInfo->numPlanes() == 2;\n> +\tnvSwap_ = info.nvSwap;\n> +\n> +\treturn 0;\n> +}\n> +\n> +void CompressorJPEG::compressRGB(const libcamera::MappedBuffer *frame)\n> +{\n> +\tunsigned char *src = static_cast<unsigned char *>(frame->maps()[0].data());\n> +\tunsigned int stride = pixelFormatInfo->stride(compress_.image_width, 0);\n> +\n> +\tJSAMPROW row_pointer[1];\n> +\n> +\twhile (compress_.next_scanline < compress_.image_height) {\n> +\t\trow_pointer[0] = &src[compress_.next_scanline * stride];\n> +\t\tjpeg_write_scanlines(&compress_, row_pointer, 1);\n> +\t}\n> +}\n> +\n> +/*\n> + * Compress the incoming buffer from a supported NV format.\n> + * This naively unpacks the semi-planar NV12 to a YUV888 format for libjpeg.\n> + * Utilisation of the RAW api will be implemented on top as a performance\n> + * improvement.\n> + */\n> +void CompressorJPEG::compressNV(const libcamera::MappedBuffer *frame)\n> +{\n> +\tstd::vector<uint8_t> tmprowbuf(compress_.image_width * 3);\n> +\n> +\t/*\n> +\t * \\todo Use the raw api, and only unpack the cb/cr samples to new line buffers.\n> +\t * If possible, see if we can set appropriate pixel strides too to save even that copy.\n\nComments on 80 cols ?\n\nAh, this is recorded then! good\n\n> +\t *\n> +\t * Possible hints at:\n> +\t * https://sourceforge.net/p/libjpeg/mailman/message/30815123/\n> +\t */\n> +\tunsigned int y_stride = pixelFormatInfo->stride(compress_.image_width, 0);\n> +\tunsigned int c_stride = pixelFormatInfo->stride(compress_.image_width, 1);\n> +\n> +\tunsigned int horzSubSample = 2 * compress_.image_width / c_stride;\n> +\tunsigned int vertSubSample = pixelFormatInfo->planes[1].verticalSubSampling;\n> +\n> +\tunsigned int c_inc = horzSubSample == 1 ? 2 : 0;\n> +\tunsigned int cb_pos = nvSwap_ ? 1 : 0;\n> +\tunsigned int cr_pos = nvSwap_ ? 0 : 1;\n> +\n> +\tconst unsigned char *src = static_cast<unsigned char *>(frame->maps()[0].data());\n> +\tconst unsigned char *src_c = src + y_stride * compress_.image_height;\n> +\n> +\tJSAMPROW row_pointer[1];\n> +\trow_pointer[0] = &tmprowbuf[0];\n> +\n> +\tfor (unsigned int y = 0; y < compress_.image_height; y++) {\n> +\t\tunsigned char *dst = &tmprowbuf[0];\n> +\n> +\t\tconst unsigned char *src_y = src + y * compress_.image_width;\n> +\t\tconst unsigned char *src_cb = src_c + (y / vertSubSample) * c_stride + cb_pos;\n> +\t\tconst unsigned char *src_cr = src_c + (y / vertSubSample) * c_stride + cr_pos;\n> +\n> +\t\tfor (unsigned int x = 0; x < compress_.image_width; x += 2) {\n> +\t\t\tdst[0] = *src_y;\n> +\t\t\tdst[1] = *src_cb;\n> +\t\t\tdst[2] = *src_cr;\n> +\t\t\tsrc_y++;\n> +\t\t\tsrc_cb += c_inc;\n> +\t\t\tsrc_cr += c_inc;\n> +\t\t\tdst += 3;\n> +\n> +\t\t\tdst[0] = *src_y;\n> +\t\t\tdst[1] = *src_cb;\n> +\t\t\tdst[2] = *src_cr;\n> +\t\t\tsrc_y++;\n> +\t\t\tsrc_cb += 2;\n> +\t\t\tsrc_cr += 2;\n> +\t\t\tdst += 3;\n> +\t\t}\n\nI haven't really reviewed that part, but if it works, it's all good,\nright ? :)\n\n> +\n> +\t\tjpeg_write_scanlines(&compress_, row_pointer, 1);\n> +\t}\n> +}\n> +\n> +int CompressorJPEG::compress(const FrameBuffer *source, CompressedImage *jpeg)\n> +{\n> +\tMappedFrameBuffer frame(source, PROT_READ);\n> +\tif (!frame.isValid()) {\n> +\t\tLOG(JPEG, Error) << \"Failed to map FrameBuffer : \"\n                                                              ^ need\n                                                              this space?\n> +\t\t\t\t << strerror(frame.error());\n> +\t\treturn -frame.error();\n\nI assume the leading '-' is intentional\n\n> +\t}\n> +\n> +\tjpeg_mem_dest(&compress_, &jpeg->data, &jpeg->length);\n> +\n> +\tjpeg_start_compress(&compress_, TRUE);\n> +\n> +\tLOG(JPEG, Debug) << \"JPEG Compress Starting:\"\n> +\t\t\t << \" Width: \" << compress_.image_width\n> +\t\t\t << \" height: \" << compress_.image_height;\n> +\n> +\tif (nv_)\n> +\t\tcompressNV(&frame);\n> +\telse\n> +\t\tcompressRGB(&frame);\n\nIf one wants to be pedantic, the distinction here seems to be if we\nhave a single place or more. It's not only NV__ which is multi-planar\n(or better, semi planar, as NV12 is, in example, the semi-planar\nversion of the three-plane YUV420 format) or RAW which is single\nplanar (ie. packed YUV)\n\nBut I see you only support NV variations and some RAW formats, if this\nis not going to be updated I think then the naming is fine.\n\n> +\n> +\tLOG(JPEG, Debug) << \"JPEG Compress Completed\";\n> +\n> +\tjpeg_finish_compress(&compress_);\n> +\n> +\treturn 0;\n> +}\n> +\n> +void CompressorJPEG::free(CompressedImage *jpeg)\n> +{\n> +\t::free(jpeg->data);\n> +\tjpeg->data = nullptr;\n\nWould setting length to zero hurt ?\n\n> +}\n> diff --git a/src/android/jpeg/compressor_jpeg.h b/src/android/jpeg/compressor_jpeg.h\n> new file mode 100644\n> index 000000000000..3c0c3ff68449\n> --- /dev/null\n> +++ b/src/android/jpeg/compressor_jpeg.h\n> @@ -0,0 +1,46 @@\n> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> +/*\n> + * Copyright (C) 2020, Google Inc.\n> + *\n> + * compressor_jpeg.h - JPEG compression using libjpeg\n> + */\n> +#ifndef __ANDROID_JPEG_COMPRESSOR_JPEG_H__\n> +#define __ANDROID_JPEG_COMPRESSOR_JPEG_H__\n> +\n> +#include \"compressor.h\"\n> +\n> +#include <libcamera/buffer.h>\n> +#include <libcamera/stream.h>\n> +\n> +#include \"libcamera/internal/buffer.h\"\n> +#include \"libcamera/internal/formats.h\"\n> +\n> +#include <jpeglib.h>\n> +\n> +class CompressorJPEG : public Compressor\n> +{\n> +public:\n> +\tCompressorJPEG();\n> +\t~CompressorJPEG();\n> +\n> +\tint configure(const libcamera::StreamConfiguration &cfg);\n> +\tint compress(const libcamera::FrameBuffer *source, CompressedImage *jpeg);\n> +\tvoid free(CompressedImage *jpeg);\n\nThese should be marked with 'override'\n\n> +\n> +private:\n> +\tvoid compressRGB(const libcamera::MappedBuffer *frame);\n> +\tvoid compressYUV(const libcamera::MappedBuffer *frame);\n\nThis seems not to be implemented\n\n> +\tvoid compressNV(const libcamera::MappedBuffer *frame);\n> +\n> +\tstruct jpeg_compress_struct compress_;\n> +\tstruct jpeg_error_mgr jerr_;\n> +\n> +\tunsigned int quality_;\n> +\n> +\tconst libcamera::PixelFormatInfo *pixelFormatInfo;\n\nas a class member this should end with _\n\nMostly nit, nothing blocking this patch for real\n\nReviewed-by: Jacopo Mondi <jacopo@jmondi.org>\n\nThanks\n  j\n\n> +\n> +\tbool nv_;\n> +\tbool nvSwap_;\n> +};\n> +\n> +#endif /* __ANDROID_JPEG_COMPRESSOR_JPEG_H__ */\n> diff --git a/src/android/meson.build b/src/android/meson.build\n> index 822cad621f01..51dcd99ee62f 100644\n> --- a/src/android/meson.build\n> +++ b/src/android/meson.build\n> @@ -6,6 +6,7 @@ android_hal_sources = files([\n>      'camera_device.cpp',\n>      'camera_metadata.cpp',\n>      'camera_ops.cpp',\n> +    'jpeg/compressor_jpeg.cpp',\n>  ])\n>\n>  android_camera_metadata_sources = files([\n> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\n> index 3aad4386ffc2..d78e2c1f6eb8 100644\n> --- a/src/libcamera/meson.build\n> +++ b/src/libcamera/meson.build\n> @@ -124,6 +124,8 @@ if get_option('android')\n>      libcamera_sources += android_hal_sources\n>      includes += android_includes\n>      libcamera_link_with += android_camera_metadata\n> +\n> +    libcamera_deps += dependency('libjpeg')\n>  endif\n>\n>  # We add '/' to the build_rpath as a 'safe' path to act as a boolean flag.\n> --\n> 2.25.1\n>\n> _______________________________________________\n> libcamera-devel mailing list\n> libcamera-devel@lists.libcamera.org\n> https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id A1939BD86F\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue,  4 Aug 2020 12:30:43 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 4075C60546;\n\tTue,  4 Aug 2020 14:30:43 +0200 (CEST)","from relay11.mail.gandi.net (relay11.mail.gandi.net\n\t[217.70.178.231])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id B50276048E\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue,  4 Aug 2020 14:30:42 +0200 (CEST)","from uno.localdomain (93-34-118-233.ip49.fastwebnet.it\n\t[93.34.118.233]) (Authenticated sender: jacopo@jmondi.org)\n\tby relay11.mail.gandi.net (Postfix) with ESMTPSA id A7F5B10000B;\n\tTue,  4 Aug 2020 12:30:41 +0000 (UTC)"],"Date":"Tue, 4 Aug 2020 14:34:21 +0200","From":"Jacopo Mondi <jacopo@jmondi.org>","To":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Message-ID":"<20200804123421.fujaixgpojtgdk5t@uno.localdomain>","References":"<20200803161816.107113-1-kieran.bingham@ideasonboard.com>\n\t<20200803161816.107113-12-kieran.bingham@ideasonboard.com>","MIME-Version":"1.0","Content-Disposition":"inline","In-Reply-To":"<20200803161816.107113-12-kieran.bingham@ideasonboard.com>","Subject":"Re: [libcamera-devel] [PATCH v2 11/12] android: Introduce JPEG\n\tcompression","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Cc":"libcamera devel <libcamera-devel@lists.libcamera.org>","Content-Type":"text/plain; charset=\"us-ascii\"","Content-Transfer-Encoding":"7bit","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":11830,"web_url":"https://patchwork.libcamera.org/comment/11830/","msgid":"<8fcf81f1-74d6-83e4-e2d9-41f1865a8aeb@ideasonboard.com>","date":"2020-08-04T13:09:50","subject":"Re: [libcamera-devel] [PATCH v2 11/12] android: Introduce JPEG\n\tcompression","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Hi Jacopo,\n\nOn 04/08/2020 13:34, Jacopo Mondi wrote:\n> Hi Kieran,\n>    a few minor comments below\n> \n> On Mon, Aug 03, 2020 at 05:18:15PM +0100, Kieran Bingham wrote:\n>> Provide a compressor interface and implement a JPEG compressor using libjpeg.\n>>\n>> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n>>\n>> ---\n>> v2:\n>>\n>>  - Convert to use the libcamera format information rather than duplicating it ourselves.\n>>    - Not easy to get the horizontal subsampling\n>>    - not easy to determine if we have an nvSwap...\n>>\n>>\n>> Possible Todos:\n>>\n>>   - Convert to RAW api if possible.\n>>   - Add custom JPEG error handler\n> \n> Do we want to record these, even if Doxygen does not parse this\n> component ?\n> \n\nI'll add the custom jpeg error handler todo. I have a patch/WIP, but\nhaven't been able to get it quite right yet, hence it's not in this series.\n\n\nActually, both of these todo's are already in the code in relevant places.\n\n>>\n>>  src/android/jpeg/compressor.h        |  28 ++++\n>>  src/android/jpeg/compressor_jpeg.cpp | 215 +++++++++++++++++++++++++++\n>>  src/android/jpeg/compressor_jpeg.h   |  46 ++++++\n>>  src/android/meson.build              |   1 +\n>>  src/libcamera/meson.build            |   2 +\n>>  5 files changed, 292 insertions(+)\n>>  create mode 100644 src/android/jpeg/compressor.h\n>>  create mode 100644 src/android/jpeg/compressor_jpeg.cpp\n>>  create mode 100644 src/android/jpeg/compressor_jpeg.h\n>>\n>> diff --git a/src/android/jpeg/compressor.h b/src/android/jpeg/compressor.h\n>> new file mode 100644\n>> index 000000000000..18d8f65eba02\n>> --- /dev/null\n>> +++ b/src/android/jpeg/compressor.h\n>> @@ -0,0 +1,28 @@\n>> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n>> +/*\n>> + * Copyright (C) 2020, Google Inc.\n>> + *\n>> + * compressor.h - Image compression interface\n>> + */\n>> +#ifndef __ANDROID_JPEG_COMPRESSOR_H__\n>> +#define __ANDROID_JPEG_COMPRESSOR_H__\n>> +\n>> +#include <libcamera/buffer.h>\n>> +#include <libcamera/stream.h>\n>> +\n>> +struct CompressedImage {\n>> +\tunsigned char *data;\n>> +\tunsigned long length;\n\nI've contemplated making this a Span<uint8_t> like with the MappedBuffer\nchanges too ... but there are some nuances that this buffer is used to\nboth provide the available buffer size, and return the consumed bytes.\nSo maybe that should change, and have an explicit bytesUsed field added.\n\nOr compress() could return the Span of actually generated data... but\nthen I'd probably have to add an error() function too :S\n\n\n>> +};\n>> +\n>> +class Compressor\n>> +{\n>> +public:\n>> +\tvirtual ~Compressor(){};\n>> +\n>> +\tvirtual int configure(const libcamera::StreamConfiguration &cfg) = 0;\n>> +\tvirtual int compress(const libcamera::FrameBuffer *source, CompressedImage *image) = 0;\n>> +\tvirtual void free(CompressedImage *image) = 0;\n>> +};\n>> +\n>> +#endif /* __ANDROID_JPEG_COMPRESSOR_H__ */\n>> diff --git a/src/android/jpeg/compressor_jpeg.cpp b/src/android/jpeg/compressor_jpeg.cpp\n>> new file mode 100644\n>> index 000000000000..83149417878d\n>> --- /dev/null\n>> +++ b/src/android/jpeg/compressor_jpeg.cpp\n>> @@ -0,0 +1,215 @@\n>> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n>> +/*\n>> + * Copyright (C) 2020, Google Inc.\n>> + *\n>> + * compressor_jpeg.cpp - JPEG compression using libjpeg native API\n>> + */\n>> +\n>> +#include \"compressor_jpeg.h\"\n>> +\n>> +#include <fcntl.h>\n>> +#include <iomanip>\n>> +#include <iostream>\n>> +#include <sstream>\n>> +#include <string.h>\n>> +#include <sys/mman.h>\n>> +#include <unistd.h>\n>> +#include <vector>\n>> +\n>> +#include <libcamera/camera.h>\n>> +#include <libcamera/formats.h>\n>> +#include <libcamera/pixel_format.h>\n>> +\n>> +#include \"libcamera/internal/formats.h\"\n>> +#include \"libcamera/internal/log.h\"\n>> +\n>> +using namespace libcamera;\n>> +\n>> +LOG_DEFINE_CATEGORY(JPEG)\n>> +\n>> +namespace {\n>> +\n>> +struct JPEGPixelFormatInfo {\n>> +\tJ_COLOR_SPACE colorSpace;\n>> +\tconst PixelFormatInfo &pixelFormatInfo;\n>> +\tbool nvSwap;\n>> +};\n>> +\n>> +std::map<PixelFormat, JPEGPixelFormatInfo> pixelInfo{\n> \n> const ?\n\nSure ;-)\n\n> \n>> +\t{ formats::R8, { JCS_GRAYSCALE, PixelFormatInfo::info(formats::R8), false } },\n>> +\n>> +\t{ formats::RGB888, { JCS_EXT_BGR, PixelFormatInfo::info(formats::RGB888), false } },\n>> +\t{ formats::BGR888, { JCS_EXT_RGB, PixelFormatInfo::info(formats::BGR888), false } },\n>> +\n\nFWIW, I was considering dropping the R8, RGB888 entries here, as we only\nreally support NV12/NV21 in the android layer currently, but they're\nquite cheap...\n\n\n\n>> +\t{ formats::NV12, { JCS_YCbCr, PixelFormatInfo::info(formats::NV12), false } },\n>> +\t{ formats::NV21, { JCS_YCbCr, PixelFormatInfo::info(formats::NV21), true } },\n>> +\t{ formats::NV16, { JCS_YCbCr, PixelFormatInfo::info(formats::NV16), false } },\n>> +\t{ formats::NV61, { JCS_YCbCr, PixelFormatInfo::info(formats::NV61), true } },\n>> +\t{ formats::NV24, { JCS_YCbCr, PixelFormatInfo::info(formats::NV24), false } },\n>> +\t{ formats::NV42, { JCS_YCbCr, PixelFormatInfo::info(formats::NV42), true } },\n>> +};\n>> +\n>> +const struct JPEGPixelFormatInfo &findPixelInfo(const PixelFormat &format)\n>> +{\n>> +\tstatic const struct JPEGPixelFormatInfo invalidPixelFormat {\n>> +\t\tJCS_UNKNOWN, PixelFormatInfo(), false\n>> +\t};\n>> +\n>> +\tconst auto iter = pixelInfo.find(format);\n>> +\tif (iter == pixelInfo.end()) {\n>> +\t\tLOG(JPEG, Error) << \"Unsupported pixel format for JPEG compressor: \"\n>> +\t\t\t\t << format.toString();\n>> +\t\treturn invalidPixelFormat;\n>> +\t}\n>> +\n>> +\treturn iter->second;\n>> +}\n>> +\n>> +} /* namespace */\n>> +\n>> +CompressorJPEG::CompressorJPEG()\n>> +\t: quality_(95)\n>> +{\n>> +\t/* \\todo: Expand error handling coverage. */\n\naha, so this is already recorded, but I'll expand this.\n\n\n>> +\tcompress_.err = jpeg_std_error(&jerr_);\n>> +\n>> +\tjpeg_create_compress(&compress_);\n>> +}\n>> +\n>> +CompressorJPEG::~CompressorJPEG()\n>> +{\n>> +\tjpeg_destroy_compress(&compress_);\n>> +}\n>> +\n>> +int CompressorJPEG::configure(const StreamConfiguration &cfg)\n>> +{\n>> +\tconst struct JPEGPixelFormatInfo info = findPixelInfo(cfg.pixelFormat);\n>> +\tif (info.colorSpace == JCS_UNKNOWN)\n>> +\t\treturn -ENOTSUP;\n>> +\n>> +\tcompress_.image_width = cfg.size.width;\n>> +\tcompress_.image_height = cfg.size.height;\n>> +\tcompress_.in_color_space = info.colorSpace;\n>> +\n>> +\tcompress_.input_components = info.colorSpace == JCS_GRAYSCALE ? 1 : 3;\n>> +\n>> +\tjpeg_set_defaults(&compress_);\n>> +\tjpeg_set_quality(&compress_, quality_, TRUE);\n>> +\n>> +\tpixelFormatInfo = &info.pixelFormatInfo;\n>> +\n>> +\tnv_ = pixelFormatInfo->numPlanes() == 2;\n>> +\tnvSwap_ = info.nvSwap;\n>> +\n>> +\treturn 0;\n>> +}\n>> +\n>> +void CompressorJPEG::compressRGB(const libcamera::MappedBuffer *frame)\n>> +{\n>> +\tunsigned char *src = static_cast<unsigned char *>(frame->maps()[0].data());\n>> +\tunsigned int stride = pixelFormatInfo->stride(compress_.image_width, 0);\n>> +\n>> +\tJSAMPROW row_pointer[1];\n>> +\n>> +\twhile (compress_.next_scanline < compress_.image_height) {\n>> +\t\trow_pointer[0] = &src[compress_.next_scanline * stride];\n>> +\t\tjpeg_write_scanlines(&compress_, row_pointer, 1);\n>> +\t}\n>> +}\n>> +\n>> +/*\n>> + * Compress the incoming buffer from a supported NV format.\n>> + * This naively unpacks the semi-planar NV12 to a YUV888 format for libjpeg.\n>> + * Utilisation of the RAW api will be implemented on top as a performance\n>> + * improvement.\n>> + */\n>> +void CompressorJPEG::compressNV(const libcamera::MappedBuffer *frame)\n>> +{\n>> +\tstd::vector<uint8_t> tmprowbuf(compress_.image_width * 3);\n>> +\n>> +\t/*\n>> +\t * \\todo Use the raw api, and only unpack the cb/cr samples to new line buffers.\n>> +\t * If possible, see if we can set appropriate pixel strides too to save even that copy.\n> \n> Comments on 80 cols ?\n\nI'll reformat the line lengths now.\n\n\n> \n> Ah, this is recorded then! good\n> \n>> +\t *\n>> +\t * Possible hints at:\n>> +\t * https://sourceforge.net/p/libjpeg/mailman/message/30815123/\n>> +\t */\n>> +\tunsigned int y_stride = pixelFormatInfo->stride(compress_.image_width, 0);\n>> +\tunsigned int c_stride = pixelFormatInfo->stride(compress_.image_width, 1);\n>> +\n>> +\tunsigned int horzSubSample = 2 * compress_.image_width / c_stride;\n>> +\tunsigned int vertSubSample = pixelFormatInfo->planes[1].verticalSubSampling;\n>> +\n>> +\tunsigned int c_inc = horzSubSample == 1 ? 2 : 0;\n>> +\tunsigned int cb_pos = nvSwap_ ? 1 : 0;\n>> +\tunsigned int cr_pos = nvSwap_ ? 0 : 1;\n>> +\n>> +\tconst unsigned char *src = static_cast<unsigned char *>(frame->maps()[0].data());\n>> +\tconst unsigned char *src_c = src + y_stride * compress_.image_height;\n>> +\n>> +\tJSAMPROW row_pointer[1];\n>> +\trow_pointer[0] = &tmprowbuf[0];\n>> +\n>> +\tfor (unsigned int y = 0; y < compress_.image_height; y++) {\n>> +\t\tunsigned char *dst = &tmprowbuf[0];\n>> +\n>> +\t\tconst unsigned char *src_y = src + y * compress_.image_width;\n>> +\t\tconst unsigned char *src_cb = src_c + (y / vertSubSample) * c_stride + cb_pos;\n>> +\t\tconst unsigned char *src_cr = src_c + (y / vertSubSample) * c_stride + cr_pos;\n>> +\n>> +\t\tfor (unsigned int x = 0; x < compress_.image_width; x += 2) {\n>> +\t\t\tdst[0] = *src_y;\n>> +\t\t\tdst[1] = *src_cb;\n>> +\t\t\tdst[2] = *src_cr;\n>> +\t\t\tsrc_y++;\n>> +\t\t\tsrc_cb += c_inc;\n>> +\t\t\tsrc_cr += c_inc;\n>> +\t\t\tdst += 3;\n>> +\n>> +\t\t\tdst[0] = *src_y;\n>> +\t\t\tdst[1] = *src_cb;\n>> +\t\t\tdst[2] = *src_cr;\n>> +\t\t\tsrc_y++;\n>> +\t\t\tsrc_cb += 2;\n>> +\t\t\tsrc_cr += 2;\n>> +\t\t\tdst += 3;\n>> +\t\t}\n> \n> I haven't really reviewed that part, but if it works, it's all good,\n> right ? :)\n\nThis is lifted from src/qcam/format_convertor.cpp:\nFormatConverter::convertNV(), and adjusted for local requirements.\n\nI hope to drop this function, and replace with the RAW api, but yes it\nworks, and allows testing of the JPEG compressor.\n\n\n> \n>> +\n>> +\t\tjpeg_write_scanlines(&compress_, row_pointer, 1);\n>> +\t}\n>> +}\n>> +\n>> +int CompressorJPEG::compress(const FrameBuffer *source, CompressedImage *jpeg)\n>> +{\n>> +\tMappedFrameBuffer frame(source, PROT_READ);\n>> +\tif (!frame.isValid()) {\n>> +\t\tLOG(JPEG, Error) << \"Failed to map FrameBuffer : \"\n>                                                               ^ need\n>                                                               this space?\n>> +\t\t\t\t << strerror(frame.error());\n>> +\t\treturn -frame.error();\n> \n> I assume the leading '-' is intentional\n\nErr, given the documentation I just added for this:\n\n> * \\return 0 on success or a negative error code otherwise\n\nThen no ;-)\n\nFixed.\n\n> \n>> +\t}\n>> +\n>> +\tjpeg_mem_dest(&compress_, &jpeg->data, &jpeg->length);\n>> +\n>> +\tjpeg_start_compress(&compress_, TRUE);\n>> +\n>> +\tLOG(JPEG, Debug) << \"JPEG Compress Starting:\"\n>> +\t\t\t << \" Width: \" << compress_.image_width\n>> +\t\t\t << \" height: \" << compress_.image_height;\n>> +\n>> +\tif (nv_)\n>> +\t\tcompressNV(&frame);\n>> +\telse\n>> +\t\tcompressRGB(&frame);\n> \n> If one wants to be pedantic, the distinction here seems to be if we\n> have a single place or more. It's not only NV__ which is multi-planar\n> (or better, semi planar, as NV12 is, in example, the semi-planar\n> version of the three-plane YUV420 format) or RAW which is single\n> planar (ie. packed YUV)\n> \n> But I see you only support NV variations and some RAW formats, if this\n> is not going to be updated I think then the naming is fine.\n\nI have patches on top which add other YUV unpacking, but it's so ugly,\nand *unused* that I have removed it, and I hope it can be added in a\nbetter form /if and when required/ or as part of using the RAW api to\nparse data without unpacking.\n\n\n>> +\n>> +\tLOG(JPEG, Debug) << \"JPEG Compress Completed\";\n>> +\n>> +\tjpeg_finish_compress(&compress_);\n>> +\n>> +\treturn 0;\n>> +}\n>> +\n>> +void CompressorJPEG::free(CompressedImage *jpeg)\n>> +{\n>> +\t::free(jpeg->data);\n>> +\tjpeg->data = nullptr;\n> \n> Would setting length to zero hurt ?\n\nNope ;-) I'll add that too for consistency.\n\n\n> \n>> +}\n>> diff --git a/src/android/jpeg/compressor_jpeg.h b/src/android/jpeg/compressor_jpeg.h\n>> new file mode 100644\n>> index 000000000000..3c0c3ff68449\n>> --- /dev/null\n>> +++ b/src/android/jpeg/compressor_jpeg.h\n>> @@ -0,0 +1,46 @@\n>> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n>> +/*\n>> + * Copyright (C) 2020, Google Inc.\n>> + *\n>> + * compressor_jpeg.h - JPEG compression using libjpeg\n>> + */\n>> +#ifndef __ANDROID_JPEG_COMPRESSOR_JPEG_H__\n>> +#define __ANDROID_JPEG_COMPRESSOR_JPEG_H__\n>> +\n>> +#include \"compressor.h\"\n>> +\n>> +#include <libcamera/buffer.h>\n>> +#include <libcamera/stream.h>\n>> +\n>> +#include \"libcamera/internal/buffer.h\"\n>> +#include \"libcamera/internal/formats.h\"\n>> +\n>> +#include <jpeglib.h>\n>> +\n>> +class CompressorJPEG : public Compressor\n>> +{\n>> +public:\n>> +\tCompressorJPEG();\n>> +\t~CompressorJPEG();\n>> +\n>> +\tint configure(const libcamera::StreamConfiguration &cfg);\n>> +\tint compress(const libcamera::FrameBuffer *source, CompressedImage *jpeg);\n>> +\tvoid free(CompressedImage *jpeg);\n> \n> These should be marked with 'override'\n\nAdded.\n\n> \n>> +\n>> +private:\n>> +\tvoid compressRGB(const libcamera::MappedBuffer *frame);\n>> +\tvoid compressYUV(const libcamera::MappedBuffer *frame);\n> \n> This seems not to be implemented\n\nOops, that's supposed to be in the patch on top (i.e. removed from here).\n\n> \n>> +\tvoid compressNV(const libcamera::MappedBuffer *frame);\n>> +\n>> +\tstruct jpeg_compress_struct compress_;\n>> +\tstruct jpeg_error_mgr jerr_;\n>> +\n>> +\tunsigned int quality_;\n>> +\n>> +\tconst libcamera::PixelFormatInfo *pixelFormatInfo;\n> \n> as a class member this should end with _\n\nYes, indeed it should.\n\n> \n> Mostly nit, nothing blocking this patch for real\n> \n> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org>\n\nThanks, all nits updated I believe.\n\n\n> \n> Thanks\n>   j\n> \n>> +\n>> +\tbool nv_;\n>> +\tbool nvSwap_;\n>> +};\n>> +\n>> +#endif /* __ANDROID_JPEG_COMPRESSOR_JPEG_H__ */\n>> diff --git a/src/android/meson.build b/src/android/meson.build\n>> index 822cad621f01..51dcd99ee62f 100644\n>> --- a/src/android/meson.build\n>> +++ b/src/android/meson.build\n>> @@ -6,6 +6,7 @@ android_hal_sources = files([\n>>      'camera_device.cpp',\n>>      'camera_metadata.cpp',\n>>      'camera_ops.cpp',\n>> +    'jpeg/compressor_jpeg.cpp',\n>>  ])\n>>\n>>  android_camera_metadata_sources = files([\n>> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\n>> index 3aad4386ffc2..d78e2c1f6eb8 100644\n>> --- a/src/libcamera/meson.build\n>> +++ b/src/libcamera/meson.build\n>> @@ -124,6 +124,8 @@ if get_option('android')\n>>      libcamera_sources += android_hal_sources\n>>      includes += android_includes\n>>      libcamera_link_with += android_camera_metadata\n>> +\n>> +    libcamera_deps += dependency('libjpeg')\n>>  endif\n>>\n>>  # We add '/' to the build_rpath as a 'safe' path to act as a boolean flag.\n>> --\n>> 2.25.1\n>>\n>> _______________________________________________\n>> libcamera-devel mailing list\n>> libcamera-devel@lists.libcamera.org\n>> https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id A83FFBD86F\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue,  4 Aug 2020 13:09:54 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 233E460548;\n\tTue,  4 Aug 2020 15:09:54 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 839946048E\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue,  4 Aug 2020 15:09:53 +0200 (CEST)","from [192.168.0.20]\n\t(cpc89244-aztw30-2-0-cust3082.18-1.cable.virginm.net [86.31.172.11])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id D940927B;\n\tTue,  4 Aug 2020 15:09:52 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"Ksz5Y4W3\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1596546593;\n\tbh=pQ0kUb18HvjrHq6JoJ1+WrP8hIILijHHFNM2Tocsotw=;\n\th=Reply-To:Subject:To:Cc:References:From:Date:In-Reply-To:From;\n\tb=Ksz5Y4W3OeMVtptFBlEaNWw6jptFJffHr0Zq+LFDJe2y3RfasONCv0hjf00vgiX83\n\tMql7OYPRIfKzPdajaG5kcyhtOV3P2DGrzsE0uUFIqj7pFNnVQmEpVbEbjiague9xz8\n\t/3QvoS2SoTQlQSf5PMB4wwfA+gB4Hx2nUzCHkde8=","To":"Jacopo Mondi <jacopo@jmondi.org>","References":"<20200803161816.107113-1-kieran.bingham@ideasonboard.com>\n\t<20200803161816.107113-12-kieran.bingham@ideasonboard.com>\n\t<20200804123421.fujaixgpojtgdk5t@uno.localdomain>","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Autocrypt":"addr=kieran.bingham@ideasonboard.com; keydata=\n\t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\n\tWZw01QYHU/GUV/zHJSFk","Organization":"Ideas on Board","Message-ID":"<8fcf81f1-74d6-83e4-e2d9-41f1865a8aeb@ideasonboard.com>","Date":"Tue, 4 Aug 2020 14:09:50 +0100","User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101\n\tThunderbird/68.10.0","MIME-Version":"1.0","In-Reply-To":"<20200804123421.fujaixgpojtgdk5t@uno.localdomain>","Content-Language":"en-GB","Subject":"Re: [libcamera-devel] [PATCH v2 11/12] android: Introduce JPEG\n\tcompression","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Reply-To":"kieran.bingham@ideasonboard.com","Cc":"libcamera devel <libcamera-devel@lists.libcamera.org>","Content-Type":"text/plain; charset=\"us-ascii\"","Content-Transfer-Encoding":"7bit","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]