Patch Detail
Show a patch.
GET /api/1.1/patches/19097/?format=api
{ "id": 19097, "url": "https://patchwork.libcamera.org/api/1.1/patches/19097/?format=api", "web_url": "https://patchwork.libcamera.org/patch/19097/", "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": "<20230928185537.20178-5-andrey.konovalov@linaro.org>", "date": "2023-09-28T18:55:36", "name": "[libcamera-devel,RFC,v3,4/5] libcamera: converter: add software converter", "commit_ref": null, "pull_url": null, "state": "superseded", "archived": false, "hash": "06d960a6b0516e21d9926c3434430613074fc342", "submitter": { "id": 25, "url": "https://patchwork.libcamera.org/api/1.1/people/25/?format=api", "name": "Andrey Konovalov", "email": "andrey.konovalov@linaro.org" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/19097/mbox/", "series": [ { "id": 4043, "url": "https://patchwork.libcamera.org/api/1.1/series/4043/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=4043", "date": "2023-09-28T18:55:32", "name": "libcamera: converter: generalize Converter to remove MediaDevice dependency", "version": 3, "mbox": "https://patchwork.libcamera.org/series/4043/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/19097/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/19097/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 CB366C0F2A\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 28 Sep 2023 18:57:12 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 8A34862968;\n\tThu, 28 Sep 2023 20:57:12 +0200 (CEST)", "from mail-wr1-x432.google.com (mail-wr1-x432.google.com\n\t[IPv6:2a00:1450:4864:20::432])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 7751962963\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 28 Sep 2023 20:57:11 +0200 (CEST)", "by mail-wr1-x432.google.com with SMTP id\n\tffacd0b85a97d-3232be274a0so5450048f8f.1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 28 Sep 2023 11:57:11 -0700 (PDT)", "from Lat-5310.. ([87.116.164.210]) by smtp.gmail.com with ESMTPSA\n\tid\n\tu1-20020adfed41000000b003247d3e5d99sm890842wro.55.2023.09.28.11.57.10\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tThu, 28 Sep 2023 11:57:10 -0700 (PDT)" ], "DKIM-Signature": [ "v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1695927432;\n\tbh=zDUi3LpHVJzJwwbZMHnvqrjZ4wAnwzhd1Wy4V3S/DJg=;\n\th=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=Fs0mL9zqwc8yFaRPhDl0M2Y5qQc/Z2ePtCciT67oJENj1FtDcRqQCOUJdtMUrfLY5\n\ts6yXqXuQlVQY9mHlC5TUZRDfsxL9ZbZ/pt0s+2IKBvq3Y3MLTaYQ7ptqMqd23seKM8\n\t023dop3db7645LBU/sgwni2undw/fk9/XWYG0P3dhTKMLh3pvWnl5eAHrwBbLy9h6p\n\tMrWrdjs1m/uDD++pclLMkyOqCz0f7AuKI2F7a2NOltEHf/gj70ab92H3wVNqz/jw+6\n\tBOac0sCKJE4UHkfJJSqK0TYxgP7V1utVuzaSe3n0dI5rjxC6ZGLJ8Qg4ihP3eCk/Q3\n\tl4jZNHrOp4/VA==", "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=linaro.org; s=google; t=1695927431; x=1696532231;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=eMsETHaSws+NBrGN8EhisryMSR4UFOOVrra7FOgytfw=;\n\tb=Ddd6DS7uFqtfUXIq1d2JqubbWaC+7FY1G96tJ6zhjpk3w9ciL0RXebierdNpqDH6a2\n\tCr+y7v0kFHf7L+zX9VztgfZwyo/17SnaJIPz8G/yB8A7mJ0W5ihBAreKo4SWPWOPyGIr\n\taoMWk8Qa0UYBjDyoammAQYW4jgYWg2K/pAyqBhA3oDF/nk/yIhMX7EzAUvgdybX48glW\n\tKsOk/a2rcmXHfRgodAi1sYr5VdYQjmmm8sWUmaIhbpXBu9/XgtkK1weA92e8RqvjXU18\n\tNjZQvoIM+GuNZvq5V2ymGGNCqIQEQwArQaUpldxHnNv+SKCn/S5IspIC/9rWqu2KARYm\n\twJFA==" ], "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (2048-bit key; \n\tunprotected) header.d=linaro.org\n\theader.i=@linaro.org header.b=\"Ddd6DS7u\"; \n\tdkim-atps=neutral", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1695927431; x=1696532231;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n\t:subject:date:message-id:reply-to;\n\tbh=eMsETHaSws+NBrGN8EhisryMSR4UFOOVrra7FOgytfw=;\n\tb=msJ7qYcezXmqdW4Jq9F5xPf+eYejA+ubuUdthj9rnsUS6KkQxw3vulDcOU/SK205x+\n\tZgUVKrf9sIV6Mse+BVso8Ez0+RLQO68fsl6egFcCPwEvmPfE1/VX//pw66BkWbVFe12A\n\tBQ8cRA8Blq07MmRXLp0kNjc4ETzhSuHBI1NXvWyY+GM8M6nUDdP/ZRZ0bo6krcsPpdYg\n\tp/c5whM6Ug5Mgx6awlCvlmg9Bj3uUNdUqi0xRlCOJsezc2mAWNYdEXIaf4Znqk48CN8t\n\tEVGeWgQMIN6wKZDGKjpNjUxvwtaPAAz6HJ7aUbUNn0ymy/VfxLUnuq2FSMeh4Nf8fJUM\n\tXB/w==", "X-Gm-Message-State": "AOJu0YwM02D00uag34rzEzmdIFYF2pFyFsiqZQsuJbVuFuaDBLV38hD+\n\tfhMpsZKIqoAZgsKokx2B63n7c2hKI8/3No7FmIQ=", "X-Google-Smtp-Source": "AGHT+IFXbWgnM8eWvD8W8DCdpsm4j3/z6ysDs9jyDb/iXGGagJbxjQUwAnucP+IMM5zZ5SMo+NyQnw==", "X-Received": "by 2002:adf:e68a:0:b0:321:57a5:6e6c with SMTP id\n\tr10-20020adfe68a000000b0032157a56e6cmr1980795wrm.34.1695927431131; \n\tThu, 28 Sep 2023 11:57:11 -0700 (PDT)", "To": "libcamera-devel@lists.libcamera.org", "Date": "Thu, 28 Sep 2023 21:55:36 +0300", "Message-Id": "<20230928185537.20178-5-andrey.konovalov@linaro.org>", "X-Mailer": "git-send-email 2.34.1", "In-Reply-To": "<20230928185537.20178-1-andrey.konovalov@linaro.org>", "References": "<20230928185537.20178-1-andrey.konovalov@linaro.org>", "MIME-Version": "1.0", "Content-Transfer-Encoding": "7bit", "Subject": "[libcamera-devel] [RFC PATCH v3 4/5] libcamera: converter: add\n\tsoftware converter", "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>", "From": "Andrey Konovalov via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>", "Reply-To": "Andrey Konovalov <andrey.konovalov@linaro.org>", "Cc": "jacopo.mondi@ideasonboard.com, bryan.odonoghue@linaro.org,\n\tsrinivas.kandagatla@linaro.org", "Errors-To": "libcamera-devel-bounces@lists.libcamera.org", "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>" }, "content": "Use the Converter interface to implement bilinear Bayer demosaic\nfiltering and very simple AWB on CPU.\n\nThe only output format currently supported is RGB888.\n\nSigned-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>\n---\n .../internal/converter/converter_softw.h | 100 ++++\n .../libcamera/internal/converter/meson.build | 1 +\n src/libcamera/converter/converter_softw.cpp | 445 ++++++++++++++++++\n src/libcamera/converter/meson.build | 3 +-\n 4 files changed, 548 insertions(+), 1 deletion(-)\n create mode 100644 include/libcamera/internal/converter/converter_softw.h\n create mode 100644 src/libcamera/converter/converter_softw.cpp", "diff": "diff --git a/include/libcamera/internal/converter/converter_softw.h b/include/libcamera/internal/converter/converter_softw.h\nnew file mode 100644\nindex 00000000..daa2d6da\n--- /dev/null\n+++ b/include/libcamera/internal/converter/converter_softw.h\n@@ -0,0 +1,100 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2023, Linaro Ltd\n+ *\n+ * converter_softw.h - interface of software converter (runs 100% on CPU)\n+ */\n+\n+#pragma once\n+\n+#include <functional>\n+#include <map>\n+#include <memory>\n+#include <string>\n+#include <tuple>\n+#include <vector>\n+\n+#include <libcamera/base/log.h>\n+#include <libcamera/base/mutex.h>\n+#include <libcamera/base/signal.h>\n+#include <libcamera/base/thread.h>\n+\n+#include <libcamera/pixel_format.h>\n+\n+#include \"libcamera/internal/converter.h\"\n+\n+namespace libcamera {\n+\n+class FrameBuffer;\n+class MediaDevice;\n+class Size;\n+class SizeRange;\n+struct StreamConfiguration;\n+\n+class SwConverter : public Converter\n+{\n+public:\n+\tSwConverter(MediaDevice *media);\n+\n+\tint loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; }\n+\tbool isValid() const { return isp_ != nullptr; }\n+\n+\tstd::vector<PixelFormat> formats(PixelFormat input);\n+\tSizeRange sizes(const Size &input);\n+\n+\tstd::tuple<unsigned int, unsigned int>\n+\tstrideAndFrameSize(const PixelFormat &pixelFormat, const Size &size);\n+\n+\tint configure(const StreamConfiguration &inputCfg,\n+\t\t const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfg);\n+\tint exportBuffers(unsigned int ouput, unsigned int count,\n+\t\t\t std::vector<std::unique_ptr<FrameBuffer>> *buffers);\n+\n+\tvoid process(FrameBuffer *input, FrameBuffer *output);\n+\tint start();\n+\tvoid stop();\n+\n+\tint queueBuffers(FrameBuffer *input,\n+\t\t\t const std::map<unsigned int, FrameBuffer *> &outputs);\n+\n+private:\n+\tclass Isp : public Object\n+\t{\n+\tpublic:\n+\t\tIsp(SwConverter *converter);\n+\t\t~Isp();\n+\n+\t\tint configure(const StreamConfiguration &inputCfg,\n+\t\t\t const StreamConfiguration &outputCfg);\n+\t\tint exportBuffers(unsigned int count,\n+\t\t\t\t std::vector<std::unique_ptr<FrameBuffer>> *buffers);\n+\t\tvoid process(FrameBuffer *input, FrameBuffer *output);\n+\t\tint start();\n+\t\tvoid stop();\n+\t\tvoid waitForIdle();\n+\n+\tprivate:\n+\t\tvoid debayer(uint8_t *dst, const uint8_t *src);\n+\n+\t\tSwConverter *converter_;\n+\n+\t\tThread thread_;\n+\n+\t\tlibcamera::Mutex idleMutex_;\n+\t\tlibcamera::ConditionVariable idleCV_;\n+\t\tbool idle_ LIBCAMERA_TSA_GUARDED_BY(idleMutex_);\n+\n+\t\tunsigned int width_;\n+\t\tunsigned int height_;\n+\t\tunsigned int stride_;\n+\t\tPoint red_shift_;\n+\n+\t\tunsigned long rNumerat_, rDenomin_; /* red gain for AWB */\n+\t\tunsigned long bNumerat_, bDenomin_; /* blue gain for AWB */\n+\t\tunsigned long gNumerat_, gDenomin_; /* green gain for AWB */\n+\t};\n+\n+\tstd::unique_ptr<Isp> isp_;\n+};\n+\n+} /* namespace libcamera */\ndiff --git a/include/libcamera/internal/converter/meson.build b/include/libcamera/internal/converter/meson.build\nindex 891e79e7..843a0483 100644\n--- a/include/libcamera/internal/converter/meson.build\n+++ b/include/libcamera/internal/converter/meson.build\n@@ -1,5 +1,6 @@\n # SPDX-License-Identifier: CC0-1.0\n \n libcamera_internal_headers += files([\n+ 'converter_softw.h',\n 'converter_v4l2_m2m.h',\n ])\ndiff --git a/src/libcamera/converter/converter_softw.cpp b/src/libcamera/converter/converter_softw.cpp\nnew file mode 100644\nindex 00000000..67245715\n--- /dev/null\n+++ b/src/libcamera/converter/converter_softw.cpp\n@@ -0,0 +1,445 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2023, Linaro Ltd\n+ *\n+ * converter_softw.h - interface of software converter (runs 100% on CPU)\n+ */\n+\n+#include \"libcamera/internal/converter/converter_softw.h\"\n+\n+#include <sys/mman.h>\n+#include <sys/types.h>\n+#include <unistd.h>\n+\n+#include <libcamera/formats.h>\n+#include <libcamera/stream.h>\n+\n+#include \"libcamera/internal/bayer_format.h\"\n+#include \"libcamera/internal/framebuffer.h\"\n+#include \"libcamera/internal/mapped_framebuffer.h\"\n+\n+namespace libcamera {\n+\n+LOG_DECLARE_CATEGORY(Converter)\n+\n+SwConverter::SwConverter([[maybe_unused]] MediaDevice *media)\n+\t: Converter()\n+{\n+\tisp_ = std::make_unique<SwConverter::Isp>(this);\n+}\n+\n+std::vector<PixelFormat> SwConverter::formats(PixelFormat input)\n+{\n+\tBayerFormat inputFormat = BayerFormat::fromPixelFormat(input);\n+\n+\t/* Only RAW10P is currently supported */\n+\tif (inputFormat.bitDepth != 10 ||\n+\t inputFormat.packing != BayerFormat::Packing::CSI2) {\n+\t\tLOG(Converter, Info)\n+\t\t\t<< \"Unsupported input format \" << input.toString();\n+\t\treturn {};\n+\t}\n+\n+\treturn { formats::RGB888 };\n+}\n+\n+SizeRange SwConverter::sizes(const Size &input)\n+{\n+\tif (input.width < 2 || input.height < 2) {\n+\t\tLOG(Converter, Error)\n+\t\t\t<< \"Input format size too small: \" << input.toString();\n+\t\treturn {};\n+\t}\n+\n+\treturn SizeRange(Size(input.width - 2, input.height - 2));\n+}\n+\n+std::tuple<unsigned int, unsigned int>\n+SwConverter::strideAndFrameSize(const PixelFormat &pixelFormat,\n+\t\t\t\tconst Size &size)\n+{\n+\t/* Only RGB888 output is currently supported */\n+\tif (pixelFormat != formats::RGB888)\n+\t\treturn {};\n+\n+\tunsigned int stride = size.width * 3;\n+\treturn std::make_tuple(stride, stride * (size.height));\n+}\n+\n+int SwConverter::configure(const StreamConfiguration &inputCfg,\n+\t\t\t const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)\n+{\n+\tif (outputCfgs.size() != 1) {\n+\t\tLOG(Converter, Error)\n+\t\t\t<< \"Unsupported number of output streams: \"\n+\t\t\t<< outputCfgs.size();\n+\t\treturn -EINVAL;\n+\t}\n+\n+\treturn isp_->invokeMethod(&SwConverter::Isp::configure,\n+\t\t\t\t ConnectionTypeBlocking, inputCfg, outputCfgs[0]);\n+}\n+\n+SwConverter::Isp::Isp(SwConverter *converter)\n+\t: converter_(converter)\n+{\n+\tmoveToThread(&thread_);\n+\tthread_.start();\n+}\n+\n+SwConverter::Isp::~Isp()\n+{\n+\tthread_.exit();\n+\tthread_.wait();\n+}\n+\n+int SwConverter::Isp::configure(const StreamConfiguration &inputCfg,\n+\t\t\t\tconst StreamConfiguration &outputCfg)\n+{\n+\tBayerFormat bayerFormat =\n+\t\tBayerFormat::fromPixelFormat(inputCfg.pixelFormat);\n+\twidth_ = inputCfg.size.width;\n+\theight_ = inputCfg.size.height;\n+\tstride_ = inputCfg.stride;\n+\n+\tif (bayerFormat.bitDepth != 10 ||\n+\t bayerFormat.packing != BayerFormat::Packing::CSI2 ||\n+\t width_ < 2 || height_ < 2) {\n+\t\tLOG(Converter, Error) << \"Input format \"\n+\t\t\t\t << inputCfg.size << \"-\"\n+\t\t\t\t << inputCfg.pixelFormat\n+\t\t\t\t << \"not supported\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tswitch (bayerFormat.order) {\n+\tcase BayerFormat::BGGR:\n+\t\tred_shift_ = Point(0, 0);\n+\t\tbreak;\n+\tcase BayerFormat::GBRG:\n+\t\tred_shift_ = Point(1, 0);\n+\t\tbreak;\n+\tcase BayerFormat::GRBG:\n+\t\tred_shift_ = Point(0, 1);\n+\t\tbreak;\n+\tcase BayerFormat::RGGB:\n+\tdefault:\n+\t\tred_shift_ = Point(1, 1);\n+\t\tbreak;\n+\t}\n+\n+\tif (outputCfg.size.width != width_ - 2 ||\n+\t outputCfg.size.height != height_ - 2 ||\n+\t outputCfg.stride != (width_ - 2) * 3 ||\n+\t outputCfg.pixelFormat != formats::RGB888) {\n+\t\tLOG(Converter, Error)\n+\t\t\t<< \"Output format not supported\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tLOG(Converter, Info) << \"SwConverter configuration: \"\n+\t\t\t << inputCfg.size << \"-\" << inputCfg.pixelFormat\n+\t\t\t << \" -> \"\n+\t\t\t << outputCfg.size << \"-\" << outputCfg.pixelFormat;\n+\n+\t/* set r/g/b gains to 1.0 until frame data collected */\n+\trNumerat_ = rDenomin_ = 1;\n+\tbNumerat_ = bDenomin_ = 1;\n+\tgNumerat_ = gDenomin_ = 1;\n+\n+\treturn 0;\n+}\n+\n+int SwConverter::exportBuffers(unsigned int output, unsigned int count,\n+\t\t\t std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n+{\n+\t/* single output for now */\n+\tif (output >= 1)\n+\t\treturn -EINVAL;\n+\n+\treturn isp_->invokeMethod(&SwConverter::Isp::exportBuffers,\n+\t\t\t\t ConnectionTypeBlocking, count, buffers);\n+}\n+\n+int SwConverter::Isp::exportBuffers(unsigned int count,\n+\t\t\t\t std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n+{\n+\t/* V4L2_PIX_FMT_BGR24 aka 'BGR3' for output: */\n+\tunsigned int bufSize = (height_ - 2) * (width_ - 2) * 3;\n+\n+\tfor (unsigned int i = 0; i < count; i++) {\n+\t\tstd::string name = \"frame-\" + std::to_string(i);\n+\n+\t\tconst int ispFd = memfd_create(name.c_str(), 0);\n+\t\tint ret = ftruncate(ispFd, bufSize);\n+\t\tif (ret < 0) {\n+\t\t\tLOG(Converter, Error) << \"ftruncate() for memfd failed \"\n+\t\t\t\t\t << strerror(-ret);\n+\t\t\treturn ret;\n+\t\t}\n+\n+\t\tFrameBuffer::Plane outPlane;\n+\t\toutPlane.fd = SharedFD(std::move(ispFd));\n+\t\toutPlane.offset = 0;\n+\t\toutPlane.length = bufSize;\n+\n+\t\tstd::vector<FrameBuffer::Plane> planes{ outPlane };\n+\t\tbuffers->emplace_back(std::make_unique<FrameBuffer>(std::move(planes)));\n+\t}\n+\n+\treturn count;\n+}\n+\n+int SwConverter::Isp::start()\n+{\n+\treturn 0;\n+}\n+\n+void SwConverter::Isp::stop()\n+{\n+}\n+\n+void SwConverter::Isp::waitForIdle()\n+{\n+\tMutexLocker locker(idleMutex_);\n+\n+\tidleCV_.wait(locker, [&]() LIBCAMERA_TSA_REQUIRES(idleMutex_) {\n+\t\t\t return idle_;\n+\t\t });\n+}\n+\n+int SwConverter::start()\n+{\n+\treturn isp_->invokeMethod(&SwConverter::Isp::start,\n+\t\t\t\t ConnectionTypeBlocking);\n+}\n+\n+void SwConverter::stop()\n+{\n+\tisp_->invokeMethod(&SwConverter::Isp::stop,\n+\t\t\t ConnectionTypeBlocking);\n+\tisp_->invokeMethod(&SwConverter::Isp::waitForIdle,\n+\t\t\t ConnectionTypeDirect);\n+}\n+\n+int SwConverter::queueBuffers(FrameBuffer *input,\n+\t\t\t const std::map<unsigned int, FrameBuffer *> &outputs)\n+{\n+\tunsigned int mask = 0;\n+\n+\t/*\n+\t * Validate the outputs as a sanity check: at least one output is\n+\t * required, all outputs must reference a valid stream and no two\n+\t * outputs can reference the same stream.\n+\t */\n+\tif (outputs.empty())\n+\t\treturn -EINVAL;\n+\n+\tfor (auto [index, buffer] : outputs) {\n+\t\tif (!buffer)\n+\t\t\treturn -EINVAL;\n+\t\tif (index >= 1) /* only single stream atm */\n+\t\t\treturn -EINVAL;\n+\t\tif (mask & (1 << index))\n+\t\t\treturn -EINVAL;\n+\n+\t\tmask |= 1 << index;\n+\t}\n+\n+\tprocess(input, outputs.at(0));\n+\n+\treturn 0;\n+}\n+\n+void SwConverter::process(FrameBuffer *input, FrameBuffer *output)\n+{\n+\tisp_->invokeMethod(&SwConverter::Isp::process,\n+\t\t\t ConnectionTypeQueued, input, output);\n+}\n+\n+void SwConverter::Isp::process(FrameBuffer *input, FrameBuffer *output)\n+{\n+\t{\n+\t\tMutexLocker locker(idleMutex_);\n+\t\tidle_ = false;\n+\t}\n+\n+\t/* Copy metadata from the input buffer */\n+\tFrameMetadata &metadata = output->_d()->metadata();\n+\tmetadata.status = input->metadata().status;\n+\tmetadata.sequence = input->metadata().sequence;\n+\tmetadata.timestamp = input->metadata().timestamp;\n+\n+\tMappedFrameBuffer in(input, MappedFrameBuffer::MapFlag::Read);\n+\tMappedFrameBuffer out(output, MappedFrameBuffer::MapFlag::Write);\n+\tif (!in.isValid() || !out.isValid()) {\n+\t\tLOG(Converter, Error) << \"mmap-ing buffer(s) failed\";\n+\t\tmetadata.status = FrameMetadata::FrameError;\n+\t\tconverter_->outputBufferReady.emit(output);\n+\t\tconverter_->inputBufferReady.emit(input);\n+\t\treturn;\n+\t}\n+\n+\tdebayer(out.planes()[0].data(), in.planes()[0].data());\n+\tmetadata.planes()[0].bytesused = out.planes()[0].size();\n+\n+\tconverter_->outputBufferReady.emit(output);\n+\tconverter_->inputBufferReady.emit(input);\n+\n+\t{\n+\t\tMutexLocker locker(idleMutex_);\n+\t\tidle_ = true;\n+\t}\n+\tidleCV_.notify_all();\n+}\n+\n+void SwConverter::Isp::debayer(uint8_t *dst, const uint8_t *src)\n+{\n+\t/* RAW10P input format is assumed */\n+\n+\t/* output buffer is in BGR24 format and is of (width-2)*(height-2) */\n+\n+\tint w_out = width_ - 2;\n+\tint h_out = height_ - 2;\n+\n+\tunsigned long sumR = 0;\n+\tunsigned long sumB = 0;\n+\tunsigned long sumG = 0;\n+\n+\tfor (int y = 0; y < h_out; y++) {\n+\t\tconst uint8_t *pin_base = src + (y + 1) * stride_;\n+\t\tuint8_t *pout = dst + y * w_out * 3;\n+\t\tint phase_y = (y + red_shift_.y) % 2;\n+\n+\t\tfor (int x = 0; x < w_out; x++) {\n+\t\t\tint phase_x = (x + red_shift_.x) % 2;\n+\t\t\tint phase = 2 * phase_y + phase_x;\n+\n+\t\t\t/* x part of the offset in the input buffer: */\n+\t\t\tint x_m1 = x + x / 4;\t\t/* offset for (x-1) */\n+\t\t\tint x_0 = x + 1 + (x + 1) / 4;\t/* offset for x */\n+\t\t\tint x_p1 = x + 2 + (x + 2) / 4;\t/* offset for (x+1) */\n+\t\t\t/* the colour component value to write to the output */\n+\t\t\tunsigned val;\n+\n+\t\t\tswitch (phase) {\n+\t\t\tcase 0: /* at R pixel */\n+\t\t\t\t/* blue: ((-1,-1)+(1,-1)+(-1,1)+(1,1)) / 4 */\n+\t\t\t\tval = ( *(pin_base + x_m1 - stride_)\n+\t\t\t\t\t+ *(pin_base + x_p1 - stride_)\n+\t\t\t\t\t+ *(pin_base + x_m1 + stride_)\n+\t\t\t\t\t+ *(pin_base + x_p1 + stride_) ) >> 2;\n+\t\t\t\tval = val * bNumerat_ / bDenomin_;\n+\t\t\t\t*pout++ = (uint8_t)std::min(val, 0xffU);\n+\t\t\t\t/* green: ((0,-1)+(-1,0)+(1,0)+(0,1)) / 4 */\n+\t\t\t\tval = ( *(pin_base + x_0 - stride_)\n+\t\t\t\t\t+ *(pin_base + x_p1)\n+\t\t\t\t\t+ *(pin_base + x_m1)\n+\t\t\t\t\t+ *(pin_base + x_0 + stride_) ) >> 2;\n+\t\t\t\tval = val * gNumerat_ / gDenomin_;\n+\t\t\t\t*pout++ = (uint8_t)std::min(val, 0xffU);\n+\t\t\t\t/* red: (0,0) */\n+\t\t\t\tval = *(pin_base + x_0);\n+\t\t\t\tsumR += val;\n+\t\t\t\tval = val * rNumerat_ / rDenomin_;\n+\t\t\t\t*pout++ = (uint8_t)std::min(val, 0xffU);\n+\t\t\t\tbreak;\n+\t\t\tcase 1: /* at Gr pixel */\n+\t\t\t\t/* blue: ((0,-1) + (0,1)) / 2 */\n+\t\t\t\tval = ( *(pin_base + x_0 - stride_)\n+\t\t\t\t\t+ *(pin_base + x_0 + stride_) ) >> 1;\n+\t\t\t\tval = val * bNumerat_ / bDenomin_;\n+\t\t\t\t*pout++ = (uint8_t)std::min(val, 0xffU);\n+\t\t\t\t/* green: (0,0) */\n+\t\t\t\tval = *(pin_base + x_0);\n+\t\t\t\tsumG += val;\n+\t\t\t\tval = val * gNumerat_ / gDenomin_;\n+\t\t\t\t*pout++ = (uint8_t)std::min(val, 0xffU);\n+\t\t\t\t/* red: ((-1,0) + (1,0)) / 2 */\n+\t\t\t\tval = ( *(pin_base + x_m1)\n+\t\t\t\t\t+ *(pin_base + x_p1) ) >> 1;\n+\t\t\t\tval = val * rNumerat_ / rDenomin_;\n+\t\t\t\t*pout++ = (uint8_t)std::min(val, 0xffU);\n+\t\t\t\tbreak;\n+\t\t\tcase 2: /* at Gb pixel */\n+\t\t\t\t/* blue: ((-1,0) + (1,0)) / 2 */\n+\t\t\t\tval = ( *(pin_base + x_m1)\n+\t\t\t\t\t+ *(pin_base + x_p1) ) >> 1;\n+\t\t\t\tval = val * bNumerat_ / bDenomin_;\n+\t\t\t\t*pout++ = (uint8_t)std::min(val, 0xffU);\n+\t\t\t\t/* green: (0,0) */\n+\t\t\t\tval = *(pin_base + x_0);\n+\t\t\t\tsumG += val;\n+\t\t\t\tval = val * gNumerat_ / gDenomin_;\n+\t\t\t\t*pout++ = (uint8_t)std::min(val, 0xffU);\n+\t\t\t\t/* red: ((0,-1) + (0,1)) / 2 */\n+\t\t\t\tval = ( *(pin_base + x_0 - stride_)\n+\t\t\t\t\t+ *(pin_base + x_0 + stride_) ) >> 1;\n+\t\t\t\tval = val * rNumerat_ / rDenomin_;\n+\t\t\t\t*pout++ = (uint8_t)std::min(val, 0xffU);\n+\t\t\t\tbreak;\n+\t\t\tdefault: /* at B pixel */\n+\t\t\t\t/* blue: (0,0) */\n+\t\t\t\tval = *(pin_base + x_0);\n+\t\t\t\tsumB += val;\n+\t\t\t\tval = val * bNumerat_ / bDenomin_;\n+\t\t\t\t*pout++ = (uint8_t)std::min(val, 0xffU);\n+\t\t\t\t/* green: ((0,-1)+(-1,0)+(1,0)+(0,1)) / 4 */\n+\t\t\t\tval = ( *(pin_base + x_0 - stride_)\n+\t\t\t\t\t+ *(pin_base + x_p1)\n+\t\t\t\t\t+ *(pin_base + x_m1)\n+\t\t\t\t\t+ *(pin_base + x_0 + stride_) ) >> 2;\n+\t\t\t\tval = val * gNumerat_ / gDenomin_;\n+\t\t\t\t*pout++ = (uint8_t)std::min(val, 0xffU);\n+\t\t\t\t/* red: ((-1,-1)+(1,-1)+(-1,1)+(1,1)) / 4 */\n+\t\t\t\tval = ( *(pin_base + x_m1 - stride_)\n+\t\t\t\t\t+ *(pin_base + x_p1 - stride_)\n+\t\t\t\t\t+ *(pin_base + x_m1 + stride_)\n+\t\t\t\t\t+ *(pin_base + x_p1 + stride_) ) >> 2;\n+\t\t\t\tval = val * rNumerat_ / rDenomin_;\n+\t\t\t\t*pout++ = (uint8_t)std::min(val, 0xffU);\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t/* calculate red and blue gains for simple AWB */\n+\tLOG(Converter, Debug) << \"sumR = \" << sumR\n+\t\t\t << \", sumB = \" << sumB << \", sumG = \" << sumG;\n+\n+\tsumG /= 2; /* the number of G pixels is twice as big vs R and B ones */\n+\n+\t/* normalize red, blue, and green sums to fit into 22-bit value */\n+\tunsigned long fRed = sumR / 0x400000;\n+\tunsigned long fBlue = sumB / 0x400000;\n+\tunsigned long fGreen = sumG / 0x400000;\n+\tunsigned long fNorm = std::max({ 1UL, fRed, fBlue, fGreen });\n+\tsumR /= fNorm;\n+\tsumB /= fNorm;\n+\tsumG /= fNorm;\n+\n+\tLOG(Converter, Debug) << \"fNorm = \" << fNorm;\n+\tLOG(Converter, Debug) << \"Normalized: sumR = \" << sumR\n+\t\t\t << \", sumB= \" << sumB << \", sumG = \" << sumG;\n+\n+\t/* make sure red/blue gains never exceed approximately 256 */\n+\tunsigned long minDenom;\n+\trNumerat_ = (sumR + sumB + sumG) / 3;\n+\tminDenom = rNumerat_ / 0x100;\n+\trDenomin_ = std::max(minDenom, sumR);\n+\tbNumerat_ = rNumerat_;\n+\tbDenomin_ = std::max(minDenom, sumB);\n+\tgNumerat_ = rNumerat_;\n+\tgDenomin_ = std::max(minDenom, sumG);\n+\n+\tLOG(Converter, Debug) << \"rGain = [ \"\n+\t\t\t << rNumerat_ << \" / \" << rDenomin_\n+\t\t\t << \" ], bGain = [ \" << bNumerat_ << \" / \" << bDenomin_\n+\t\t\t << \" ], gGain = [ \" << gNumerat_ << \" / \" << gDenomin_\n+\t\t\t << \" (minDenom = \" << minDenom << \")\";\n+}\n+\n+static std::initializer_list<std::string> compatibles = {};\n+\n+REGISTER_CONVERTER(\"linaro-sw-converter\", SwConverter, compatibles)\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/converter/meson.build b/src/libcamera/converter/meson.build\nindex 2aa72fe4..1f1e0ea4 100644\n--- a/src/libcamera/converter/meson.build\n+++ b/src/libcamera/converter/meson.build\n@@ -1,5 +1,6 @@\n # SPDX-License-Identifier: CC0-1.0\n \n libcamera_sources += files([\n- 'converter_v4l2_m2m.cpp'\n+ 'converter_softw.cpp',\n+ 'converter_v4l2_m2m.cpp',\n ])\n", "prefixes": [ "libcamera-devel", "RFC", "v3", "4/5" ] }