[3/3] libcamera: software_isp: Allow running IPA and stats on raw
diff mbox series

Message ID 20261005131700.97438-4-mzamazal@redhat.com
State New
Headers show
Series
  • Use softisp IPA with simple pipeline raw output
Related show

Commit Message

Milan Zamazal Oct. 5, 2026, 1:16 p.m. UTC
`simple' pipeline can basically pass raw buffers from V4L2 to the
output.  While the raw data is untouched, we may still want to:

- Gather statistics from it.
- Run IPA on the images (without modifying the images).
- Provide metadata from the IPA.
- Adjust exposure/gain.

This patch implements all of that.

Since this imposes some overhead, a new configuration option
pipelines.simple.enable_raw_ipa is added.  The option is on by default
as at least the metadata and exposure adjustments may be useful.  Using
a configuration option rather than a runtime-adjustable option is not
perfect, but I don't know what better mechanism to use.

The patch attempts to fit into the current structure as close as
possible, with minimum changes.  Future enhancements might include
computing the stats asynchronously or even splitting `simple' pipeline
into separate components (debayering, converter, raw output).

Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
---
 Documentation/runtime_configuration.rst       | 10 +++++
 .../internal/software_isp/software_isp.h      |  3 +-
 src/libcamera/pipeline/simple/simple.cpp      | 36 ++++++++++++++---
 src/libcamera/software_isp/software_isp.cpp   | 40 ++++++++++++++++---
 4 files changed, 77 insertions(+), 12 deletions(-)

Patch
diff mbox series

diff --git a/Documentation/runtime_configuration.rst b/Documentation/runtime_configuration.rst
index a71b715a1..1d93ad3a8 100644
--- a/Documentation/runtime_configuration.rst
+++ b/Documentation/runtime_configuration.rst
@@ -46,6 +46,7 @@  file structure:
         supported_devices:
           - driver: # driver name, e.g. `mxc-isi`
             software_isp: # true/false
+        enable_raw_ipa: # true/false
     software_isp:
       copy_input_buffer: # true/false
       measure:
@@ -81,6 +82,7 @@  Configuration file example
          supported_devices:
            - driver: mxc-isi
              software_isp: true
+         enable_raw_ipa: true
      software_isp:
        copy_input_buffer: false
        measure:
@@ -157,6 +159,14 @@  pipelines.simple.supported_devices.driver, pipelines.simple.supported_devices.so
 
    Example `software_isp` value: ``true``
 
+pipelines.simple.enable_raw_ipa
+   Whether image processing algorithms are enabled also with raw-only
+   output. When enabled, statistics is gathered for raw-only output,
+   image processing algorithms are run on it, metadata is produced and
+   exposure is adjusted.
+
+   Example value: ``true``
+
 software_isp.copy_input_buffer
    Define whether input buffers should be copied into standard (cached)
    memory in software ISP. This is done by default to prevent very slow
diff --git a/include/libcamera/internal/software_isp/software_isp.h b/include/libcamera/internal/software_isp/software_isp.h
index 3e420242b..b4c57dbf5 100644
--- a/include/libcamera/internal/software_isp/software_isp.h
+++ b/include/libcamera/internal/software_isp/software_isp.h
@@ -67,7 +67,8 @@  public:
 	int configure(const StreamConfiguration &inputCfg,
 		      const std::vector<std::reference_wrapper<const StreamConfiguration>> &outputCfgs,
 		      const ipa::softisp::IPAConfigInfo &configInfo,
-		      ControlInfoMap *ipaControls);
+		      ControlInfoMap *ipaControls,
+		      bool debayerEnabled);
 
 	int exportBuffers(const Stream *stream, unsigned int count,
 			  std::vector<std::unique_ptr<FrameBuffer>> *buffers);
diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
index 35c29ceca..dd1d69a5b 100644
--- a/src/libcamera/pipeline/simple/simple.cpp
+++ b/src/libcamera/pipeline/simple/simple.cpp
@@ -427,6 +427,7 @@  public:
 	V4L2Subdevice *subdev(const MediaEntity *entity);
 	std::shared_ptr<MediaDevice> converter() { return converter_; }
 	bool swIspEnabled() const { return swIspEnabled_; }
+	bool rawIpaEnabled() const { return rawIpaEnabled_; }
 
 protected:
 	int queueRequestDevice(Camera *camera, Request *request) override;
@@ -460,6 +461,7 @@  private:
 
 	std::shared_ptr<MediaDevice> converter_;
 	bool swIspEnabled_;
+	bool rawIpaEnabled_ = true;
 };
 
 /* -----------------------------------------------------------------------------
@@ -968,6 +970,11 @@  void SimpleCameraData::imageBufferReady(FrameBuffer *buffer)
 
 		conversionQueue_.pop();
 		return;
+	} else if (pipe->rawIpaEnabled()) {
+		/* \todo Make the processing asynchronous? */
+		swIsp_->process(request->sequence(), buffer, nullptr);
+		pipe->completeBuffer(request, buffer);
+		return;
 	}
 
 	/* Otherwise simply complete the request. */
@@ -1591,7 +1598,8 @@  int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c)
 			data->rawStream_ = &data->streams_[i];
 	}
 
-	if (outputCfgs.empty())
+	if (outputCfgs.empty() &&
+	    (data->converter_ || !data->rawStream_ || !rawIpaEnabled_))
 		return 0;
 
 	StreamConfiguration inputCfg;
@@ -1605,7 +1613,12 @@  int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c)
 	} else {
 		ipa::softisp::IPAConfigInfo configInfo;
 		configInfo.sensorControls = data->sensor_->controls();
-		return data->swIsp_->configure(inputCfg, outputCfgs, configInfo, &data->controlInfo_);
+		return data->swIsp_->configure(
+			inputCfg,
+			outputCfgs,
+			configInfo,
+			&data->controlInfo_,
+			data->useConversion_);
 	}
 }
 
@@ -1698,6 +1711,12 @@  int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL
 		if (!data->rawStream_)
 			for (std::unique_ptr<FrameBuffer> &buffer : data->conversionBuffers_)
 				video->queueBuffer(buffer.get());
+	} else if (data->swIsp_) {
+		ret = data->swIsp_->startIpa();
+		if (ret < 0) {
+			stop(camera);
+			return ret;
+		}
 	}
 
 	return 0;
@@ -1720,6 +1739,8 @@  void SimplePipelineHandler::stopDevice(Camera *camera)
 			data->converter_->stop();
 		else if (data->swIsp_)
 			data->swIsp_->stop();
+	} else if (data->swIsp_) {
+		data->swIsp_->stopIpa();
 	}
 
 	video->streamOff();
@@ -1763,6 +1784,8 @@  int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request)
 		data->conversionQueue_.push({ request, std::move(buffers) });
 		if (data->swIsp_)
 			data->swIsp_->queueRequest(request->sequence(), request->controls());
+	} else if (data->swIsp_ && rawIpaEnabled_) {
+		data->swIsp_->queueRequest(request->sequence(), request->controls());
 	}
 
 	return 0;
@@ -1882,9 +1905,8 @@  bool SimplePipelineHandler::matchDevice(std::shared_ptr<MediaDevice> media,
 
 	swIspEnabled_ = info.swIspEnabled;
 	const GlobalConfiguration &configuration = cameraManager()->_d()->configuration();
-	for (const ValueNode &entry :
-	     configuration.configuration()["pipelines"]["simple"]["supported_devices"]
-		     .asList()) {
+	auto &simpleConfiguration = configuration.configuration()["pipelines"]["simple"];
+	for (const ValueNode &entry : simpleConfiguration["supported_devices"].asList()) {
 		auto name = entry["driver"].get<std::string>();
 		if (name == info.driver) {
 			swIspEnabled_ = entry["software_isp"].get<bool>().value_or(swIspEnabled_);
@@ -1903,6 +1925,10 @@  bool SimplePipelineHandler::matchDevice(std::shared_ptr<MediaDevice> media,
 		 */
 		ASSERT(!converter_);
 		numStreams = 2;
+
+		auto enableRawIpa = simpleConfiguration["enable_raw_ipa"].get<bool>();
+		if (enableRawIpa)
+			rawIpaEnabled_ = enableRawIpa.value();
 	}
 
 	/* Locate the sensors. */
diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
index 11e0c49ed..594125f8f 100644
--- a/src/libcamera/software_isp/software_isp.cpp
+++ b/src/libcamera/software_isp/software_isp.cpp
@@ -25,6 +25,7 @@ 
 
 #include "libcamera/internal/bayer_format.h"
 #include "libcamera/internal/framebuffer.h"
+#include "libcamera/internal/mapped_framebuffer.h"
 #include "libcamera/internal/software_isp/debayer_params.h"
 
 #include "debayer_cpu.h"
@@ -280,19 +281,34 @@  uint32_t SoftwareIsp::preferredInputStride(const PixelFormat &inputFormat, const
  * \param[in] outputCfgs The output configurations
  * \param[in] configInfo The IPA configuration data, received from the pipeline handler
  * \param[out] ipaControls The IPA controls to update
+ * \param[in] debayerEnabled Whether debayering should be performed in addition
+ *   to stats and IPA processing
  * \return 0 on success, a negative errno on failure
  */
 int SoftwareIsp::configure(const StreamConfiguration &inputCfg,
 			   const std::vector<std::reference_wrapper<const StreamConfiguration>> &outputCfgs,
 			   const ipa::softisp::IPAConfigInfo &configInfo,
-			   ControlInfoMap *ipaControls)
+			   ControlInfoMap *ipaControls,
+			   bool debayerEnabled)
 {
-	ASSERT(ipa_ && debayer_);
+	ASSERT(ipa_ && (!debayerEnabled || debayer_));
+
+	int ret;
+	if (!debayerEnabled) {
+		ret = stats_->configure(inputCfg);
+		if (ret < 0)
+			return ret;
+		stats_->setWindow(Rectangle(inputCfg.size));
+	}
 
-	int ret = ipa_->configure(configInfo, ipaControls);
+	ret = ipa_->configure(configInfo, ipaControls);
 	if (ret < 0)
 		return ret;
 
+	if (!debayerEnabled) {
+		return 0;
+	}
+
 	ret = debayer_->configure(inputCfg, outputCfgs, ccmEnabled_);
 	if (ret < 0)
 		return ret;
@@ -452,13 +468,25 @@  void SoftwareIsp::stopIpa()
  * \brief Passes the input framebuffer to the ISP worker to process
  * \param[in] frame The frame number
  * \param[in] input The input framebuffer
- * \param[out] output The framebuffer to write the processed frame to
+ * \param[out] output The framebuffer to write the processed frame to; if
+ *   nullptr then debayering is skipped and stats are processed synchronously
  */
 void SoftwareIsp::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output)
 {
 	ipa_->computeParams(frame);
-	debayer_->invokeMethod(&Debayer::process,
-			       ConnectionTypeQueued, frame, input, output, debayerParams_);
+
+	if (output) {
+		debayer_->invokeMethod(&Debayer::process,
+				       ConnectionTypeQueued, frame, input, output, debayerParams_);
+	} else {
+		/* Compute stats, which are otherwise computed in debayering */
+		if (frame % SwStatsCpu::kStatPerNumFrames) {
+			stats_->finishFrame(frame, 0);
+		} else {
+			MappedFrameBuffer inputMapped(input, MappedFrameBuffer::MapFlag::Read);
+			stats_->processFrame(frame, 0, inputMapped);
+		}
+	}
 }
 
 void SoftwareIsp::saveIspParams([[maybe_unused]] uint32_t frame)