@@ -50,3 +50,4 @@ libcamera_internal_headers = files([
])
subdir('converter')
+subdir('software_isp')
@@ -2,4 +2,5 @@
libcamera_internal_headers += files([
'statistics-linaro.h',
+ 'swisp_linaro.h',
])
new file mode 100644
@@ -0,0 +1,117 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Linaro Ltd
+ *
+ * swisp_linaro.h - software ISP implementation by Linaro
+ */
+
+#pragma once
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/thread.h>
+
+#include <libcamera/pixel_format.h>
+
+#include <libcamera/ipa/soft_ipa_interface.h>
+#include <libcamera/ipa/soft_ipa_proxy.h>
+
+#include "libcamera/internal/shared_mem_object.h"
+#include "libcamera/internal/software_isp.h"
+#include "libcamera/internal/software_isp/statistics-linaro.h"
+
+namespace libcamera {
+
+class SwIspLinaro : public SoftwareIsp
+{
+public:
+ SwIspLinaro(PipelineHandler *pipe, const ControlInfoMap &sensorControls);
+ ~SwIspLinaro() {}
+
+ int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; }
+ bool isValid() const;
+
+ std::vector<PixelFormat> formats(PixelFormat input);
+ SizeRange sizes(PixelFormat inputFormat, const Size &inputSize);
+
+ std::tuple<unsigned int, unsigned int>
+ strideAndFrameSize(const PixelFormat &outputFormat, const Size &size);
+
+ int configure(const StreamConfiguration &inputCfg,
+ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs);
+ int exportBuffers(unsigned int output, unsigned int count,
+ std::vector<std::unique_ptr<FrameBuffer>> *buffers);
+ void processStats(const ControlList &sensorControls);
+
+ int start();
+ void stop();
+
+ int queueBuffers(FrameBuffer *input,
+ const std::map<unsigned int, FrameBuffer *> &outputs);
+
+ Signal<const ControlList &> &getSignalSetSensorControls();
+
+ void process(FrameBuffer *input, FrameBuffer *output);
+
+private:
+ SharedMemObject<SwIspStats> sharedStats_;
+
+ class IspWorker : public Object
+ {
+ public:
+ IspWorker(SwIspLinaro *swIsp);
+
+ std::vector<PixelFormat> 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<PixelFormat, IspWorker::debayerInfo> 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> ispWorker_;
+ Thread ispWorkerThread_;
+
+ std::unique_ptr<ipa::soft::IPAProxySoft> ipa_;
+};
+
+} /* namespace libcamera */
@@ -70,6 +70,7 @@ subdir('converter')
subdir('ipa')
subdir('pipeline')
subdir('proxy')
+subdir('software_isp')
null_dep = dependency('', required : false)
new file mode 100644
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: CC0-1.0
+
+# Software ISP is enabled for 'simple' pipeline handler.
+# The 'pipelines' option value should be
+# 'simple/<Software ISP implementation>' e.g.:
+# -Dpipelines=simple/linaro
+# The source file should be named swisp_<Software ISP implementation>.cpp,
+# e.g. 'swisp_linaro.cpp'.
+
+foreach pipeline : pipelines
+ pipeline = pipeline.split('/')
+ if pipeline.length() == 2 and pipeline[0] == 'simple'
+ libcamera_sources += files([
+ 'swisp_' + pipeline[1] + '.cpp',
+ ])
+ # the 'break' below can be removed if/when multiple
+ # Software ISP implementations are allowed in single build
+ break
+ endif
+endforeach
new file mode 100644
@@ -0,0 +1,589 @@
+/* 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 <sys/mman.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <libcamera/formats.h>
+#include <libcamera/stream.h>
+
+#include "libcamera/internal/bayer_format.h"
+#include "libcamera/internal/framebuffer.h"
+#include "libcamera/internal/ipa_manager.h"
+#include "libcamera/internal/mapped_framebuffer.h"
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(SoftwareIsp)
+
+SwIspLinaro::SwIspLinaro(PipelineHandler *pipe, const ControlInfoMap &sensorControls)
+ : SoftwareIsp(pipe, sensorControls)
+{
+ ispWorker_ = std::make_unique<SwIspLinaro::IspWorker>(this);
+ if (!ispWorker_) {
+ LOG(SoftwareIsp, Error)
+ << "Failed to create ISP worker";
+ return;
+ }
+
+ sharedStats_ = SharedMemObject<SwIspStats>("softIsp_stats");
+ if (!sharedStats_.fd().isValid()) {
+ LOG(SoftwareIsp, Error)
+ << "Failed to create shared memory for statistics";
+ ispWorker_.reset();
+ return;
+ }
+
+ ipa_ = IPAManager::createIPA<ipa::soft::IPAProxySoft>(pipe, 0, 0);
+ if (!ipa_) {
+ LOG(SoftwareIsp, Error)
+ << "Creating IPA for software ISP failed";
+ ispWorker_.reset();
+ return;
+ }
+
+ int ret = ipa_->init(IPASettings{ "No cfg file", "No sensor model" },
+ sharedStats_.fd(),
+ sensorControls);
+ if (ret) {
+ LOG(SoftwareIsp, Error) << "IPA init failed";
+ ispWorker_.reset();
+ return;
+ }
+
+ ipa_->configure(sensorControls);
+
+ ispWorker_->moveToThread(&ispWorkerThread_);
+}
+
+void SwIspLinaro::processStats(const ControlList &sensorControls)
+{
+ ASSERT(ipa_);
+ ipa_->processStats(sensorControls);
+}
+
+Signal<const ControlList &> &SwIspLinaro::getSignalSetSensorControls()
+{
+ ASSERT(ipa_);
+ return ipa_->setSensorControls;
+}
+
+bool SwIspLinaro::isValid() const
+{
+ return !!ispWorker_;
+}
+
+/*
+ * Demosaic Raw10P frame into RGB888 format.
+ * Two LS bits of the RAW10P's total 10 are ignored.
+ * Also this function performs the statistics calculations, and has a fairly
+ * naive grey world AWB algorithm implementation.
+ * \todo Split the stats calculations out of this function.
+ * \todo Move the AWB algorithm into the IPA module.
+ */
+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<PixelFormat> SwIspLinaro::IspWorker::formats(PixelFormat input)
+{
+ std::vector<PixelFormat> 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<PixelFormat> 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<unsigned int, unsigned int>
+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<std::reference_wrapper<StreamConfiguration>> &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<std::unique_ptr<FrameBuffer>> *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<FrameBuffer::Plane> planes{ outPlane };
+ buffers->emplace_back(std::make_unique<FrameBuffer>(std::move(planes)));
+ }
+
+ return count;
+}
+
+int SwIspLinaro::queueBuffers(FrameBuffer *input,
+ const std::map<unsigned int, FrameBuffer *> &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()
+{
+ int ret = ipa_->start();
+ if (ret)
+ return ret;
+
+ ispWorkerThread_.start();
+ return 0;
+}
+
+void SwIspLinaro::stop()
+{
+ ispWorkerThread_.exit();
+ ispWorkerThread_.wait();
+
+ ipa_->stop();
+}
+
+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);
+}
+
+REGISTER_SOFTWAREISP(SwIspLinaro)
+
+} /* namespace libcamera */
The implementation of SoftwareIsp handles creation of Soft IPA and interactions with it, so that the pipeline handler wouldn't need to care about the Soft IPA. Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org> --- include/libcamera/internal/meson.build | 1 + .../internal/software_isp/meson.build | 1 + .../internal/software_isp/swisp_linaro.h | 117 ++++ src/libcamera/meson.build | 1 + src/libcamera/software_isp/meson.build | 20 + src/libcamera/software_isp/swisp_linaro.cpp | 589 ++++++++++++++++++ 6 files changed, 729 insertions(+) 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