From patchwork Mon Dec 4 00:10:09 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrey Konovalov X-Patchwork-Id: 19265 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 7F553C3226 for ; Mon, 4 Dec 2023 00:10:46 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 26DBC629D6; Mon, 4 Dec 2023 01:10:46 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1701648646; bh=D5fo1V8p+jrOhg6enCbL+FVy5lwayRKFVeW+Z0Sc0Vw=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc: From; b=l1aghxnhB8X8ffzi6Y0iyIAONQDxsOja02kNr6W9zBcxze/NBLDTN/zbK1UyqRD62 fL+5beWmfapt+loHT1BNVCPXqhiPsyX5RCPrVuA9CTtBW0HIK9pm+31fEL/IIpHsre 4MVCN5dAS+PL0hLIc8YfOlJnVcurHF8DaBrY0nTG7NHyeNgt4+dhfAIzquNNSsfs3s 1PcKT9ZgQrI5Y880tZ72c6q/ihWflwR6TKkWQmtBpzilNlPTVdztgEMJv/5ArSYpXS 8m+alFGtXwF18rr5Zwo4XhtxPBevM4D9XmpUtettEktaWthC4Zpdq1y8V9xRRwAzSD pQpYchqzogncQ== Received: from mail-wm1-x333.google.com (mail-wm1-x333.google.com [IPv6:2a00:1450:4864:20::333]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 34EBB629D5 for ; Mon, 4 Dec 2023 01:10:45 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=linaro.org header.i=@linaro.org header.b="Db0P4occ"; dkim-atps=neutral Received: by mail-wm1-x333.google.com with SMTP id 5b1f17b1804b1-40c09fcfa9fso6866105e9.2 for ; Sun, 03 Dec 2023 16:10:45 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1701648645; x=1702253445; darn=lists.libcamera.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=Gh+koOeAw51LTI6Xbzy0kR5L2rjRgitnCI0TM1zcDoc=; b=Db0P4occALNC46LbFoUsC9dXHMvF79u5k1R18W3AuMKo+ASRueNk2hDCk7CfzDnbBD p8C3krvvbIAEsrbjlPTrK1Ge/XW0L6+03xana4k6Zp1jfVKlLU8rTWIkiJJTXPtB7WrN n4YqPQqIi2KdrFbbBMDLHjs/cEkUlDj/2f8tgiLu4AWgQwksWCSxRpbiQuww02VXfkJP /Ggbw5IYPp1QyvwJGCi4n1eSNTlAsnaKQkbHyroQzRXbvUfsWcWNhAXONlClkYgpEuSq LoSAygNSQlkIG8Zd5pSXLAioc7ID3D6+2At81hkOI0Oytw7csSLxiZR/lsaLnnrUHEdI nMcQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1701648645; x=1702253445; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Gh+koOeAw51LTI6Xbzy0kR5L2rjRgitnCI0TM1zcDoc=; b=EZleO5SmRlm/1650XskN41PHo34UiDq/qtm5Rsw6BMMvBfLpy5ZHVM5r0XeBbxmzH/ o+7/w5yhsdq6b8gd1zjhkbftxllwC0E6JhOeCjIkMlq9BswC1FZG2izEhXU9DH9A5xlQ cCOrwAUL43tVcNvtGynDV1yAeXpj9/7+v0StQyh36ipRwMN8bAXCKy9fMVkFEL/LTw8X 4MAaPdxRBrlzB1Aklphf4/q3Jywy9u4gLqt0CgvVhRFZ7rDXVUhLQwWRr0lS6dXcKkzL UXCiCD6tKjOILQ5oi5OveWRv7f1xpsO9/8ckQU8ntBqmdZgY541+h2rgAej9+JEwuxwk CRQg== X-Gm-Message-State: AOJu0YxWZeuFWXw3AodJNMGgeWAdJ0RL/e5SMHZSXuoyBOKrrQEf1akD sSPbAz69GdFKFK4ZZEFjzdNCUJF09kcfJgupfeQ= X-Google-Smtp-Source: AGHT+IGpcZBI3Dp7n/wOZVnsuJQh9IAQIZAYSNqzKHeBZr2NtXA2FBK4JBVm+aT0jPOqmMGyxd0Iww== X-Received: by 2002:a5d:58e1:0:b0:333:2fd2:766d with SMTP id f1-20020a5d58e1000000b003332fd2766dmr1624495wrd.94.1701648644838; Sun, 03 Dec 2023 16:10:44 -0800 (PST) Received: from Lat-5310.. ([87.116.165.212]) by smtp.gmail.com with ESMTPSA id u29-20020adfa19d000000b003332db7d91dsm8835997wru.39.2023.12.03.16.10.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 03 Dec 2023 16:10:44 -0800 (PST) To: libcamera-devel@lists.libcamera.org Date: Mon, 4 Dec 2023 03:10:09 +0300 Message-Id: <20231204001013.404720-4-andrey.konovalov@linaro.org> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20231204001013.404720-1-andrey.konovalov@linaro.org> References: <20231204001013.404720-1-andrey.konovalov@linaro.org> MIME-Version: 1.0 Subject: [libcamera-devel] [RFC PATCH 3/7] libcamera: software_isp: add SwIspLinaro implementation of SoftwareIsp X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Andrey Konovalov via libcamera-devel From: Andrey Konovalov Reply-To: Andrey Konovalov Cc: mripard@redhat.com, g.martti@gmail.com, t.langendam@gmail.com, srinivas.kandagatla@linaro.org, bryan.odonoghue@linaro.org, admin@dennisbonke.com Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Signed-off-by: Andrey Konovalov --- include/libcamera/internal/meson.build | 1 + .../internal/software_isp/meson.build | 6 + .../internal/software_isp/statistics-linaro.h | 17 + .../internal/software_isp/swisp_linaro.h | 109 ++++ src/libcamera/meson.build | 1 + src/libcamera/software_isp/meson.build | 5 + src/libcamera/software_isp/swisp_linaro.cpp | 539 ++++++++++++++++++ 7 files changed, 678 insertions(+) create mode 100644 include/libcamera/internal/software_isp/meson.build create mode 100644 include/libcamera/internal/software_isp/statistics-linaro.h create mode 100644 include/libcamera/internal/software_isp/swisp_linaro.h create mode 100644 src/libcamera/software_isp/meson.build create mode 100644 src/libcamera/software_isp/swisp_linaro.cpp diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build index b780777c..eeae801c 100644 --- a/include/libcamera/internal/meson.build +++ b/include/libcamera/internal/meson.build @@ -50,3 +50,4 @@ libcamera_internal_headers = files([ ]) subdir('converter') +subdir('software_isp') diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build new file mode 100644 index 00000000..9f84f00e --- /dev/null +++ b/include/libcamera/internal/software_isp/meson.build @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: CC0-1.0 + +libcamera_internal_headers += files([ + 'statistics-linaro.h', + 'swisp_linaro.h', +]) diff --git a/include/libcamera/internal/software_isp/statistics-linaro.h b/include/libcamera/internal/software_isp/statistics-linaro.h new file mode 100644 index 00000000..20c64e44 --- /dev/null +++ b/include/libcamera/internal/software_isp/statistics-linaro.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Linaro Ltd + * + * statistics.h - Statistics data format used by the software ISP + */ + +#pragma once + +namespace libcamera { + +struct SwIspStats { + float bright_ratio; + float too_bright_ratio; +}; + +} /* namespace libcamera */ diff --git a/include/libcamera/internal/software_isp/swisp_linaro.h b/include/libcamera/internal/software_isp/swisp_linaro.h new file mode 100644 index 00000000..0d8a31a6 --- /dev/null +++ b/include/libcamera/internal/software_isp/swisp_linaro.h @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Linaro Ltd + * + * swisp_linaro.h - software ISP implementation by Linaro + */ + +#pragma once + +#include +#include + +#include + +#include "libcamera/internal/shared_mem_object.h" +#include "libcamera/internal/software_isp/statistics-linaro.h" +#include "libcamera/internal/software_isp.h" + +namespace libcamera { + +class SwIspLinaro : public SoftwareIsp +{ +public: + SwIspLinaro(const std::string &name); + ~SwIspLinaro() {} + + int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; } + bool isValid() const; + + std::vector formats(PixelFormat input); + SizeRange sizes(PixelFormat inputFormat, const Size &inputSize); + + std::tuple + strideAndFrameSize(const PixelFormat &outputFormat, const Size &size); + + int configure(const StreamConfiguration &inputCfg, + const std::vector> &outputCfgs); + int exportBuffers(unsigned int output, unsigned int count, + std::vector> *buffers); + + int start(); + void stop(); + + int queueBuffers(FrameBuffer *input, + const std::map &outputs); + + void process(FrameBuffer *input, FrameBuffer *output); + +private: + SharedMemObject sharedStats_; + + class IspWorker : public Object + { + public: + IspWorker(SwIspLinaro *swIsp); + + std::vector formats(PixelFormat input); + SizeRange sizes(PixelFormat inputFormat, const Size &inputSize); + unsigned int outStride(const PixelFormat &outputFormat, + const Size &outSize); + + int configure(const StreamConfiguration &inputCfg, + const StreamConfiguration &outputCfg); + unsigned int outBufferSize(); + void process(FrameBuffer *input, FrameBuffer *output); + + private: + SwIspLinaro *swIsp_; + + typedef void (SwIspLinaro::IspWorker::*debayerFn)(uint8_t *dst, const uint8_t *src); + typedef SizeRange (*outSizesFn)(const Size &inSize); + typedef unsigned int (*outStrideFn)(const Size &outSize); + struct debayerInfo { + PixelFormat outPixelFmt; + debayerFn debayer; + outSizesFn getOutSizes; + outStrideFn getOutStride; + }; + // TODO: use inputFormat+outputFormat as the map key + // to enable multiple output formats + // TODO: use BayerFormat instead of PixelFormat as inputFormat + std::map debayerInfos_; + int setDebayerInfo(PixelFormat format); + debayerInfo *debayerInfo_; + + /* CSI-2 packed 10-bit raw bayer format (all the 4 orders) */ + void debayerRaw10P(uint8_t *dst, const uint8_t *src); + static SizeRange outSizesRaw10P(const Size &inSize); + static unsigned int outStrideRaw10P(const Size &outSize); + + unsigned int width_; + unsigned int height_; + unsigned int stride_; + Point redShift_; + unsigned int outHeight_; + unsigned int outStride_; + + unsigned long rNumerat_, rDenomin_; /* red gain for AWB */ + unsigned long bNumerat_, bDenomin_; /* blue gain for AWB */ + unsigned long gNumerat_, gDenomin_; /* green gain for AWB */ + + SwIspStats stats_; + }; + + std::unique_ptr ispWorker_; + Thread ispWorkerThread_; +}; + +} /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index 9d26a87f..395235ed 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -70,6 +70,7 @@ subdir('converter') subdir('ipa') subdir('pipeline') subdir('proxy') +subdir('software_isp') null_dep = dependency('', required : false) diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build new file mode 100644 index 00000000..05eda181 --- /dev/null +++ b/src/libcamera/software_isp/meson.build @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: CC0-1.0 + +libcamera_sources += files([ + 'swisp_linaro.cpp', +]) diff --git a/src/libcamera/software_isp/swisp_linaro.cpp b/src/libcamera/software_isp/swisp_linaro.cpp new file mode 100644 index 00000000..79ceda4d --- /dev/null +++ b/src/libcamera/software_isp/swisp_linaro.cpp @@ -0,0 +1,539 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Linaro Ltd + * + * swisp_linaro.cpp - software ISP implementation by Linaro + */ + +#include "libcamera/internal/software_isp/swisp_linaro.h" + +#include +#include +#include + +#include +#include + +#include "libcamera/internal/bayer_format.h" +#include "libcamera/internal/framebuffer.h" +#include "libcamera/internal/mapped_framebuffer.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(SoftwareIsp) + +SwIspLinaro::SwIspLinaro(const std::string &name) + : SoftwareIsp(name) +{ + ispWorker_ = std::make_unique(this); + if (!ispWorker_) { + LOG(SoftwareIsp, Error) + << "Failed to create ISP worker"; + } else { + sharedStats_ = SharedMemObject("softIsp_stats"); + if (!sharedStats_.fd().isValid()) { + LOG(SoftwareIsp, Error) + << "Failed to create shared memory for statistics"; + ispWorker_.reset(); + } else { + ispWorker_->moveToThread(&ispWorkerThread_); + } + } +} + +bool SwIspLinaro::isValid() const +{ + return !!ispWorker_; +} + +void SwIspLinaro::IspWorker::debayerRaw10P(uint8_t *dst, const uint8_t *src) +{ + /* for brightness values in the 0 to 255 range: */ + static const unsigned int BRIGHT_LVL = 200U << 8; + static const unsigned int TOO_BRIGHT_LVL = 240U << 8; + + static const unsigned int RED_Y_MUL = 77; /* 0.30 * 256 */ + static const unsigned int GREEN_Y_MUL = 150; /* 0.59 * 256 */ + static const unsigned int BLUE_Y_MUL = 29; /* 0.11 * 256 */ + + int w_out = width_ - 2; + int h_out = height_ - 2; + + unsigned long sumR = 0; + unsigned long sumB = 0; + unsigned long sumG = 0; + + unsigned long bright_sum = 0; + unsigned long too_bright_sum = 0; + + for (int y = 0; y < h_out; y++) { + const uint8_t *pin_base = src + (y + 1) * stride_; + uint8_t *pout = dst + y * w_out * 3; + int phase_y = (y + redShift_.y) % 2; + + for (int x = 0; x < w_out; x++) { + int phase_x = (x + redShift_.x) % 2; + int phase = 2 * phase_y + phase_x; + + /* x part of the offset in the input buffer: */ + int x_m1 = x + x / 4; /* offset for (x-1) */ + int x_0 = x + 1 + (x + 1) / 4; /* offset for x */ + int x_p1 = x + 2 + (x + 2) / 4; /* offset for (x+1) */ + /* the colour component value to write to the output */ + unsigned val; + /* Y value times 256 */ + unsigned y_val; + + switch (phase) { + case 0: /* at R pixel */ + /* blue: ((-1,-1)+(1,-1)+(-1,1)+(1,1)) / 4 */ + val = ( *(pin_base + x_m1 - stride_) + + *(pin_base + x_p1 - stride_) + + *(pin_base + x_m1 + stride_) + + *(pin_base + x_p1 + stride_) ) >> 2; + y_val = BLUE_Y_MUL * val; + val = val * bNumerat_ / bDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + /* green: ((0,-1)+(-1,0)+(1,0)+(0,1)) / 4 */ + val = ( *(pin_base + x_0 - stride_) + + *(pin_base + x_p1) + + *(pin_base + x_m1) + + *(pin_base + x_0 + stride_) ) >> 2; + val = val * gNumerat_ / gDenomin_; + y_val += GREEN_Y_MUL * val; + *pout++ = (uint8_t)std::min(val, 0xffU); + /* red: (0,0) */ + val = *(pin_base + x_0); + sumR += val; + y_val += RED_Y_MUL * val; + if (y_val > BRIGHT_LVL) ++bright_sum; + if (y_val > TOO_BRIGHT_LVL) ++too_bright_sum; + val = val * rNumerat_ / rDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + break; + case 1: /* at Gr pixel */ + /* blue: ((0,-1) + (0,1)) / 2 */ + val = ( *(pin_base + x_0 - stride_) + + *(pin_base + x_0 + stride_) ) >> 1; + y_val = BLUE_Y_MUL * val; + val = val * bNumerat_ / bDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + /* green: (0,0) */ + val = *(pin_base + x_0); + sumG += val; + y_val += GREEN_Y_MUL * val; + val = val * gNumerat_ / gDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + /* red: ((-1,0) + (1,0)) / 2 */ + val = ( *(pin_base + x_m1) + + *(pin_base + x_p1) ) >> 1; + y_val += RED_Y_MUL * val; + if (y_val > BRIGHT_LVL) ++bright_sum; + if (y_val > TOO_BRIGHT_LVL) ++too_bright_sum; + val = val * rNumerat_ / rDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + break; + case 2: /* at Gb pixel */ + /* blue: ((-1,0) + (1,0)) / 2 */ + val = ( *(pin_base + x_m1) + + *(pin_base + x_p1) ) >> 1; + y_val = BLUE_Y_MUL * val; + val = val * bNumerat_ / bDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + /* green: (0,0) */ + val = *(pin_base + x_0); + sumG += val; + y_val += GREEN_Y_MUL * val; + val = val * gNumerat_ / gDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + /* red: ((0,-1) + (0,1)) / 2 */ + val = ( *(pin_base + x_0 - stride_) + + *(pin_base + x_0 + stride_) ) >> 1; + y_val += RED_Y_MUL * val; + if (y_val > BRIGHT_LVL) ++bright_sum; + if (y_val > TOO_BRIGHT_LVL) ++too_bright_sum; + val = val * rNumerat_ / rDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + break; + default: /* at B pixel */ + /* blue: (0,0) */ + val = *(pin_base + x_0); + sumB += val; + y_val = BLUE_Y_MUL * val; + val = val * bNumerat_ / bDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + /* green: ((0,-1)+(-1,0)+(1,0)+(0,1)) / 4 */ + val = ( *(pin_base + x_0 - stride_) + + *(pin_base + x_p1) + + *(pin_base + x_m1) + + *(pin_base + x_0 + stride_) ) >> 2; + y_val += GREEN_Y_MUL * val; + val = val * gNumerat_ / gDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + /* red: ((-1,-1)+(1,-1)+(-1,1)+(1,1)) / 4 */ + val = ( *(pin_base + x_m1 - stride_) + + *(pin_base + x_p1 - stride_) + + *(pin_base + x_m1 + stride_) + + *(pin_base + x_p1 + stride_) ) >> 2; + y_val += RED_Y_MUL * val; + if (y_val > BRIGHT_LVL) ++bright_sum; + if (y_val > TOO_BRIGHT_LVL) ++too_bright_sum; + val = val * rNumerat_ / rDenomin_; + *pout++ = (uint8_t)std::min(val, 0xffU); + } + } + } + + /* calculate the fractions of "bright" and "too bright" pixels */ + stats_.bright_ratio = (float)bright_sum / (h_out * w_out); + stats_.too_bright_ratio = (float)too_bright_sum / (h_out * w_out); + + /* calculate red and blue gains for simple AWB */ + LOG(SoftwareIsp, Debug) + << "sumR = " << sumR << ", sumB = " << sumB << ", sumG = " << sumG; + + sumG /= 2; /* the number of G pixels is twice as big vs R and B ones */ + + /* normalize red, blue, and green sums to fit into 22-bit value */ + unsigned long fRed = sumR / 0x400000; + unsigned long fBlue = sumB / 0x400000; + unsigned long fGreen = sumG / 0x400000; + unsigned long fNorm = std::max({ 1UL, fRed, fBlue, fGreen }); + sumR /= fNorm; + sumB /= fNorm; + sumG /= fNorm; + + LOG(SoftwareIsp, Debug) << "fNorm = " << fNorm; + LOG(SoftwareIsp, Debug) + << "Normalized: sumR = " << sumR + << ", sumB= " << sumB << ", sumG = " << sumG; + + /* make sure red/blue gains never exceed approximately 256 */ + unsigned long minDenom; + rNumerat_ = (sumR + sumB + sumG) / 3; + minDenom = rNumerat_ / 0x100; + rDenomin_ = std::max(minDenom, sumR); + bNumerat_ = rNumerat_; + bDenomin_ = std::max(minDenom, sumB); + gNumerat_ = rNumerat_; + gDenomin_ = std::max(minDenom, sumG); + + LOG(SoftwareIsp, Debug) + << "rGain = [ " << rNumerat_ << " / " << rDenomin_ + << " ], bGain = [ " << bNumerat_ << " / " << bDenomin_ + << " ], gGain = [ " << gNumerat_ << " / " << gDenomin_ + << " (minDenom = " << minDenom << ")"; +} + +SizeRange SwIspLinaro::IspWorker::outSizesRaw10P(const Size &inSize) +{ + if (inSize.width < 2 || inSize.height < 2) { + LOG(SoftwareIsp, Error) + << "Input format size too small: " << inSize.toString(); + return {}; + } + + return SizeRange(Size(inSize.width - 2, inSize.height - 2)); +} + +unsigned int SwIspLinaro::IspWorker::outStrideRaw10P(const Size &outSize) +{ + return outSize.width * 3; +} + +SwIspLinaro::IspWorker::IspWorker(SwIspLinaro *swIsp) + : swIsp_(swIsp) +{ + debayerInfos_[formats::SBGGR10_CSI2P] = { formats::RGB888, + &SwIspLinaro::IspWorker::debayerRaw10P, + &SwIspLinaro::IspWorker::outSizesRaw10P, + &SwIspLinaro::IspWorker::outStrideRaw10P }; + debayerInfos_[formats::SGBRG10_CSI2P] = { formats::RGB888, + &SwIspLinaro::IspWorker::debayerRaw10P, + &SwIspLinaro::IspWorker::outSizesRaw10P, + &SwIspLinaro::IspWorker::outStrideRaw10P }; + debayerInfos_[formats::SGRBG10_CSI2P] = { formats::RGB888, + &SwIspLinaro::IspWorker::debayerRaw10P, + &SwIspLinaro::IspWorker::outSizesRaw10P, + &SwIspLinaro::IspWorker::outStrideRaw10P }; + debayerInfos_[formats::SRGGB10_CSI2P] = { formats::RGB888, + &SwIspLinaro::IspWorker::debayerRaw10P, + &SwIspLinaro::IspWorker::outSizesRaw10P, + &SwIspLinaro::IspWorker::outStrideRaw10P }; +} + +int SwIspLinaro::IspWorker::setDebayerInfo(PixelFormat format) +{ + const auto it = debayerInfos_.find(format); + if (it == debayerInfos_.end()) + return -1; + + debayerInfo_ = &it->second; + return 0; +} + +std::vector SwIspLinaro::IspWorker::formats(PixelFormat input) +{ + std::vector pixelFormats; + + const auto it = debayerInfos_.find(input); + if (it == debayerInfos_.end()) + LOG(SoftwareIsp, Info) + << "Unsupported input format " << input.toString(); + else + pixelFormats.push_back(it->second.outPixelFmt); + + return pixelFormats; +} + +SizeRange SwIspLinaro::IspWorker::sizes(PixelFormat inputFormat, + const Size &inputSize) +{ + const auto it = debayerInfos_.find(inputFormat); + if (it == debayerInfos_.end()) { + LOG(SoftwareIsp, Info) + << "Unsupported input format " << inputFormat.toString(); + return {}; + } + + return (*it->second.getOutSizes)(inputSize); +} + +unsigned int SwIspLinaro::IspWorker::outStride(const PixelFormat &outputFormat, + const Size &outSize) +{ + /* + * Assuming that the output stride depends only on the outputFormat, + * we use the first debayerInfos_ entry with the matching output format + */ + for (auto it = debayerInfos_.begin(); it != debayerInfos_.end(); it++) { + if (it->second.outPixelFmt == outputFormat) + return (*it->second.getOutStride)(outSize); + } + + return 0; +} + +int SwIspLinaro::IspWorker::configure(const StreamConfiguration &inputCfg, + const StreamConfiguration &outputCfg) +{ + if (setDebayerInfo(inputCfg.pixelFormat) != 0) { + LOG(SoftwareIsp, Error) + << "Input format " << inputCfg.pixelFormat + << "not supported"; + return -EINVAL; + } + + /* check that: + * - output format is valid + * - output size matches the input size and is valid */ + SizeRange outSizeRange = (*debayerInfo_->getOutSizes)(inputCfg.size); + if (debayerInfo_->outPixelFmt != outputCfg.pixelFormat || + outputCfg.size.isNull() || !outSizeRange.contains(outputCfg.size) || + (*debayerInfo_->getOutStride)(outputCfg.size) != outputCfg.stride) { + LOG(SoftwareIsp, Error) + << "Invalid output format/size/stride: " + << "\n " << outputCfg.pixelFormat << " (" + << debayerInfo_->outPixelFmt << ")" + << "\n " << outputCfg.size << " (" + << outSizeRange << ")" + << "\n " << outputCfg.stride << " (" + << (*debayerInfo_->getOutStride)(outputCfg.size) << ")"; + return -EINVAL; + } + + width_ = inputCfg.size.width; + height_ = inputCfg.size.height; + stride_ = inputCfg.stride; + + BayerFormat bayerFormat = + BayerFormat::fromPixelFormat(inputCfg.pixelFormat); + switch (bayerFormat.order) { + case BayerFormat::BGGR: + redShift_ = Point(0, 0); + break; + case BayerFormat::GBRG: + redShift_ = Point(1, 0); + break; + case BayerFormat::GRBG: + redShift_ = Point(0, 1); + break; + case BayerFormat::RGGB: + default: + redShift_ = Point(1, 1); + break; + } + + outStride_ = outputCfg.stride; + outHeight_ = outputCfg.size.height; + + LOG(SoftwareIsp, Info) + << "SoftwareISP configuration: " + << inputCfg.size << "-" << inputCfg.pixelFormat << " -> " + << outputCfg.size << "-" << outputCfg.pixelFormat; + + /* set r/g/b gains to 1.0 until frame data collected */ + rNumerat_ = rDenomin_ = 1; + bNumerat_ = bDenomin_ = 1; + gNumerat_ = gDenomin_ = 1; + + return 0; +} + +/* May not be called before SwIspLinaro::IspWorker::configure() */ +unsigned int SwIspLinaro::IspWorker::outBufferSize() +{ + return outHeight_ * outStride_; +} + +std::vector SwIspLinaro::formats(PixelFormat inputFormat) +{ + ASSERT(ispWorker_ != nullptr); + + return ispWorker_->formats(inputFormat); +} + +SizeRange SwIspLinaro::sizes(PixelFormat inputFormat, const Size &inputSize) +{ + ASSERT(ispWorker_ != nullptr); + + return ispWorker_->sizes(inputFormat, inputSize); +} + +std::tuple +SwIspLinaro::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) +{ + ASSERT(ispWorker_ != nullptr); + + unsigned int stride = ispWorker_->outStride(outputFormat, size); + + return std::make_tuple(stride, stride * size.height); +} + +int SwIspLinaro::configure(const StreamConfiguration &inputCfg, + const std::vector> &outputCfgs) +{ + ASSERT(ispWorker_ != nullptr); + + if (outputCfgs.size() != 1) { + LOG(SoftwareIsp, Error) + << "Unsupported number of output streams: " + << outputCfgs.size(); + return -EINVAL; + } + + return ispWorker_->configure(inputCfg, outputCfgs[0]); +} + +int SwIspLinaro::exportBuffers(unsigned int output, unsigned int count, + std::vector> *buffers) +{ + ASSERT(ispWorker_ != nullptr); + + /* single output for now */ + if (output >= 1) + return -EINVAL; + + unsigned int bufSize = ispWorker_->outBufferSize(); + + /* TODO: allocate from dma_heap; memfd buffs aren't allowed in FrameBuffer */ + for (unsigned int i = 0; i < count; i++) { + std::string name = "frame-" + std::to_string(i); + + const int ispFd = memfd_create(name.c_str(), 0); + int ret = ftruncate(ispFd, bufSize); + if (ret < 0) { + LOG(SoftwareIsp, Error) + << "ftruncate() for memfd failed " + << strerror(-ret); + return ret; + } + + FrameBuffer::Plane outPlane; + outPlane.fd = SharedFD(std::move(ispFd)); + outPlane.offset = 0; + outPlane.length = bufSize; + + std::vector planes{ outPlane }; + buffers->emplace_back(std::make_unique(std::move(planes))); + } + + return count; +} + +int SwIspLinaro::queueBuffers(FrameBuffer *input, + const std::map &outputs) +{ + unsigned int mask = 0; + + /* + * Validate the outputs as a sanity check: at least one output is + * required, all outputs must reference a valid stream and no two + * outputs can reference the same stream. + */ + if (outputs.empty()) + return -EINVAL; + + for (auto [index, buffer] : outputs) { + if (!buffer) + return -EINVAL; + if (index >= 1) /* only single stream atm */ + return -EINVAL; + if (mask & (1 << index)) + return -EINVAL; + + mask |= 1 << index; + } + + process(input, outputs.at(0)); + + return 0; +} + +int SwIspLinaro::start() +{ + ispWorkerThread_.start(); + return 0; +} + +void SwIspLinaro::stop() +{ + ispWorkerThread_.exit(); + ispWorkerThread_.wait(); +} + +void SwIspLinaro::IspWorker::process(FrameBuffer *input, FrameBuffer *output) +{ + /* Copy metadata from the input buffer */ + FrameMetadata &metadata = output->_d()->metadata(); + metadata.status = input->metadata().status; + metadata.sequence = input->metadata().sequence; + metadata.timestamp = input->metadata().timestamp; + + MappedFrameBuffer in(input, MappedFrameBuffer::MapFlag::Read); + MappedFrameBuffer out(output, MappedFrameBuffer::MapFlag::Write); + if (!in.isValid() || !out.isValid()) { + LOG(SoftwareIsp, Error) << "mmap-ing buffer(s) failed"; + metadata.status = FrameMetadata::FrameError; + swIsp_->outputBufferReady.emit(output); + swIsp_->inputBufferReady.emit(input); + return; + } + + (this->*debayerInfo_->debayer)(out.planes()[0].data(), in.planes()[0].data()); + metadata.planes()[0].bytesused = out.planes()[0].size(); + + *swIsp_->sharedStats_ = stats_; + swIsp_->ispStatsReady.emit(0); + + swIsp_->outputBufferReady.emit(output); + swIsp_->inputBufferReady.emit(input); +} + +void SwIspLinaro::process(FrameBuffer *input, FrameBuffer *output) +{ + ispWorker_->invokeMethod(&SwIspLinaro::IspWorker::process, + ConnectionTypeQueued, input, output); +} + +} /* namespace libcamera */