[v6,18/18] libcamera: Soft IPA: use CameraSensorHelper for analogue gain
diff mbox series

Message ID 20240319123622.675599-19-mzamazal@redhat.com
State Superseded
Headers show
Series
  • libcamera: introduce Software ISP and Software IPA
Related show

Commit Message

Milan Zamazal March 19, 2024, 12:36 p.m. UTC
From: Andrei Konovalov <andrey.konovalov.ynk@gmail.com>

Use CameraSensorHelper to convert the analogue gain code read from the
camera sensor into real analogue gain value. In the future this makes
it possible to use faster AE/AGC algorithm. For now the same AE/AGC
algorithm is used, but even then the CameraSensorHelper lets us use the
full range of analogue gain values.

If there is no CameraSensorHelper for the camera sensor in use, a
warning log message is printed, and the AE/AGC works exactly as before
this change.

Signed-off-by: Andrei Konovalov <andrey.konovalov.ynk@gmail.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
---
 .../internal/software_isp/software_isp.h      |  3 +-
 src/ipa/simple/soft_simple.cpp                | 77 ++++++++++++-------
 src/libcamera/pipeline/simple/simple.cpp      |  2 +-
 src/libcamera/software_isp/software_isp.cpp   |  8 +-
 4 files changed, 57 insertions(+), 33 deletions(-)

Comments

Laurent Pinchart March 27, 2024, 7:10 p.m. UTC | #1
Hi Milan and Andrei,

Thank you for the patch.

On Tue, Mar 19, 2024 at 01:36:05PM +0100, Milan Zamazal wrote:
> From: Andrei Konovalov <andrey.konovalov.ynk@gmail.com>
> 
> Use CameraSensorHelper to convert the analogue gain code read from the
> camera sensor into real analogue gain value. In the future this makes
> it possible to use faster AE/AGC algorithm. For now the same AE/AGC
> algorithm is used, but even then the CameraSensorHelper lets us use the
> full range of analogue gain values.
> 
> If there is no CameraSensorHelper for the camera sensor in use, a
> warning log message is printed, and the AE/AGC works exactly as before
> this change.

I'd rather not proceed if the sensor is unsupported. New sensors are
easy to add to libcamera.

> Signed-off-by: Andrei Konovalov <andrey.konovalov.ynk@gmail.com>
> Reviewed-by: Hans de Goede <hdegoede@redhat.com>
> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
> Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
> ---
>  .../internal/software_isp/software_isp.h      |  3 +-
>  src/ipa/simple/soft_simple.cpp                | 77 ++++++++++++-------
>  src/libcamera/pipeline/simple/simple.cpp      |  2 +-
>  src/libcamera/software_isp/software_isp.cpp   |  8 +-
>  4 files changed, 57 insertions(+), 33 deletions(-)

Ideally this patch should be split and squashed into the appropriate
other patches. If that ends up being too difficult I'm OK keeping it
separate, but it's not nice to review new code that contains issues
being fixed in later patches in the same series.

> 
> diff --git a/include/libcamera/internal/software_isp/software_isp.h b/include/libcamera/internal/software_isp/software_isp.h
> index 8d25e979..2a6db7ba 100644
> --- a/include/libcamera/internal/software_isp/software_isp.h
> +++ b/include/libcamera/internal/software_isp/software_isp.h
> @@ -26,6 +26,7 @@
>  #include <libcamera/ipa/soft_ipa_interface.h>
>  #include <libcamera/ipa/soft_ipa_proxy.h>
>  
> +#include "libcamera/internal/camera_sensor.h"
>  #include "libcamera/internal/dma_heaps.h"
>  #include "libcamera/internal/pipeline_handler.h"
>  #include "libcamera/internal/shared_mem_object.h"
> @@ -43,7 +44,7 @@ LOG_DECLARE_CATEGORY(SoftwareIsp)
>  class SoftwareIsp
>  {
>  public:
> -	SoftwareIsp(PipelineHandler *pipe, const ControlInfoMap &sensorControls);
> +	SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor);
>  	~SoftwareIsp();
>  
>  	int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; }
> diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
> index ac027568..e4d64762 100644
> --- a/src/ipa/simple/soft_simple.cpp
> +++ b/src/ipa/simple/soft_simple.cpp
> @@ -22,6 +22,8 @@
>  #include "libcamera/internal/software_isp/debayer_params.h"
>  #include "libcamera/internal/software_isp/swisp_stats.h"
>  
> +#include "libipa/camera_sensor_helper.h"
> +
>  #include "black_level.h"
>  
>  namespace libcamera {
> @@ -67,18 +69,27 @@ private:
>  	DebayerParams *params_;
>  	SwIspStats *stats_;
>  	BlackLevel blackLevel_;
> +	std::unique_ptr<CameraSensorHelper> camHelper_;
>  
>  	int32_t exposure_min_, exposure_max_;
> -	int32_t again_min_, again_max_;
> -	int32_t again_, exposure_;
> +	int32_t exposure_;
> +	double again_min_, again_max_, againMinStep_;
> +	double again_;
>  	unsigned int ignore_updates_;
>  };
>  
> -int IPASoftSimple::init([[maybe_unused]] const IPASettings &settings,
> +int IPASoftSimple::init(const IPASettings &settings,
>  			const SharedFD &fdStats,
>  			const SharedFD &fdParams,
>  			const ControlInfoMap &sensorInfoMap)
>  {
> +	camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel);
> +	if (camHelper_ == nullptr) {

	if (!camHelper) {

> +		LOG(IPASoft, Warning)
> +			<< "Failed to create camera sensor helper for "
> +			<< settings.sensorModel;
> +	}
> +
>  	fdStats_ = fdStats;
>  	if (!fdStats_.isValid()) {
>  		LOG(IPASoft, Error) << "Invalid Statistics handle";
> @@ -132,25 +143,35 @@ int IPASoftSimple::configure(const ControlInfoMap &sensorInfoMap)
>  		exposure_min_ = 1;
>  	}
>  
> -	again_min_ = gain_info.min().get<int32_t>();
> -	again_max_ = gain_info.max().get<int32_t>();
> -	/*
> -	 * The camera sensor gain (g) is usually not equal to the value written
> -	 * into the gain register (x). But the way how the AGC algorithm changes
> -	 * the gain value to make the total exposure closer to the optimum assumes
> -	 * that g(x) is not too far from linear function. If the minimal gain is 0,
> -	 * the g(x) is likely to be far from the linear, like g(x) = a / (b * x + c).
> -	 * To avoid unexpected changes to the gain by the AGC algorithm (abrupt near
> -	 * one edge, and very small near the other) we limit the range of the gain
> -	 * values used.
> -	 */
> -	if (!again_min_) {
> -		LOG(IPASoft, Warning) << "Minimum gain is zero, that can't be linear";
> -		again_min_ = std::min(100, again_min_ / 2 + again_max_ / 2);
> +	int32_t again_min = gain_info.min().get<int32_t>();
> +	int32_t again_max = gain_info.max().get<int32_t>();
> +
> +	if (camHelper_) {
> +		again_min_ = camHelper_->gain(again_min);
> +		again_max_ = camHelper_->gain(again_max);
> +		againMinStep_ = (again_max_ - again_min_) / 100.0;
> +	} else {
> +		/*
> +		 * The camera sensor gain (g) is usually not equal to the value written
> +		 * into the gain register (x). But the way how the AGC algorithm changes
> +		 * the gain value to make the total exposure closer to the optimum assumes
> +		 * that g(x) is not too far from linear function. If the minimal gain is 0,
> +		 * the g(x) is likely to be far from the linear, like g(x) = a / (b * x + c).
> +		 * To avoid unexpected changes to the gain by the AGC algorithm (abrupt near
> +		 * one edge, and very small near the other) we limit the range of the gain
> +		 * values used.
> +		 */
> +		again_max_ = again_max;
> +		if (!again_min) {
> +			LOG(IPASoft, Warning) << "Minimum gain is zero, that can't be linear";
> +			again_min_ = std::min(100, again_min / 2 + again_max / 2);
> +		}
> +		againMinStep_ = 1.0;
>  	}
>  
>  	LOG(IPASoft, Info) << "Exposure " << exposure_min_ << "-" << exposure_max_
> -			   << ", gain " << again_min_ << "-" << again_max_;
> +			   << ", gain " << again_min_ << "-" << again_max_
> +			   << " (" << againMinStep_ << ")";
>  
>  	return 0;
>  }
> @@ -252,12 +273,14 @@ void IPASoftSimple::processStats(const ControlList &sensorControls)
>  	ControlList ctrls(sensorControls);
>  
>  	exposure_ = ctrls.get(V4L2_CID_EXPOSURE).get<int32_t>();
> -	again_ = ctrls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();
> +	int32_t again = ctrls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();
> +	again_ = camHelper_ ? camHelper_->gain(again) : again;
>  
>  	updateExposure(exposureMSV);
>  
>  	ctrls.set(V4L2_CID_EXPOSURE, exposure_);
> -	ctrls.set(V4L2_CID_ANALOGUE_GAIN, again_);
> +	ctrls.set(V4L2_CID_ANALOGUE_GAIN,
> +		  static_cast<int32_t>(camHelper_ ? camHelper_->gainCode(again_) : again_));
>  
>  	ignore_updates_ = 2;
>  
> @@ -276,7 +299,7 @@ void IPASoftSimple::updateExposure(double exposureMSV)
>  	static constexpr uint8_t kExpNumeratorUp = kExpDenominator + 1;
>  	static constexpr uint8_t kExpNumeratorDown = kExpDenominator - 1;
>  
> -	int next;
> +	double next;
>  
>  	if (exposureMSV < kExposureOptimal - kExposureSatisfactory) {
>  		next = exposure_ * kExpNumeratorUp / kExpDenominator;
> @@ -286,18 +309,18 @@ void IPASoftSimple::updateExposure(double exposureMSV)
>  			exposure_ = next;
>  		if (exposure_ >= exposure_max_) {
>  			next = again_ * kExpNumeratorUp / kExpDenominator;
> -			if (next - again_ < 1)
> -				again_ += 1;
> +			if (next - again_ < againMinStep_)
> +				again_ += againMinStep_;
>  			else
>  				again_ = next;
>  		}
>  	}
>  
>  	if (exposureMSV > kExposureOptimal + kExposureSatisfactory) {
> -		if (exposure_ == exposure_max_ && again_ != again_min_) {
> +		if (exposure_ == exposure_max_ && again_ > again_min_) {
>  			next = again_ * kExpNumeratorDown / kExpDenominator;
> -			if (again_ - next < 1)
> -				again_ -= 1;
> +			if (again_ - next < againMinStep_)
> +				again_ -= againMinStep_;
>  			else
>  				again_ = next;
>  		} else {
> diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
> index ac796b9b..22639407 100644
> --- a/src/libcamera/pipeline/simple/simple.cpp
> +++ b/src/libcamera/pipeline/simple/simple.cpp
> @@ -526,7 +526,7 @@ int SimpleCameraData::init()
>  	 * Instantiate Soft ISP if this is enabled for the given driver and no converter is used.
>  	 */
>  	if (!converter_ && pipe->swIspEnabled()) {
> -		swIsp_ = std::make_unique<SoftwareIsp>(pipe, sensor_->controls());
> +		swIsp_ = std::make_unique<SoftwareIsp>(pipe, sensor_.get());
>  		if (!swIsp_->isValid()) {
>  			LOG(SimplePipeline, Warning)
>  				<< "Failed to create software ISP, disabling software debayering";
> diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
> index 9b49be41..ea4d96e4 100644
> --- a/src/libcamera/software_isp/software_isp.cpp
> +++ b/src/libcamera/software_isp/software_isp.cpp
> @@ -60,9 +60,9 @@ LOG_DEFINE_CATEGORY(SoftwareIsp)
>  /**
>   * \brief Constructs SoftwareIsp object
>   * \param[in] pipe The pipeline handler in use
> - * \param[in] sensorControls ControlInfoMap describing the controls supported by the sensor
> + * \param[in] sensor Pointer to the CameraSensor instance owned by the pipeline handler
>   */
> -SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const ControlInfoMap &sensorControls)
> +SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor)
>  	: debayer_(nullptr),
>  	  debayerParams_{ DebayerParams::kGain10, DebayerParams::kGain10, DebayerParams::kGain10, 0.5f, 0 },
>  	  dmaHeap_(DmaHeap::DmaHeapFlag::Cma | DmaHeap::DmaHeapFlag::System)
> @@ -97,10 +97,10 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const ControlInfoMap &sensorCont
>  		return;
>  	}
>  
> -	int ret = ipa_->init(IPASettings{ "No cfg file", "No sensor model" },
> +	int ret = ipa_->init(IPASettings{ "No cfg file", sensor->model() },
>  			     debayer_->getStatsFD(),
>  			     sharedParams_.fd(),
> -			     sensorControls);
> +			     sensor->controls());
>  	if (ret) {
>  		LOG(SoftwareIsp, Error) << "IPA init failed";
>  		debayer_.reset();

Patch
diff mbox series

diff --git a/include/libcamera/internal/software_isp/software_isp.h b/include/libcamera/internal/software_isp/software_isp.h
index 8d25e979..2a6db7ba 100644
--- a/include/libcamera/internal/software_isp/software_isp.h
+++ b/include/libcamera/internal/software_isp/software_isp.h
@@ -26,6 +26,7 @@ 
 #include <libcamera/ipa/soft_ipa_interface.h>
 #include <libcamera/ipa/soft_ipa_proxy.h>
 
+#include "libcamera/internal/camera_sensor.h"
 #include "libcamera/internal/dma_heaps.h"
 #include "libcamera/internal/pipeline_handler.h"
 #include "libcamera/internal/shared_mem_object.h"
@@ -43,7 +44,7 @@  LOG_DECLARE_CATEGORY(SoftwareIsp)
 class SoftwareIsp
 {
 public:
-	SoftwareIsp(PipelineHandler *pipe, const ControlInfoMap &sensorControls);
+	SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor);
 	~SoftwareIsp();
 
 	int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; }
diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
index ac027568..e4d64762 100644
--- a/src/ipa/simple/soft_simple.cpp
+++ b/src/ipa/simple/soft_simple.cpp
@@ -22,6 +22,8 @@ 
 #include "libcamera/internal/software_isp/debayer_params.h"
 #include "libcamera/internal/software_isp/swisp_stats.h"
 
+#include "libipa/camera_sensor_helper.h"
+
 #include "black_level.h"
 
 namespace libcamera {
@@ -67,18 +69,27 @@  private:
 	DebayerParams *params_;
 	SwIspStats *stats_;
 	BlackLevel blackLevel_;
+	std::unique_ptr<CameraSensorHelper> camHelper_;
 
 	int32_t exposure_min_, exposure_max_;
-	int32_t again_min_, again_max_;
-	int32_t again_, exposure_;
+	int32_t exposure_;
+	double again_min_, again_max_, againMinStep_;
+	double again_;
 	unsigned int ignore_updates_;
 };
 
-int IPASoftSimple::init([[maybe_unused]] const IPASettings &settings,
+int IPASoftSimple::init(const IPASettings &settings,
 			const SharedFD &fdStats,
 			const SharedFD &fdParams,
 			const ControlInfoMap &sensorInfoMap)
 {
+	camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel);
+	if (camHelper_ == nullptr) {
+		LOG(IPASoft, Warning)
+			<< "Failed to create camera sensor helper for "
+			<< settings.sensorModel;
+	}
+
 	fdStats_ = fdStats;
 	if (!fdStats_.isValid()) {
 		LOG(IPASoft, Error) << "Invalid Statistics handle";
@@ -132,25 +143,35 @@  int IPASoftSimple::configure(const ControlInfoMap &sensorInfoMap)
 		exposure_min_ = 1;
 	}
 
-	again_min_ = gain_info.min().get<int32_t>();
-	again_max_ = gain_info.max().get<int32_t>();
-	/*
-	 * The camera sensor gain (g) is usually not equal to the value written
-	 * into the gain register (x). But the way how the AGC algorithm changes
-	 * the gain value to make the total exposure closer to the optimum assumes
-	 * that g(x) is not too far from linear function. If the minimal gain is 0,
-	 * the g(x) is likely to be far from the linear, like g(x) = a / (b * x + c).
-	 * To avoid unexpected changes to the gain by the AGC algorithm (abrupt near
-	 * one edge, and very small near the other) we limit the range of the gain
-	 * values used.
-	 */
-	if (!again_min_) {
-		LOG(IPASoft, Warning) << "Minimum gain is zero, that can't be linear";
-		again_min_ = std::min(100, again_min_ / 2 + again_max_ / 2);
+	int32_t again_min = gain_info.min().get<int32_t>();
+	int32_t again_max = gain_info.max().get<int32_t>();
+
+	if (camHelper_) {
+		again_min_ = camHelper_->gain(again_min);
+		again_max_ = camHelper_->gain(again_max);
+		againMinStep_ = (again_max_ - again_min_) / 100.0;
+	} else {
+		/*
+		 * The camera sensor gain (g) is usually not equal to the value written
+		 * into the gain register (x). But the way how the AGC algorithm changes
+		 * the gain value to make the total exposure closer to the optimum assumes
+		 * that g(x) is not too far from linear function. If the minimal gain is 0,
+		 * the g(x) is likely to be far from the linear, like g(x) = a / (b * x + c).
+		 * To avoid unexpected changes to the gain by the AGC algorithm (abrupt near
+		 * one edge, and very small near the other) we limit the range of the gain
+		 * values used.
+		 */
+		again_max_ = again_max;
+		if (!again_min) {
+			LOG(IPASoft, Warning) << "Minimum gain is zero, that can't be linear";
+			again_min_ = std::min(100, again_min / 2 + again_max / 2);
+		}
+		againMinStep_ = 1.0;
 	}
 
 	LOG(IPASoft, Info) << "Exposure " << exposure_min_ << "-" << exposure_max_
-			   << ", gain " << again_min_ << "-" << again_max_;
+			   << ", gain " << again_min_ << "-" << again_max_
+			   << " (" << againMinStep_ << ")";
 
 	return 0;
 }
@@ -252,12 +273,14 @@  void IPASoftSimple::processStats(const ControlList &sensorControls)
 	ControlList ctrls(sensorControls);
 
 	exposure_ = ctrls.get(V4L2_CID_EXPOSURE).get<int32_t>();
-	again_ = ctrls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();
+	int32_t again = ctrls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();
+	again_ = camHelper_ ? camHelper_->gain(again) : again;
 
 	updateExposure(exposureMSV);
 
 	ctrls.set(V4L2_CID_EXPOSURE, exposure_);
-	ctrls.set(V4L2_CID_ANALOGUE_GAIN, again_);
+	ctrls.set(V4L2_CID_ANALOGUE_GAIN,
+		  static_cast<int32_t>(camHelper_ ? camHelper_->gainCode(again_) : again_));
 
 	ignore_updates_ = 2;
 
@@ -276,7 +299,7 @@  void IPASoftSimple::updateExposure(double exposureMSV)
 	static constexpr uint8_t kExpNumeratorUp = kExpDenominator + 1;
 	static constexpr uint8_t kExpNumeratorDown = kExpDenominator - 1;
 
-	int next;
+	double next;
 
 	if (exposureMSV < kExposureOptimal - kExposureSatisfactory) {
 		next = exposure_ * kExpNumeratorUp / kExpDenominator;
@@ -286,18 +309,18 @@  void IPASoftSimple::updateExposure(double exposureMSV)
 			exposure_ = next;
 		if (exposure_ >= exposure_max_) {
 			next = again_ * kExpNumeratorUp / kExpDenominator;
-			if (next - again_ < 1)
-				again_ += 1;
+			if (next - again_ < againMinStep_)
+				again_ += againMinStep_;
 			else
 				again_ = next;
 		}
 	}
 
 	if (exposureMSV > kExposureOptimal + kExposureSatisfactory) {
-		if (exposure_ == exposure_max_ && again_ != again_min_) {
+		if (exposure_ == exposure_max_ && again_ > again_min_) {
 			next = again_ * kExpNumeratorDown / kExpDenominator;
-			if (again_ - next < 1)
-				again_ -= 1;
+			if (again_ - next < againMinStep_)
+				again_ -= againMinStep_;
 			else
 				again_ = next;
 		} else {
diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
index ac796b9b..22639407 100644
--- a/src/libcamera/pipeline/simple/simple.cpp
+++ b/src/libcamera/pipeline/simple/simple.cpp
@@ -526,7 +526,7 @@  int SimpleCameraData::init()
 	 * Instantiate Soft ISP if this is enabled for the given driver and no converter is used.
 	 */
 	if (!converter_ && pipe->swIspEnabled()) {
-		swIsp_ = std::make_unique<SoftwareIsp>(pipe, sensor_->controls());
+		swIsp_ = std::make_unique<SoftwareIsp>(pipe, sensor_.get());
 		if (!swIsp_->isValid()) {
 			LOG(SimplePipeline, Warning)
 				<< "Failed to create software ISP, disabling software debayering";
diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
index 9b49be41..ea4d96e4 100644
--- a/src/libcamera/software_isp/software_isp.cpp
+++ b/src/libcamera/software_isp/software_isp.cpp
@@ -60,9 +60,9 @@  LOG_DEFINE_CATEGORY(SoftwareIsp)
 /**
  * \brief Constructs SoftwareIsp object
  * \param[in] pipe The pipeline handler in use
- * \param[in] sensorControls ControlInfoMap describing the controls supported by the sensor
+ * \param[in] sensor Pointer to the CameraSensor instance owned by the pipeline handler
  */
-SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const ControlInfoMap &sensorControls)
+SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor)
 	: debayer_(nullptr),
 	  debayerParams_{ DebayerParams::kGain10, DebayerParams::kGain10, DebayerParams::kGain10, 0.5f, 0 },
 	  dmaHeap_(DmaHeap::DmaHeapFlag::Cma | DmaHeap::DmaHeapFlag::System)
@@ -97,10 +97,10 @@  SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const ControlInfoMap &sensorCont
 		return;
 	}
 
-	int ret = ipa_->init(IPASettings{ "No cfg file", "No sensor model" },
+	int ret = ipa_->init(IPASettings{ "No cfg file", sensor->model() },
 			     debayer_->getStatsFD(),
 			     sharedParams_.fd(),
-			     sensorControls);
+			     sensor->controls());
 	if (ret) {
 		LOG(SoftwareIsp, Error) << "IPA init failed";
 		debayer_.reset();