{"id":18981,"url":"https://patchwork.libcamera.org/api/patches/18981/?format=json","web_url":"https://patchwork.libcamera.org/patch/18981/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20230910175027.23384-3-andrey.konovalov@linaro.org>","date":"2023-09-10T17:50:26","name":"[libcamera-devel,RFC,2/3] libcamera: converter: add software converter","commit_ref":null,"pull_url":null,"state":"superseded","archived":false,"hash":"e1df272fce7dbeec8501c7554d175aa0dd60dfd9","submitter":{"id":25,"url":"https://patchwork.libcamera.org/api/people/25/?format=json","name":"Andrey Konovalov","email":"andrey.konovalov@linaro.org"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/18981/mbox/","series":[{"id":4014,"url":"https://patchwork.libcamera.org/api/series/4014/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=4014","date":"2023-09-10T17:50:24","name":"libcamera: converter: generalize Converter to remove MediaDevice dependency","version":1,"mbox":"https://patchwork.libcamera.org/series/4014/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/18981/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/18981/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 2CFE0C3260\n\tfor <parsemail@patchwork.libcamera.org>;\n\tSun, 10 Sep 2023 17:52:12 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 5E97B628F1;\n\tSun, 10 Sep 2023 19:52:11 +0200 (CEST)","from mail-ej1-x633.google.com (mail-ej1-x633.google.com\n\t[IPv6:2a00:1450:4864:20::633])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 8B792628E7\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSun, 10 Sep 2023 19:52:10 +0200 (CEST)","by mail-ej1-x633.google.com with SMTP id\n\ta640c23a62f3a-9a63b2793ecso461580966b.2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSun, 10 Sep 2023 10:52:10 -0700 (PDT)","from Lat-5310.. ([87.116.166.126]) by smtp.gmail.com with ESMTPSA\n\tid\n\tkd27-20020a17090798db00b009a57d30df89sm4090089ejc.132.2023.09.10.10.52.09\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tSun, 10 Sep 2023 10:52:09 -0700 (PDT)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1694368331;\n\tbh=l7PfTNOR6AaHQ3ZDSpXlVuPFfR+LlKy+UHMxbQHeioI=;\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=fukOmB+lB/LAENYO5Qd3QR/L5h8i0xsLaXa5d/A6kk22fpRGLmrclh62nwZMDdvaJ\n\tM4OFGY8z/jc3BHCh+hqQbFuaTo7K2kPNfgm+pF5VZSpBlcy1XndPjlkZCo7qNGC7o5\n\tKKpMw2o+umGJ/nA6uOUlJ9/mPTjFD2Np6BPwkREeecVG7daXNC5RA4jaFiny90lnXM\n\tGl1KAO0zfXztfSgy4bKHb2vREok1/xEACXcnqXhAHvPhM1DdMlmto/DAmrtAj7rENW\n\tz7W9Tkrf+ORTkvL9/DxfOXfm3njN/wrfYZvDkuo6/opNuOoaHPlt8he68hTtYDA1iO\n\thaDUfEUDBFRtA==","v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=linaro.org; s=google; t=1694368330; x=1694973130;\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=xvWNpNkJSK8RV8XrpOdADEp8LJKAmGEfU5DdngUjYV0=;\n\tb=GsMLlNJEiyBAjCmAlKuK3MyHoBPOCEy725Anu+1cWCUu2vrL+C9bXFecm7rdGgOWxg\n\tDohSZuGh7CYqd+wFbEo+xonRmoKtWmvGsqwk3QR/hNhPRcsmnab8Scl9Ht9EOOdguMgZ\n\tPSHV3i59RS6tiTyUHik9WKOx+uqWEMLgSOyEc8cw1ysJkbiNrRcX+jjUBaQs8SdCMtjg\n\tdhA780Olol/rB+haqXna2bPPpvhwRfiRM1AsqNc3dSCdWyUnApcFKGQJtT8DrtpiKm5M\n\ti+Au9zEGfrxvSW5JGNHUroflkMrg4IXrT7vUqiWzBdfEg28GrqAFg8SootcuDyFWxpfw\n\tjopA=="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key; \n\tunprotected) header.d=linaro.org\n\theader.i=@linaro.org header.b=\"GsMLlNJE\"; \n\tdkim-atps=neutral","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1694368330; x=1694973130;\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=xvWNpNkJSK8RV8XrpOdADEp8LJKAmGEfU5DdngUjYV0=;\n\tb=mZ0VKgBi5HBQsMCNnM5L+fJVRvy5RMNdrTFgwegmmMRTWVxYuhq1b+NIBKsr3Gm6ta\n\tIJxm1LxDDSc1v6YK81u4yZIvtRCkKBWvr+QC53yOfI9Bd2z1GcOxfJ3XxP829JsnOsVu\n\tGU1t4UYXwsubzl08itvwTl2gdus86E/rnAAa7ZcU4Jbipnat5Cd3inXbAGjrFXe1nkz3\n\t0o7TVfZklEsW/J3ZYTuYvLX64dex16Rc2LxjE9xsQBRlKi/30HcS2syyygb8+ZvyjnqZ\n\tIc6GehtNgD0GvL+QSrSpCnNPslWA1bvcb187QETplWJN4X2DFzd0a1cqEBeIxtertwAR\n\tSUXw==","X-Gm-Message-State":"AOJu0Yz0OOqz64m2XxWiL4CGhzJK8o70NERG7oExE3Nc6g5z1nbr1GVA\n\tIngJ0I1bHPkFBvdj/gAzPeQMaNNKFc3CggBzjCs=","X-Google-Smtp-Source":"AGHT+IGoGtTq8Ale+IbJWOJAQbxJ70dyNhzkoMMSbNOup/PI5oaNHgPRbhn7ENaxskvD0DzUAb9zOA==","X-Received":"by 2002:a17:906:10dc:b0:9a5:852f:10ac with SMTP id\n\tv28-20020a17090610dc00b009a5852f10acmr6853976ejv.53.1694368330109; \n\tSun, 10 Sep 2023 10:52:10 -0700 (PDT)","To":"libcamera-devel@lists.libcamera.org","Date":"Sun, 10 Sep 2023 20:50:26 +0300","Message-Id":"<20230910175027.23384-3-andrey.konovalov@linaro.org>","X-Mailer":"git-send-email 2.34.1","In-Reply-To":"<20230910175027.23384-1-andrey.konovalov@linaro.org>","References":"<20230910175027.23384-1-andrey.konovalov@linaro.org>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","Subject":"[libcamera-devel] [RFC PATCH 2/3] 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      |  88 ++++\n .../libcamera/internal/converter/meson.build  |   1 +\n src/libcamera/converter/converter_softw.cpp   | 412 ++++++++++++++++++\n src/libcamera/converter/meson.build           |   3 +-\n 4 files changed, 503 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..aab1a650\n--- /dev/null\n+++ b/include/libcamera/internal/converter/converter_softw.h\n@@ -0,0 +1,88 @@\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 <map>\n+#include <tuple>\n+#include <vector>\n+\n+#include <libcamera/base/log.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 Size;\n+class SizeRange;\n+struct StreamConfiguration;\n+\n+class SwConverter : public Converter\n+{\n+public:\n+\tSwConverter() : Converter() {}\n+\n+\tint loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; }\n+\tbool isValid() const { return true; }\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\t: converter_(converter) {}\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+\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\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..c66400b0\n--- /dev/null\n+++ b/src/libcamera/converter/converter_softw.cpp\n@@ -0,0 +1,412 @@\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 <tuple>\n+#include <unistd.h>\n+#include <vector>\n+\n+#include <libcamera/base/log.h>\n+#include <libcamera/base/signal.h>\n+#include <libcamera/base/thread.h>\n+\n+#include <libcamera/formats.h>\n+#include <libcamera/geometry.h>\n+#include <libcamera/pixel_format.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+std::vector<PixelFormat> SwConverter::formats(PixelFormat input)\n+{\n+\tstd::vector<PixelFormat> pixelFormats;\n+\tBayerFormat inputFormat = BayerFormat::fromPixelFormat(input);\n+\n+\t/* Only RAW10P is currently supported */\n+\tif (inputFormat.bitDepth == 10 && inputFormat.packing == BayerFormat::Packing::CSI2)\n+\t\tpixelFormats.push_back(formats::RGB888);\n+\n+\tif (pixelFormats.empty())\n+\t\tLOG(Converter, Info)\n+\t\t\t<< \"Unsupported input format \" << input.toString();\n+\n+\treturn pixelFormats;\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\tint stride = size.width * 3;\n+\t\treturn std::make_tuple(stride, stride * (size.height));\n+\t}\n+\n+\treturn std::make_tuple(0, 0);\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+\tisp_ = std::make_unique<SwConverter::Isp>(this);\n+\n+\treturn isp_->configure(inputCfg, outputCfgs[0]);\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_->exportBuffers(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\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+\tmoveToThread(&thread_);\n+\tthread_.start();\n+\treturn 0;\n+}\n+\n+void SwConverter::Isp::stop()\n+{\n+\tthread_.exit();\n+\tthread_.wait();\n+}\n+\n+int SwConverter::start()\n+{\n+\treturn isp_->start();\n+}\n+\n+void SwConverter::stop()\n+{\n+\treturn isp_->stop();\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/* 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+\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+} /* 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","2/3"]}