[v5,45/47] ipa: libipa: agc: Work without `CameraSensorHelper`
diff mbox series

Message ID 20260817114349.994123-46-barnabas.pocze@ideasonboard.com
State New
Headers show
Series
  • ipa: libipa: agc rework
Related show

Commit Message

Barnabás Pőcze Aug. 17, 2026, 11:43 a.m. UTC
`AgcMeanLuminance` can operate without a `CameraSensorHelper`, but in that
case it assumes an "ideal" gain model, where gains are truly continuous,
and a gain of x will apply a gain of exactly x to the result. This is not
a good fit when only gain codes are available.

So now that the agc algorithm from the simple ipa has been extracted (AgcMSV),
let's use that in `AgcAlgorithm` to be able to always provide some level of
automatic exposure/gain control. Even though the main use case for operating
without a known gain model is empirically determining the gain model using
the manual controls.

Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
---
 src/ipa/ipu3/algorithms/agc.cpp     |   4 +-
 src/ipa/libipa/agc.cpp              | 202 +++++++++++++++++++---------
 src/ipa/libipa/agc.h                |   9 +-
 src/ipa/mali-c55/algorithms/agc.cpp |   4 +-
 src/ipa/rkisp1/algorithms/agc.cpp   |   4 +-
 5 files changed, 145 insertions(+), 78 deletions(-)

Patch
diff mbox series

diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp
index c9ea02ed00..975f82eaf3 100644
--- a/src/ipa/ipu3/algorithms/agc.cpp
+++ b/src/ipa/ipu3/algorithms/agc.cpp
@@ -68,12 +68,11 @@  int Agc::init(IPAContext &context, const ValueNode &tuningData)
 {
 	int ret;
 
-	ret = agc_.init(tuningData);
+	ret = agc_.init(tuningData, context.camHelper.get());
 	if (ret)
 		return ret;
 
 	ret = agc_.configure(context.configuration.agc, context.activeState.agc, {
-		.sensor = context.camHelper.get(),
 		.sensorInfo = context.sensorInfo,
 		.sensorControls = context.sensorControls,
 		.ctrlMap = context.ctrlMap,
@@ -98,7 +97,6 @@  int Agc::configure(IPAContext &context,
 	bdsGrid_ = context.configuration.grid.bdsGrid;
 
 	return agc_.configure(context.configuration.agc, context.activeState.agc, {
-		.sensor = context.camHelper.get(),
 		.sensorInfo = context.sensorInfo,
 		.sensorControls = context.sensorControls,
 		.ctrlMap = context.ctrlMap,
diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp
index ff1e8d5a84..d327548979 100644
--- a/src/ipa/libipa/agc.cpp
+++ b/src/ipa/libipa/agc.cpp
@@ -11,10 +11,12 @@ 
 #include <array>
 #include <chrono>
 #include <optional>
+#include <variant>
 
 #include <linux/v4l2-controls.h>
 
 #include <libcamera/base/log.h>
+#include <libcamera/base/utils.h>
 
 #include <libcamera/control_ids.h>
 #include <libcamera/controls.h>
@@ -79,6 +81,9 @@  namespace agc {
  * \var agc::Session::maxAnalogueGain
  * \brief Maximum analogue gain for the streaming session
  *
+ * \var agc::Session::defAnalogueGain
+ * \brief Default analogue gain of the configured sensor
+ *
  * \var agc::Session::minFrameDuration
  * \brief Minimum frame duration for the streaming session
  *
@@ -217,9 +222,6 @@  namespace agc {
  * \struct AgcAlgorithm::ConfigurationParams
  * \brief Parameters for AgcAlgorithm::configure()
  *
- * \var AgcAlgorithm::ConfigurationParams::sensor
- * \brief CameraSensorHelper for the sensor
- *
  * \var AgcAlgorithm::ConfigurationParams::sensorInfo
  * \brief Current configuration of the sensor
  *
@@ -264,11 +266,18 @@  namespace agc {
 /**
  * \brief Load tuning data
  */
-int AgcAlgorithm::init(const ValueNode &tuningData)
+int AgcAlgorithm::init(const ValueNode &tuningData, CameraSensorHelper *sensor)
 {
-	int ret = impl_.parseTuningData(tuningData);
-	if (ret)
-		return ret;
+	if (sensor) {
+		auto &impl = impl_.emplace<AgcMeanLuminance>();
+		int ret = impl.parseTuningData(tuningData);
+		if (ret)
+			return ret;
+	} else {
+		impl_.emplace<AgcMSV>();
+	}
+
+	sensor_ = sensor;
 
 	return 0;
 }
@@ -303,10 +312,14 @@  int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state,
 	int32_t defExposure = v4l2Exposure.def().get<int32_t>();
 
 	/* Compute the analogue gain limits. */
+	const auto extractGain = [&](const ControlValue &v) {
+		auto gainCode = v.get<int32_t>();
+		return sensor_ ? sensor_->gain(gainCode) : gainCode;
+	};
 	const ControlInfo &v4l2Gain = config.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second;
-	float minGain = config.sensor->gain(v4l2Gain.min().get<int32_t>());
-	float maxGain = config.sensor->gain(v4l2Gain.max().get<int32_t>());
-	float defGain = config.sensor->gain(v4l2Gain.def().get<int32_t>());
+	float minGain = extractGain(v4l2Gain.min());
+	float maxGain = extractGain(v4l2Gain.max());
+	float defGain = extractGain(v4l2Gain.def());
 
 	LOG(Agc, Debug)
 		<< "exposure:[" << minExposure << ',' << maxExposure << ']'
@@ -345,28 +358,21 @@  int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state,
 	session.maxExposureTime = maxExposure * session.lineDuration;
 	session.minAnalogueGain = minGain;
 	session.maxAnalogueGain = maxGain;
+	session.defAnalogueGain = defGain;
 	session.minFrameDuration = std::chrono::microseconds(frameDurations[0]);
 	session.maxFrameDuration = std::chrono::microseconds(frameDurations[1]);
 
-	impl_.configure(session.lineDuration, config.sensor);
-	impl_.resetFrameCount();
-
 	/* Configure the default exposure and gain. */
 	state = {};
 	state.automatic.gain = session.minAnalogueGain;
 	state.automatic.exposure = defExposure;
 	state.automatic.quantizationGain = 1;
 	state.automatic.digitalGain = 1;
-	state.automatic.yTarget = impl_.effectiveYTarget(0, 1);
 	state.manual.gain = state.automatic.gain;
 	state.manual.exposure = state.automatic.exposure;
 	state.autoExposureEnabled = session.autoAllowed;
 	state.autoGainEnabled = session.autoAllowed;
 	state.exposureValue = 0;
-	state.constraintMode =
-		static_cast<controls::AeConstraintModeEnum>(impl_.constraintModes().begin()->first);
-	state.exposureMode =
-		static_cast<controls::AeExposureModeEnum>(impl_.exposureModeHelpers().begin()->first);
 	state.minFrameDuration = session.minFrameDuration;
 	state.maxFrameDuration = session.maxFrameDuration;
 
@@ -417,25 +423,57 @@  int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state,
 	add(controls::AnalogueGainMode,
 	    controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual);
 
-	if (session.autoAllowed) {
-		config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f);
-
-		{
-			std::vector<ControlValue> options;
-			for (const auto &[id, _] : impl_.constraintModes())
-				options.emplace_back(id);
-
-			config.ctrlMap[&controls::AeConstraintMode] = ControlInfo(options);
-		}
-
-		{
-			std::vector<ControlValue> options;
-			for (const auto &[id, _] : impl_.exposureModeHelpers())
-				options.emplace_back(id);
-
-			config.ctrlMap[&controls::AeExposureMode] = ControlInfo(options);
-		}
-	}
+	std::visit(utils::overloaded{
+		[&](AgcMSV&) {
+			/* no constraint/exposure mode support */
+			state.constraintMode = controls::AeConstraintModeEnum::ConstraintNormal;
+			state.exposureMode = controls::AeExposureModeEnum::ExposureNormal;
+
+			state.automatic.yTarget = (2.5 - 1) / (5 - 1); /* \todo hack? */
+
+			if (session.autoAllowed) {
+				config.ctrlMap[&controls::AeConstraintMode] = ControlInfo(
+					std::array{ ControlValue(state.constraintMode) }
+				);
+
+				config.ctrlMap[&controls::AeExposureMode] = ControlInfo(
+					std::array{ ControlValue(state.exposureMode) }
+				);
+			}
+		},
+		[&](AgcMeanLuminance& impl) {
+			state.constraintMode =
+				static_cast<controls::AeConstraintModeEnum>(impl.constraintModes().begin()->first);
+			state.exposureMode =
+				static_cast<controls::AeExposureModeEnum>(impl.exposureModeHelpers().begin()->first);
+
+			state.automatic.yTarget = impl.effectiveYTarget(0, 1);
+
+			ASSERT(sensor_);
+			impl.configure(session.lineDuration, sensor_);
+			impl.resetFrameCount();
+
+			if (session.autoAllowed) {
+				config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f);
+
+				{
+					std::vector<ControlValue> options;
+					for (const auto &[id, _] : impl.constraintModes())
+						options.emplace_back(id);
+
+					config.ctrlMap[&controls::AeConstraintMode] = ControlInfo(options);
+				}
+
+				{
+					std::vector<ControlValue> options;
+					for (const auto &[id, _] : impl.exposureModeHelpers())
+						options.emplace_back(id);
+
+					config.ctrlMap[&controls::AeExposureMode] = ControlInfo(options);
+				}
+			}
+		},
+	}, impl_);
 
 	return 0;
 }
@@ -625,36 +663,68 @@  void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state,
 		maxAnalogueGain = frameContext.gain;
 	}
 
-	/*
-	 * The Agc algorithm needs to know the effective exposure value that was
-	 * applied to the sensor when the statistics were collected.
-	 */
-	utils::Duration effectiveExposureValue =
-		lineDuration * params->exposure * params->gain;
-
-	impl_.setLimits(minExposureTime, maxExposureTime,
-			minAnalogueGain, maxAnalogueGain,
-			std::move(params->additionalConstraints));
-
-	const auto &newEv = impl_.calculateNewEv({
-		.traits = params->traits,
-		.yHist = params->yHist,
-		.effectiveExposureValue = effectiveExposureValue,
-		.constraintModeIndex = frameContext.constraintMode,
-		.exposureModeIndex = frameContext.exposureMode,
-		.lux = params->lux,
-		.exposureCompensation = pow(2.0, frameContext.exposureValue),
-	});
-
-	/* Update the estimated exposure and gain. */
-	state.automatic.exposure = newEv.exposureTime / lineDuration;
-	state.automatic.gain = newEv.analogueGain;
-	state.automatic.quantizationGain = newEv.quantizationGain;
-	state.automatic.digitalGain = newEv.digitalGain;
-	state.automatic.yTarget = newEv.yTarget;
+	std::visit(utils::overloaded{
+		[&](AgcMSV& impl) {
+			impl.setLimits({
+				.exposure = {
+					uint32_t(minExposureTime / lineDuration),
+					uint32_t(maxExposureTime / lineDuration),
+				},
+				.gain = {
+					minAnalogueGain,
+					maxAnalogueGain,
+				},
+				/* gain codes -> step size of 1 */
+				.gainMinStep = 1,
+				/* assume default gain is close to 1.0 */
+				.gain1 = session.defAnalogueGain,
+			});
+
+			const auto& newEv = impl.calculateNewEv({
+				.yHist = params->yHist,
+				.exposure = params->exposure,
+				.gain = params->gain,
+			});
+
+			state.automatic.exposure = newEv.exposure;
+			state.automatic.gain = newEv.analogueGain;
+		},
+		[&](AgcMeanLuminance& impl) {
+			/*
+			 * The Agc algorithm needs to know the effective exposure
+			 * value that was applied to the sensor when the statistics
+			 * were collected.
+			 */
+			utils::Duration effectiveExposureValue =
+				lineDuration * params->exposure * params->gain;
+
+			impl.setLimits(minExposureTime, maxExposureTime,
+				       minAnalogueGain, maxAnalogueGain,
+				       std::move(params->additionalConstraints));
+
+			const auto &newEv = impl.calculateNewEv({
+				.traits = params->traits,
+				.yHist = params->yHist,
+				.effectiveExposureValue = effectiveExposureValue,
+				.constraintModeIndex = frameContext.constraintMode,
+				.exposureModeIndex = frameContext.exposureMode,
+				.lux = params->lux,
+				.exposureCompensation = pow(2.0, frameContext.exposureValue),
+			});
+
+			/* Update the estimated exposure and gain. */
+			state.automatic.exposure = newEv.exposureTime / lineDuration;
+			state.automatic.gain = newEv.analogueGain;
+			state.automatic.quantizationGain = newEv.quantizationGain;
+			state.automatic.digitalGain = newEv.digitalGain;
+			state.automatic.yTarget = newEv.yTarget;
+		},
+	}, impl_);
+
+	const utils::Duration newExposureTime = state.automatic.exposure * lineDuration;
 
 	LOG(Agc, Debug)
-		<< "exposure-time:" << newEv.exposureTime
+		<< "exposure-time:" << newExposureTime
 		<< " analogue-gain:" << state.automatic.gain
 		<< " quantization-gain:" << state.automatic.quantizationGain
 		<< " digital-gain:" << state.automatic.digitalGain;
@@ -664,7 +734,7 @@  void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state,
 	 * the minimum frame duration when we have short exposures.
 	 */
 	processFrameDuration(session, frameContext,
-			     std::max(frameContext.minFrameDuration, newEv.exposureTime));
+			     std::max(frameContext.minFrameDuration, newExposureTime));
 
 	fillMetadata(session, frameContext, metadata);
 }
diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h
index 612ac539da..b31f7de8fa 100644
--- a/src/ipa/libipa/agc.h
+++ b/src/ipa/libipa/agc.h
@@ -9,6 +9,7 @@ 
 
 #include <optional>
 #include <utility>
+#include <variant>
 
 #include <linux/v4l2-controls.h>
 
@@ -18,6 +19,7 @@ 
 #include <libcamera/ipa/core_ipa_interface.h>
 
 #include "agc_mean_luminance.h"
+#include "agc_msv.h"
 #include "camera_sensor_helper.h"
 #include "histogram.h"
 
@@ -32,6 +34,7 @@  struct Session {
 	utils::Duration maxExposureTime;
 	double minAnalogueGain;
 	double maxAnalogueGain;
+	double defAnalogueGain;
 	utils::Duration minFrameDuration;
 	utils::Duration maxFrameDuration;
 	utils::Duration lineDuration;
@@ -113,7 +116,6 @@  class AgcAlgorithm
 {
 public:
 	struct ConfigurationParams {
-		const CameraSensorHelper *sensor;
 		const IPACameraSensorInfo &sensorInfo;
 		const ControlInfoMap &sensorControls;
 		ControlInfoMap::Map &ctrlMap;
@@ -129,7 +131,7 @@  public:
 		double lux = 0;
 	};
 
-	int init(const ValueNode &tuningData);
+	int init(const ValueNode &tuningData, CameraSensorHelper *sensor);
 
 	int configure(agc::Session &session, agc::ActiveState &state,
 		      const ConfigurationParams &config);
@@ -151,7 +153,8 @@  private:
 			  const agc::FrameContext &frameContext,
 			  ControlList &metadata);
 
-	AgcMeanLuminance impl_;
+	std::variant<AgcMSV, AgcMeanLuminance> impl_;
+	CameraSensorHelper *sensor_ = nullptr;
 };
 
 } /* namespace ipa */
diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp
index 4b675eb7e5..140f34ad50 100644
--- a/src/ipa/mali-c55/algorithms/agc.cpp
+++ b/src/ipa/mali-c55/algorithms/agc.cpp
@@ -122,12 +122,11 @@  Agc::Agc()
 
 int Agc::init(IPAContext &context, const ValueNode &tuningData)
 {
-	int ret = agc_.init(tuningData);
+	int ret = agc_.init(tuningData, context.camHelper.get());
 	if (ret)
 		return ret;
 
 	ret = agc_.configure(context.configuration.agc, context.activeState.agc, {
-		.sensor = context.camHelper.get(),
 		.sensorInfo = context.sensorInfo,
 		.sensorControls = context.sensorControls,
 		.ctrlMap = context.ctrlMap,
@@ -147,7 +146,6 @@  int Agc::configure(IPAContext &context,
 		return ret;
 
 	ret = agc_.configure(context.configuration.agc, context.activeState.agc, {
-		.sensor = context.camHelper.get(),
 		.sensorInfo = context.sensorInfo,
 		.sensorControls = context.sensorControls,
 		.ctrlMap = context.ctrlMap,
diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
index 4c2a066e86..41a8cd581f 100644
--- a/src/ipa/rkisp1/algorithms/agc.cpp
+++ b/src/ipa/rkisp1/algorithms/agc.cpp
@@ -136,12 +136,11 @@  int Agc::init(IPAContext &context, const ValueNode &tuningData)
 {
 	int ret;
 
-	ret = agc_.init(tuningData);
+	ret = agc_.init(tuningData, context.camHelper.get());
 	if (ret)
 		return ret;
 
 	ret = agc_.configure(context.configuration.agc, context.activeState.agc, {
-		.sensor = context.camHelper.get(),
 		.sensorInfo = context.sensorInfo,
 		.sensorControls = context.sensorControls,
 		.ctrlMap = context.ctrlMap,
@@ -167,7 +166,6 @@  int Agc::init(IPAContext &context, const ValueNode &tuningData)
 int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
 {
 	int ret = agc_.configure(context.configuration.agc, context.activeState.agc, {
-		.sensor = context.camHelper.get(),
 		.sensorInfo = context.sensorInfo,
 		.sensorControls = context.sensorControls,
 		.ctrlMap = context.ctrlMap,