{"id":19097,"url":"https://patchwork.libcamera.org/api/patches/19097/?format=json","web_url":"https://patchwork.libcamera.org/patch/19097/","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":"<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/people/25/?format=json","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/series/4043/?format=json","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"]}