[libcamera-devel,v2,13/18] libcamera: software_isp: add Simple SoftwareIsp implementation
diff mbox series

Message ID 20240113142218.28063-14-hdegoede@redhat.com
State Superseded
Headers show
Series
  • [libcamera-devel,v2,01/18] libcamera: pipeline: simple: fix size adjustment in validate()
Related show

Commit Message

Hans de Goede Jan. 13, 2024, 2:22 p.m. UTC
From: Andrey Konovalov <andrey.konovalov@linaro.org>

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.

Doxygen documentation by Dennis Bonke.

Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
Co-authored-by: Dennis Bonke <admin@dennisbonke.com>
Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
Co-authored-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
Tested-by: Pavel Machek <pavel@ucw.cz>
---
 .../internal/software_isp/meson.build         |   1 +
 .../internal/software_isp/swisp_simple.h      | 163 ++++++++++++
 src/libcamera/software_isp/meson.build        |  19 ++
 src/libcamera/software_isp/swisp_simple.cpp   | 238 ++++++++++++++++++
 4 files changed, 421 insertions(+)
 create mode 100644 include/libcamera/internal/software_isp/swisp_simple.h
 create mode 100644 src/libcamera/software_isp/swisp_simple.cpp

Comments

Barnabás Pőcze Jan. 13, 2024, 4:30 p.m. UTC | #1
Hi


2024. január 13., szombat 15:22 keltezéssel, Hans de Goede via libcamera-devel írta:

> From: Andrey Konovalov <andrey.konovalov@linaro.org>
> 
> 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.
> 
> Doxygen documentation by Dennis Bonke.
> 
> Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
> Co-authored-by: Dennis Bonke <admin@dennisbonke.com>
> Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
> Co-authored-by: Hans de Goede <hdegoede@redhat.com>
> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
> Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
> Tested-by: Pavel Machek <pavel@ucw.cz>
> ---
>  .../internal/software_isp/meson.build         |   1 +
>  .../internal/software_isp/swisp_simple.h      | 163 ++++++++++++
>  src/libcamera/software_isp/meson.build        |  19 ++
>  src/libcamera/software_isp/swisp_simple.cpp   | 238 ++++++++++++++++++
>  4 files changed, 421 insertions(+)
>  create mode 100644 include/libcamera/internal/software_isp/swisp_simple.h
>  create mode 100644 src/libcamera/software_isp/swisp_simple.cpp
> 
> diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build
> index b5a0d737..cf21ace5 100644
> --- a/include/libcamera/internal/software_isp/meson.build
> +++ b/include/libcamera/internal/software_isp/meson.build
> @@ -4,6 +4,7 @@ libcamera_internal_headers += files([
>      'debayer.h',
>      'debayer_cpu.h',
>      'debayer_params.h',
> +    'swisp_simple.h',
>      'swisp_stats.h',
>      'swstats.h',
>      'swstats_cpu.h',
> diff --git a/include/libcamera/internal/software_isp/swisp_simple.h b/include/libcamera/internal/software_isp/swisp_simple.h
> new file mode 100644
> index 00000000..87613c23
> --- /dev/null
> +++ b/include/libcamera/internal/software_isp/swisp_simple.h
> @@ -0,0 +1,163 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2023, Linaro Ltd
> + *
> + * swisp_simple.h - Simple software ISP implementation
> + */
> +
> +#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/dma_heaps.h"
> +#include "libcamera/internal/software_isp.h"
> +#include "libcamera/internal/software_isp/debayer_cpu.h"
> +
> +namespace libcamera {
> +
> +/**
> + * \brief Class for the Simple Software ISP.
> + *
> + * Implementation of the SoftwareIsp interface.
> + */
> +class SwIspSimple : public SoftwareIsp
> +{
> +public:
> +	/**
> +	 * \brief Constructor for the SwIspSimple object.
> +	 *
> +	 * \param[in] pipe The pipeline handler in use.
> +	 * \param[in] sensorControls The sensor controls.
> +	 */
> +	SwIspSimple(PipelineHandler *pipe, const ControlInfoMap &sensorControls);
> +	~SwIspSimple() {}

~SwIspSimple() = default;


> +
> +	/**
> +	 * \brief Load a configuration from a file.
> +	 * \param[in] filename The file to load from.
> +	 *
> +	 * \return 0 on success.
> +	 */
> +	int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; }
> +
> +	/**
> +	 * \brief Gets if there is a valid debayer object.
> +	 *
> +	 * \returns true if there is, false otherwise.
> +	 */
> +	bool isValid() const;
> +
> +	/**
> +	 * \brief Get the supported output formats.
> +	 * \param[in] input The input format.
> +	 *
> +	 * \return all supported output formats or an empty vector if there are none.
> +	 */
> +	std::vector<PixelFormat> formats(PixelFormat input);
> +
> +	/**
> +	 * \brief Get the supported output sizes for the given input format and size.
> +	 * \param[in] inputFormat The input format.
> +	 * \param[in] inputSize The input size.
> +	 *
> +	 * \return The valid size ranges or an empty range if there are none.
> +	 */
> +	SizeRange sizes(PixelFormat inputFormat, const Size &inputSize);
> +
> +	/**
> +	 * \brief Get the stride and the frame size.
> +	 * \param[in] outputFormat The output format.
> +	 * \param[in] size The output size.
> +	 *
> +	 * \return a tuple of the stride and the frame size, or a tuple with 0,0 if there is no valid output config.
> +	 */
> +	std::tuple<unsigned int, unsigned int>
> +	strideAndFrameSize(const PixelFormat &outputFormat, const Size &size);
> +
> +	/**
> +	 * \brief Configure the SwIspSimple object according to the passed in parameters.
> +	 * \param[in] inputCfg The input configuration.
> +	 * \param[in] outputCfgs The output configurations.
> +	 * \param[in] sensorControls The sensor controls.
> +	 *
> +	 * \return 0 on success, a negative errno on failure.
> +	 */
> +	int configure(const StreamConfiguration &inputCfg,
> +		      const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,
> +		      const ControlInfoMap &sensorControls);
> +
> +	/**
> +	 * \brief Exports the buffers for use in processing.
> +	 * \param[in] output The number of outputs requested.
> +	 * \param[in] count The number of planes.
> +	 * \param[out] buffers The exported buffers.
> +	 *
> +	 * \return count when successful, a negative return value if an error occurred.
> +	 */
> +	int exportBuffers(unsigned int output, unsigned int count,
> +			  std::vector<std::unique_ptr<FrameBuffer>> *buffers);
> +
> +	/**
> +	 * \brief Process the statistics gathered.
> +	 * \param[in] sensorControls The sensor controls.
> +	 */
> +	void processStats(const ControlList &sensorControls);
> +
> +	/**
> +	 * \brief Starts the Software ISP worker.
> +	 *
> +	 * \return 0 on success, any other value indicates an error.
> +	 */
> +	int start();
> +
> +	/**
> +	 * \brief Stops the Software ISP worker.
> +	 */
> +	void stop();
> +
> +	/**
> +	 * \brief Queues buffers for processing.
> +	 * \param[in] input The input framebuffer.
> +	 * \param[in] outputs The output framebuffers.
> +	 *
> +	 * \return 0 on success, a negative errno on failure
> +	 */
> +	int queueBuffers(FrameBuffer *input,
> +			 const std::map<unsigned int, FrameBuffer *> &outputs);
> +
> +	/**
> +	 * \brief Get the signal for when the sensor controls are set.
> +	 *
> +	 * \return The control list of the sensor controls.
> +	 */
> +	Signal<const ControlList &> &getSignalSetSensorControls();
> +
> +	/**
> +	 * \brief Process the input framebuffer.
> +	 * \param[in] input The input framebuffer.
> +	 * \param[out] output The output framebuffer.
> +	 */
> +	void process(FrameBuffer *input, FrameBuffer *output);
> +
> +private:
> +	void saveIspParams(int dummy);
> +	void statsReady(int dummy);
> +	void inputReady(FrameBuffer *input);
> +	void outputReady(FrameBuffer *output);
> +
> +	std::unique_ptr<DebayerCpu> debayer_;
> +	Thread ispWorkerThread_;
> +	SharedMemObject<DebayerParams> sharedParams_;
> +	DebayerParams debayerParams_;
> +	DmaHeap dmaHeap_;
> +
> +	std::unique_ptr<ipa::soft::IPAProxySoft> ipa_;
> +};
> +
> +} /* namespace libcamera */
> diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build
> index 6d7a44d7..9464f2fd 100644
> --- a/src/libcamera/software_isp/meson.build
> +++ b/src/libcamera/software_isp/meson.build
> @@ -6,3 +6,22 @@ libcamera_sources += files([
>  	'swstats.cpp',
>  	'swstats_cpu.cpp',
>  ])
> +
> +# Software ISP is enabled for 'simple' pipeline handler.
> +# The 'pipelines' option value should be
> +# 'simple/<Software ISP implementation>' e.g.:
> +#   -Dpipelines=simple/simple
> +# The source file should be named swisp_<Software ISP implementation>.cpp,
> +# e.g. 'swisp_simple.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
> diff --git a/src/libcamera/software_isp/swisp_simple.cpp b/src/libcamera/software_isp/swisp_simple.cpp
> new file mode 100644
> index 00000000..0884166e
> --- /dev/null
> +++ b/src/libcamera/software_isp/swisp_simple.cpp
> @@ -0,0 +1,238 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2023, Linaro Ltd
> + *
> + * swisp_simple.cpp - Simple software ISP implementation
> + */
> +
> +#include "libcamera/internal/software_isp/swisp_simple.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 {
> +
> +SwIspSimple::SwIspSimple(PipelineHandler *pipe, const ControlInfoMap &sensorControls)
> +	: SoftwareIsp(pipe, sensorControls), debayer_(nullptr), debayerParams_{ 256, 256, 256, 0.5f },
> +	  dmaHeap_(DmaHeap::DmaHeapFlag::Cma | DmaHeap::DmaHeapFlag::System)
> +{
> +	if (!dmaHeap_.isValid()) {
> +		LOG(SoftwareIsp, Error) << "Failed to create DmaHeap object";
> +		return;
> +	}
> +
> +	sharedParams_ = SharedMemObject<DebayerParams>("softIsp_params");
> +	if (!sharedParams_.fd().isValid()) {
> +		LOG(SoftwareIsp, Error) << "Failed to create shared memory for parameters";
> +		return;
> +	}
> +
> +	std::unique_ptr<SwStatsCpu> stats;
> +
> +	stats = std::make_unique<SwStatsCpu>();

You can just say

  auto stats = std::make_unique<SwStatsCpu>();


> +	if (!stats) {

std::make_unique should never return nullptr.


> +		LOG(SoftwareIsp, Error) << "Failed to create SwStatsCpu object";
> +		return;
> +	}
> +
> +	stats->statsReady.connect(this, &SwIspSimple::statsReady);
> +
> +	debayer_ = std::make_unique<DebayerCpu>(std::move(stats));
> +	if (!debayer_) {

Same here.


> +		LOG(SoftwareIsp, Error) << "Failed to create DebayerCpu object";
> +		return;
> +	}
> +
> +	debayer_->inputBufferReady.connect(this, &SwIspSimple::inputReady);
> +	debayer_->outputBufferReady.connect(this, &SwIspSimple::outputReady);
> +
> +	ipa_ = IPAManager::createIPA<ipa::soft::IPAProxySoft>(pipe, 0, 0);
> +	if (!ipa_) {
> +		LOG(SoftwareIsp, Error)
> +			<< "Creating IPA for software ISP failed";
> +		debayer_.reset();
> +		return;
> +	}
> +
> +	int ret = ipa_->init(IPASettings{ "No cfg file", "No sensor model" },
> +			     debayer_->getStatsFD(),
> +			     sharedParams_.fd(),
> +			     sensorControls);
> +	if (ret) {
> +		LOG(SoftwareIsp, Error) << "IPA init failed";
> +		debayer_.reset();
> +		return;
> +	}
> +
> +	ipa_->setIspParams.connect(this, &SwIspSimple::saveIspParams);
> +
> +	debayer_->moveToThread(&ispWorkerThread_);
> +}
> [...]


Regards,
Barnabás Pőcze

Patch
diff mbox series

diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build
index b5a0d737..cf21ace5 100644
--- a/include/libcamera/internal/software_isp/meson.build
+++ b/include/libcamera/internal/software_isp/meson.build
@@ -4,6 +4,7 @@  libcamera_internal_headers += files([
     'debayer.h',
     'debayer_cpu.h',
     'debayer_params.h',
+    'swisp_simple.h',
     'swisp_stats.h',
     'swstats.h',
     'swstats_cpu.h',
diff --git a/include/libcamera/internal/software_isp/swisp_simple.h b/include/libcamera/internal/software_isp/swisp_simple.h
new file mode 100644
index 00000000..87613c23
--- /dev/null
+++ b/include/libcamera/internal/software_isp/swisp_simple.h
@@ -0,0 +1,163 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Linaro Ltd
+ *
+ * swisp_simple.h - Simple software ISP implementation
+ */
+
+#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/dma_heaps.h"
+#include "libcamera/internal/software_isp.h"
+#include "libcamera/internal/software_isp/debayer_cpu.h"
+
+namespace libcamera {
+
+/**
+ * \brief Class for the Simple Software ISP.
+ *
+ * Implementation of the SoftwareIsp interface.
+ */
+class SwIspSimple : public SoftwareIsp
+{
+public:
+	/**
+	 * \brief Constructor for the SwIspSimple object.
+	 *
+	 * \param[in] pipe The pipeline handler in use.
+	 * \param[in] sensorControls The sensor controls.
+	 */
+	SwIspSimple(PipelineHandler *pipe, const ControlInfoMap &sensorControls);
+	~SwIspSimple() {}
+
+	/**
+	 * \brief Load a configuration from a file.
+	 * \param[in] filename The file to load from.
+	 *
+	 * \return 0 on success.
+	 */
+	int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; }
+
+	/**
+	 * \brief Gets if there is a valid debayer object.
+	 *
+	 * \returns true if there is, false otherwise.
+	 */
+	bool isValid() const;
+
+	/**
+	 * \brief Get the supported output formats.
+	 * \param[in] input The input format.
+	 *
+	 * \return all supported output formats or an empty vector if there are none.
+	 */
+	std::vector<PixelFormat> formats(PixelFormat input);
+
+	/**
+	 * \brief Get the supported output sizes for the given input format and size.
+	 * \param[in] inputFormat The input format.
+	 * \param[in] inputSize The input size.
+	 *
+	 * \return The valid size ranges or an empty range if there are none.
+	 */
+	SizeRange sizes(PixelFormat inputFormat, const Size &inputSize);
+
+	/**
+	 * \brief Get the stride and the frame size.
+	 * \param[in] outputFormat The output format.
+	 * \param[in] size The output size.
+	 *
+	 * \return a tuple of the stride and the frame size, or a tuple with 0,0 if there is no valid output config.
+	 */
+	std::tuple<unsigned int, unsigned int>
+	strideAndFrameSize(const PixelFormat &outputFormat, const Size &size);
+
+	/**
+	 * \brief Configure the SwIspSimple object according to the passed in parameters.
+	 * \param[in] inputCfg The input configuration.
+	 * \param[in] outputCfgs The output configurations.
+	 * \param[in] sensorControls The sensor controls.
+	 *
+	 * \return 0 on success, a negative errno on failure.
+	 */
+	int configure(const StreamConfiguration &inputCfg,
+		      const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,
+		      const ControlInfoMap &sensorControls);
+
+	/**
+	 * \brief Exports the buffers for use in processing.
+	 * \param[in] output The number of outputs requested.
+	 * \param[in] count The number of planes.
+	 * \param[out] buffers The exported buffers.
+	 *
+	 * \return count when successful, a negative return value if an error occurred.
+	 */
+	int exportBuffers(unsigned int output, unsigned int count,
+			  std::vector<std::unique_ptr<FrameBuffer>> *buffers);
+
+	/**
+	 * \brief Process the statistics gathered.
+	 * \param[in] sensorControls The sensor controls.
+	 */
+	void processStats(const ControlList &sensorControls);
+
+	/**
+	 * \brief Starts the Software ISP worker.
+	 *
+	 * \return 0 on success, any other value indicates an error.
+	 */
+	int start();
+
+	/**
+	 * \brief Stops the Software ISP worker.
+	 */
+	void stop();
+
+	/**
+	 * \brief Queues buffers for processing.
+	 * \param[in] input The input framebuffer.
+	 * \param[in] outputs The output framebuffers.
+	 *
+	 * \return 0 on success, a negative errno on failure
+	 */
+	int queueBuffers(FrameBuffer *input,
+			 const std::map<unsigned int, FrameBuffer *> &outputs);
+
+	/**
+	 * \brief Get the signal for when the sensor controls are set.
+	 *
+	 * \return The control list of the sensor controls.
+	 */
+	Signal<const ControlList &> &getSignalSetSensorControls();
+
+	/**
+	 * \brief Process the input framebuffer.
+	 * \param[in] input The input framebuffer.
+	 * \param[out] output The output framebuffer.
+	 */
+	void process(FrameBuffer *input, FrameBuffer *output);
+
+private:
+	void saveIspParams(int dummy);
+	void statsReady(int dummy);
+	void inputReady(FrameBuffer *input);
+	void outputReady(FrameBuffer *output);
+
+	std::unique_ptr<DebayerCpu> debayer_;
+	Thread ispWorkerThread_;
+	SharedMemObject<DebayerParams> sharedParams_;
+	DebayerParams debayerParams_;
+	DmaHeap dmaHeap_;
+
+	std::unique_ptr<ipa::soft::IPAProxySoft> ipa_;
+};
+
+} /* namespace libcamera */
diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build
index 6d7a44d7..9464f2fd 100644
--- a/src/libcamera/software_isp/meson.build
+++ b/src/libcamera/software_isp/meson.build
@@ -6,3 +6,22 @@  libcamera_sources += files([
 	'swstats.cpp',
 	'swstats_cpu.cpp',
 ])
+
+# Software ISP is enabled for 'simple' pipeline handler.
+# The 'pipelines' option value should be
+# 'simple/<Software ISP implementation>' e.g.:
+#   -Dpipelines=simple/simple
+# The source file should be named swisp_<Software ISP implementation>.cpp,
+# e.g. 'swisp_simple.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
diff --git a/src/libcamera/software_isp/swisp_simple.cpp b/src/libcamera/software_isp/swisp_simple.cpp
new file mode 100644
index 00000000..0884166e
--- /dev/null
+++ b/src/libcamera/software_isp/swisp_simple.cpp
@@ -0,0 +1,238 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Linaro Ltd
+ *
+ * swisp_simple.cpp - Simple software ISP implementation
+ */
+
+#include "libcamera/internal/software_isp/swisp_simple.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 {
+
+SwIspSimple::SwIspSimple(PipelineHandler *pipe, const ControlInfoMap &sensorControls)
+	: SoftwareIsp(pipe, sensorControls), debayer_(nullptr), debayerParams_{ 256, 256, 256, 0.5f },
+	  dmaHeap_(DmaHeap::DmaHeapFlag::Cma | DmaHeap::DmaHeapFlag::System)
+{
+	if (!dmaHeap_.isValid()) {
+		LOG(SoftwareIsp, Error) << "Failed to create DmaHeap object";
+		return;
+	}
+
+	sharedParams_ = SharedMemObject<DebayerParams>("softIsp_params");
+	if (!sharedParams_.fd().isValid()) {
+		LOG(SoftwareIsp, Error) << "Failed to create shared memory for parameters";
+		return;
+	}
+
+	std::unique_ptr<SwStatsCpu> stats;
+
+	stats = std::make_unique<SwStatsCpu>();
+	if (!stats) {
+		LOG(SoftwareIsp, Error) << "Failed to create SwStatsCpu object";
+		return;
+	}
+
+	stats->statsReady.connect(this, &SwIspSimple::statsReady);
+
+	debayer_ = std::make_unique<DebayerCpu>(std::move(stats));
+	if (!debayer_) {
+		LOG(SoftwareIsp, Error) << "Failed to create DebayerCpu object";
+		return;
+	}
+
+	debayer_->inputBufferReady.connect(this, &SwIspSimple::inputReady);
+	debayer_->outputBufferReady.connect(this, &SwIspSimple::outputReady);
+
+	ipa_ = IPAManager::createIPA<ipa::soft::IPAProxySoft>(pipe, 0, 0);
+	if (!ipa_) {
+		LOG(SoftwareIsp, Error)
+			<< "Creating IPA for software ISP failed";
+		debayer_.reset();
+		return;
+	}
+
+	int ret = ipa_->init(IPASettings{ "No cfg file", "No sensor model" },
+			     debayer_->getStatsFD(),
+			     sharedParams_.fd(),
+			     sensorControls);
+	if (ret) {
+		LOG(SoftwareIsp, Error) << "IPA init failed";
+		debayer_.reset();
+		return;
+	}
+
+	ipa_->setIspParams.connect(this, &SwIspSimple::saveIspParams);
+
+	debayer_->moveToThread(&ispWorkerThread_);
+}
+
+void SwIspSimple::processStats(const ControlList &sensorControls)
+{
+	ASSERT(ipa_);
+	ipa_->processStats(sensorControls);
+}
+
+Signal<const ControlList &> &SwIspSimple::getSignalSetSensorControls()
+{
+	ASSERT(ipa_);
+	return ipa_->setSensorControls;
+}
+
+bool SwIspSimple::isValid() const
+{
+	return !!debayer_;
+}
+
+std::vector<PixelFormat> SwIspSimple::formats(PixelFormat inputFormat)
+{
+	ASSERT(debayer_ != nullptr);
+
+	return debayer_->formats(inputFormat);
+}
+
+SizeRange SwIspSimple::sizes(PixelFormat inputFormat, const Size &inputSize)
+{
+	ASSERT(debayer_ != nullptr);
+
+	return debayer_->sizes(inputFormat, inputSize);
+}
+
+std::tuple<unsigned int, unsigned int>
+SwIspSimple::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size)
+{
+	ASSERT(debayer_ != nullptr);
+
+	return debayer_->strideAndFrameSize(outputFormat, size);
+}
+
+int SwIspSimple::configure(const StreamConfiguration &inputCfg,
+			   const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,
+			   const ControlInfoMap &sensorControls)
+{
+	ASSERT(ipa_ != nullptr && debayer_ != nullptr);
+
+	int ret = ipa_->configure(sensorControls);
+	if (ret < 0)
+		return ret;
+
+	return debayer_->configure(inputCfg, outputCfgs);
+}
+
+int SwIspSimple::exportBuffers(unsigned int output, unsigned int count,
+			       std::vector<std::unique_ptr<FrameBuffer>> *buffers)
+{
+	ASSERT(debayer_ != nullptr);
+
+	/* single output for now */
+	if (output >= 1)
+		return -EINVAL;
+
+	for (unsigned int i = 0; i < count; i++) {
+		const std::string name = "frame-" + std::to_string(i);
+		const size_t frameSize = debayer_->frameSize();
+
+		FrameBuffer::Plane outPlane;
+		outPlane.fd = SharedFD(dmaHeap_.alloc(name.c_str(), frameSize));
+		if (!outPlane.fd.isValid()) {
+			LOG(SoftwareIsp, Error)
+				<< "failed to allocate a dma_buf";
+			return -ENOMEM;
+		}
+		outPlane.offset = 0;
+		outPlane.length = frameSize;
+
+		std::vector<FrameBuffer::Plane> planes{ outPlane };
+		buffers->emplace_back(std::make_unique<FrameBuffer>(std::move(planes)));
+	}
+
+	return count;
+}
+
+int SwIspSimple::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 SwIspSimple::start()
+{
+	int ret = ipa_->start();
+	if (ret)
+		return ret;
+
+	ispWorkerThread_.start();
+	return 0;
+}
+
+void SwIspSimple::stop()
+{
+	ispWorkerThread_.exit();
+	ispWorkerThread_.wait();
+
+	ipa_->stop();
+}
+
+void SwIspSimple::process(FrameBuffer *input, FrameBuffer *output)
+{
+	debayer_->invokeMethod(&DebayerCpu::process,
+			       ConnectionTypeQueued, input, output, debayerParams_);
+}
+
+void SwIspSimple::saveIspParams([[maybe_unused]] int dummy)
+{
+	debayerParams_ = *sharedParams_;
+}
+
+void SwIspSimple::statsReady(int dummy)
+{
+	ispStatsReady.emit(dummy);
+}
+
+void SwIspSimple::inputReady(FrameBuffer *input)
+{
+	inputBufferReady.emit(input);
+}
+
+void SwIspSimple::outputReady(FrameBuffer *output)
+{
+	outputBufferReady.emit(output);
+}
+
+REGISTER_SOFTWAREISP(SwIspSimple)
+
+} /* namespace libcamera */