Patch Detail
Show a patch.
GET /api/1.1/patches/8910/?format=api
{ "id": 8910, "url": "https://patchwork.libcamera.org/api/1.1/patches/8910/?format=api", "web_url": "https://patchwork.libcamera.org/patch/8910/", "project": { "id": 1, "url": "https://patchwork.libcamera.org/api/1.1/projects/1/?format=api", "name": "libcamera", "link_name": "libcamera", "list_id": "libcamera_core", "list_email": "libcamera-devel@lists.libcamera.org", "web_url": "", "scm_url": "", "webscm_url": "" }, "msgid": "<20200721220126.202065-2-kieran.bingham@ideasonboard.com>", "date": "2020-07-21T22:01:21", "name": "[libcamera-devel,RFC,1/6] android: Introduce JPEG compression", "commit_ref": null, "pull_url": null, "state": "rfc", "archived": false, "hash": "1902a7c56643d0b8533ba506dc7d9c1bb64de4dd", "submitter": { "id": 4, "url": "https://patchwork.libcamera.org/api/1.1/people/4/?format=api", "name": "Kieran Bingham", "email": "kieran.bingham@ideasonboard.com" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/8910/mbox/", "series": [ { "id": 1125, "url": "https://patchwork.libcamera.org/api/1.1/series/1125/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=1125", "date": "2020-07-21T22:01:20", "name": "android: jpeg / software streams", "version": 1, "mbox": "https://patchwork.libcamera.org/series/1125/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/8910/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/8910/checks/", "tags": {}, "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 498F6C2E68\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 21 Jul 2020 22:01:36 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 16BBA6099C;\n\tWed, 22 Jul 2020 00:01:36 +0200 (CEST)", "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id A540160923\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 22 Jul 2020 00:01:32 +0200 (CEST)", "from localhost.localdomain\n\t(cpc89242-aztw30-2-0-cust488.18-1.cable.virginm.net [86.31.129.233])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 07B77585;\n\tWed, 22 Jul 2020 00:01:30 +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=\"ME8STVyK\"; dkim-atps=neutral", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1595368891;\n\tbh=4EDcEmP0yH0t+8WP9R39WQ6pcFZhIjGrKnzfKWFNVpc=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=ME8STVyKogGi8p7xtmzTnMkdmn74wlbBgxXss3czPs6WUtp9KWQIbfW7uF44i8GJi\n\tWHo8JNIUP9wQuBH9QTSHQRliWorZOVMowyvhT/ApGyAzwzyon3f5vSg8XOexe/ybnM\n\t+eMYHbo5U8p0yfBjahiL7HfFzqo8fAqr6EGsIapE=", "From": "Kieran Bingham <kieran.bingham@ideasonboard.com>", "To": "libcamera devel <libcamera-devel@lists.libcamera.org>", "Date": "Tue, 21 Jul 2020 23:01:21 +0100", "Message-Id": "<20200721220126.202065-2-kieran.bingham@ideasonboard.com>", "X-Mailer": "git-send-email 2.25.1", "In-Reply-To": "<20200721220126.202065-1-kieran.bingham@ideasonboard.com>", "References": "<20200721220126.202065-1-kieran.bingham@ideasonboard.com>", "MIME-Version": "1.0", "Subject": "[libcamera-devel] [RFC PATCH 1/6] 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>", "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>" }, "content": "Provide a compressor interface and implement a JPEG compressor using libjpeg.\n\nSigned-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n---\n src/android/jpeg/compressor.h | 28 +++\n src/android/jpeg/compressor_jpeg.cpp | 279 +++++++++++++++++++++++++++\n src/android/jpeg/compressor_jpeg.h | 44 +++++\n src/android/meson.build | 1 +\n src/libcamera/meson.build | 2 +\n 5 files changed, 354 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", "diff": "diff --git a/src/android/jpeg/compressor.h b/src/android/jpeg/compressor.h\nnew file mode 100644\nindex 000000000000..f95e4a4539cb\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 __LIBCAMERA_COMPRESSOR_H__\n+#define __LIBCAMERA_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 /* __LIBCAMERA_COMPRESSOR_H__ */\ndiff --git a/src/android/jpeg/compressor_jpeg.cpp b/src/android/jpeg/compressor_jpeg.cpp\nnew file mode 100644\nindex 000000000000..78fa5e399d99\n--- /dev/null\n+++ b/src/android/jpeg/compressor_jpeg.cpp\n@@ -0,0 +1,279 @@\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/log.h\"\n+\n+using namespace libcamera;\n+\n+LOG_DEFINE_CATEGORY(JPEG)\n+\n+struct PixelFormatPlaneInfo {\n+\tunsigned int bitsPerPixel;\n+\tunsigned int hSubSampling;\n+\tunsigned int vSubSampling;\n+};\n+\n+struct PixelFormatInfo {\n+\tJ_COLOR_SPACE colorSpace;\n+\tunsigned int numPlanes;\n+\tbool nvSwap;\n+\tPixelFormatPlaneInfo planes[3];\n+};\n+\n+namespace {\n+\n+static const std::map<PixelFormat, struct PixelFormatInfo> pixelInfo{\n+\t{ formats::R8, { JCS_GRAYSCALE, 1, false, { { 8, 1, 1 }, { 0, 0, 0 }, { 0, 0, 0 } } } },\n+\n+\t/* RGB formats. */\n+\t{ formats::RGB888, { JCS_EXT_BGR, 1, false, { { 24, 1, 1 }, { 0, 0, 0 }, { 0, 0, 0 } } } },\n+\t{ formats::BGR888, { JCS_EXT_RGB, 1, false, { { 24, 1, 1 }, { 0, 0, 0 }, { 0, 0, 0 } } } },\n+\n+\t/* YUV packed formats. */\n+\t{ formats::UYVY, { JCS_YCbCr, 1, false, { { 16, 1, 1 }, { 0, 0, 0 }, { 0, 0, 0 } } } },\n+\t{ formats::VYUY, { JCS_YCbCr, 1, false, { { 16, 1, 1 }, { 0, 0, 0 }, { 0, 0, 0 } } } },\n+\t{ formats::YUYV, { JCS_YCbCr, 1, false, { { 16, 1, 1 }, { 0, 0, 0 }, { 0, 0, 0 } } } },\n+\t{ formats::YVYU, { JCS_YCbCr, 1, false, { { 16, 1, 1 }, { 0, 0, 0 }, { 0, 0, 0 } } } },\n+\n+\t/* YUY planar formats. */\n+\t{ formats::NV12, { JCS_YCbCr, 2, false, { { 8, 1, 1 }, { 16, 2, 2 }, { 0, 0, 0 } } } },\n+\t{ formats::NV21, { JCS_YCbCr, 2, true, { { 8, 1, 1 }, { 16, 2, 2 }, { 0, 0, 0 } } } },\n+\t{ formats::NV16, { JCS_YCbCr, 2, false, { { 8, 1, 1 }, { 16, 2, 1 }, { 0, 0, 0 } } } },\n+\t{ formats::NV61, { JCS_YCbCr, 2, true, { { 8, 1, 1 }, { 16, 2, 1 }, { 0, 0, 0 } } } },\n+\t{ formats::NV24, { JCS_YCbCr, 2, false, { { 8, 1, 1 }, { 16, 1, 1 }, { 0, 0, 0 } } } },\n+\t{ formats::NV42, { JCS_YCbCr, 2, true, { { 8, 1, 1 }, { 16, 1, 1 }, { 0, 0, 0 } } } },\n+};\n+\n+}\n+\n+const struct PixelFormatInfo &findPixelInfo(const PixelFormat &format)\n+{\n+\tstatic const struct PixelFormatInfo invalidPixelFormat {\n+\t\t\tJCS_UNKNOWN, 0, 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+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+\t{\n+\t\tLOG(JPEG, Warning) << \"Configuring pixelformat as : \"\n+\t\t\t\t\t<< cfg.pixelFormat.toString();\n+\t\tLOG(JPEG, Warning) << \" : \" << cfg.toString();\n+\n+\t\tstd::vector<PixelFormat> formats = cfg.formats().pixelformats();\n+\t\tLOG(JPEG, Warning) << \"StreamConfiguration supports \" << formats.size() << \" formats:\";\n+\t\tfor (const PixelFormat &format : formats)\n+\t\t\tLOG(JPEG, Warning) << \" - \" << format.toString();\n+\t}\n+\n+\tconst struct PixelFormatInfo info = findPixelInfo(cfg.pixelFormat);\n+\tif (info.colorSpace == JCS_UNKNOWN)\n+\t\treturn -ENOTSUP;\n+\n+\t/*\n+\t * Todo: The stride given by the stream configuration has caused issues.\n+\t * Validate it and also handle per-plane strides.\n+\t */\n+\tstride_ = cfg.stride;\n+\tstride_ = cfg.size.width * info.planes[0].bitsPerPixel / 8;\n+\t/* Saw some errors with strides, so this is a debug/develop check */\n+\tif (cfg.stride != stride_)\n+\t\tLOG(JPEG, Error) << \"*** StreamConfigure provided stride of \"\n+\t\t\t\t << cfg.stride << \" rather than \" << stride_;\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+\tnv_ = info.numPlanes == 2;\n+\tnvSwap_ = info.nvSwap;\n+\n+\t/* Use the 'last' plane for subsampling of component info. */\n+\tunsigned int p = info.numPlanes - 1;\n+\thorzSubSample_ = info.planes[p].hSubSampling;\n+\tvertSubSample_ = info.planes[p].vSubSampling;\n+\n+\treturn 0;\n+}\n+\n+void CompressorJPEG::compressRGB(const libcamera::MappedFrameBuffer *frame)\n+{\n+\tunsigned char *src = static_cast<unsigned char *>(frame->maps()[0].address);\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+ * A very dull implementation to compress YUYV.\n+ * To be converted to a generic algorithm akin to NV12.\n+ * If it can be shared with NV12 great, but we might be able to further\n+ * optimisze the NV layouts by only depacking the CrCb pixels.\n+ */\n+void CompressorJPEG::compressYUV(const libcamera::MappedFrameBuffer *frame)\n+{\n+\tstd::vector<uint8_t> tmprowbuf(compress_.image_width * 3);\n+\tunsigned char *input = static_cast<unsigned char *>(frame->maps()[0].address);\n+\n+\tJSAMPROW row_pointer[1];\n+\trow_pointer[0] = &tmprowbuf[0];\n+\twhile (compress_.next_scanline < compress_.image_height) {\n+\t\tunsigned i, j;\n+\t\tunsigned offset = compress_.next_scanline * compress_.image_width * 2; //offset to the correct row\n+\t\tfor (i = 0, j = 0; i < compress_.image_width * 2; i += 4, j += 6) { //input strides by 4 bytes, output strides by 6 (2 pixels)\n+\t\t\ttmprowbuf[j + 0] = input[offset + i + 0]; // Y (unique to this pixel)\n+\t\t\ttmprowbuf[j + 1] = input[offset + i + 1]; // U (shared between pixels)\n+\t\t\ttmprowbuf[j + 2] = input[offset + i + 3]; // V (shared between pixels)\n+\t\t\ttmprowbuf[j + 3] = input[offset + i + 2]; // Y (unique to this pixel)\n+\t\t\ttmprowbuf[j + 4] = input[offset + i + 1]; // U (shared between pixels)\n+\t\t\ttmprowbuf[j + 5] = input[offset + i + 3]; // V (shared between pixels)\n+\t\t}\n+\t\tjpeg_write_scanlines(&compress_, row_pointer, 1);\n+\t}\n+}\n+\n+/*\n+ * Really inefficient NV unpacking to YUV888 for JPEG compress.\n+ * This /could/ be improved (drastically I hope) ;-)\n+ */\n+void CompressorJPEG::compressNV(const libcamera::MappedFrameBuffer *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+\t *\n+\t * Possible hints at:\n+\t * https://sourceforge.net/p/libjpeg/mailman/message/30815123/\n+\t */\n+\tunsigned int c_stride = compress_.image_width * (2 / horzSubSample_);\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].address);\n+\tconst unsigned char *src_c = src + compress_.image_width * compress_.image_height; // * stride[0] surely\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_) *\n+\t\t\t\t\t c_stride + cb_pos;\n+\t\tconst unsigned char *src_cr = src_c + (y / vertSubSample_) *\n+\t\t\t\t\t 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+\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+\t\t\t\t << strerror(frame.error());\n+\t\treturn -frame.error();\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+\tLOG(JPEG, Debug) << \"Width: \" << compress_.image_width\n+\t\t\t << \" height: \" << compress_.image_height\n+\t\t\t << \" stride: \" << stride_;\n+\n+\tif (nv_)\n+\t\tcompressNV(&frame);\n+\telse if (compress_.in_color_space == JCS_YCbCr)\n+\t\tcompressYUV(&frame);\n+\telse\n+\t\tcompressRGB(&frame);\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+}\ndiff --git a/src/android/jpeg/compressor_jpeg.h b/src/android/jpeg/compressor_jpeg.h\nnew file mode 100644\nindex 000000000000..11009481a2fe\n--- /dev/null\n+++ b/src/android/jpeg/compressor_jpeg.h\n@@ -0,0 +1,44 @@\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 __COMPRESSOR_JPEG_H__\n+#define __COMPRESSOR_JPEG_H__\n+\n+#include \"compressor.h\"\n+\n+#include <libcamera/buffer.h>\n+#include <libcamera/stream.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+private:\n+\tvoid compressRGB(const libcamera::MappedFrameBuffer *frame);\n+\tvoid compressYUV(const libcamera::MappedFrameBuffer *frame);\n+\tvoid compressNV(const libcamera::MappedFrameBuffer *frame);\n+\n+\tstruct jpeg_compress_struct compress_;\n+\tstruct jpeg_error_mgr jerr_;\n+\n+\tunsigned int quality_;\n+\tunsigned int stride_;\n+\n+\tbool nv_;\n+\tbool nvSwap_;\n+\tunsigned int horzSubSample_;\n+\tunsigned int vertSubSample_;\n+};\n+\n+#endif /* __COMPRESSOR_JPEG_H__ */\ndiff --git a/src/android/meson.build b/src/android/meson.build\nindex 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([\ndiff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\nindex 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", "prefixes": [ "libcamera-devel", "RFC", "1/6" ] }