[v2,01/10] ipa: camera_sensor_helper: Introduce exposureMargin()
diff mbox series

Message ID 20251028-exposure-limits-v2-1-a8b5a318323e@ideasonboard.com
State New
Headers show
Series
  • libipa: agc: Calculate exposure limits
Related show

Commit Message

Jacopo Mondi Oct. 28, 2025, 9:31 a.m. UTC
There is a margin between the maximum achievable exposure time
and the frame length. The value is sensor-dependent and should
be used to adjust the maximum achievable exposure time whenever
the frame length changes.

Introduce in the CameraSensorHelper an exposureMargin() function
that returns a std::optional<> with the sensor-specific margin.
Use a std::optional<> to easily identify CameraSensorHelper
implementations that do not populate exposureMargin_ and warn
about possibly sub-optimal results in exposure time calculations.

This feature is already implemented in the RPi CamHelper class
hierarchy with the name of frameIntegrationDiff.

Populate the CameraSensorHelper instances with an
exposureMargin_. The value of the exposure margin comes from
the mainline driver version of each sensor, and it has been
compared with the frameIntegrationDiff_ value in the rpi
camera helpers.

The only outliner is the Vd56g3 sensor, whose exposure margin from
the driver differs from the RPi camera helper.

Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
---
 src/ipa/libipa/camera_sensor_helper.cpp | 72 +++++++++++++++++++++++++++++++++
 src/ipa/libipa/camera_sensor_helper.h   |  2 +
 2 files changed, 74 insertions(+)

Comments

Laurent Pinchart Nov. 2, 2025, 7:48 p.m. UTC | #1
Hi Jacopo,

Thank you for the patch.

On Tue, Oct 28, 2025 at 10:31:47AM +0100, Jacopo Mondi wrote:
> There is a margin between the maximum achievable exposure time
> and the frame length. The value is sensor-dependent and should
> be used to adjust the maximum achievable exposure time whenever
> the frame length changes.

The line length is short, incorrect text editor configuration ?

> 
> Introduce in the CameraSensorHelper an exposureMargin() function
> that returns a std::optional<> with the sensor-specific margin.
> Use a std::optional<> to easily identify CameraSensorHelper
> implementations that do not populate exposureMargin_ and warn
> about possibly sub-optimal results in exposure time calculations.
> 
> This feature is already implemented in the RPi CamHelper class
> hierarchy with the name of frameIntegrationDiff.

It's private information there, used in the virtual
CamHelper::getBlanking() function. I wonder if we should expose the raw
value, or move the calculations that use it to the CameraSensorHelper
class, the same way we have gain() and gainCode() functions. Can we
guarantee that all sensors use a fixed margin ?

> Populate the CameraSensorHelper instances with an
> exposureMargin_. The value of the exposure margin comes from
> the mainline driver version of each sensor, and it has been
> compared with the frameIntegrationDiff_ value in the rpi
> camera helpers.
> 
> The only outliner is the Vd56g3 sensor, whose exposure margin from
> the driver differs from the RPi camera helper.
> 
> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> ---
>  src/ipa/libipa/camera_sensor_helper.cpp | 72 +++++++++++++++++++++++++++++++++
>  src/ipa/libipa/camera_sensor_helper.h   |  2 +
>  2 files changed, 74 insertions(+)
> 
> diff --git a/src/ipa/libipa/camera_sensor_helper.cpp b/src/ipa/libipa/camera_sensor_helper.cpp
> index ef3bd0d621e6bead2b3f8a7fe6988f642e4791f7..6d14405657903704d6ffee72eb336ec8b78db1cd 100644
> --- a/src/ipa/libipa/camera_sensor_helper.cpp
> +++ b/src/ipa/libipa/camera_sensor_helper.cpp
> @@ -154,6 +154,18 @@ double CameraSensorHelper::quantizeGain(double _gain, double *quantizationGain)
>  	return g;
>  }
>  
> +/**
> + * \fn CameraSensorHelper::exposureMargin()
> + * \brief Fetch the integration time margin
> + *
> + * This function returns the number of lines that represent the minimum
> + * difference between the frame length and the maximum achievable integration
> + * time. If it is unknown an empty optional is returned.
> + *
> + * \return The minimum difference between the exposure time and the frame
> + * length in lines, or an empty optional
> + */
> +
>  /**
>   * \struct CameraSensorHelper::AnalogueGainLinear
>   * \brief Analogue gain constants for the linear gain model
> @@ -229,6 +241,13 @@ double CameraSensorHelper::quantizeGain(double _gain, double *quantizationGain)
>   * sensor specific. Use this variable to store the values at init time.
>   */
>  
> +/**
> + * \var CameraSensorHelper::exposureMargin_
> + * \brief The smallest margin between the integration time and the frame lenght
> + * in lines
> + * \sa CameraSensorHelper::exposureMargin()
> + */
> +
>  /**
>   * \class CameraSensorHelperFactoryBase
>   * \brief Base class for camera sensor helper factories
> @@ -385,6 +404,7 @@ public:
>  	{
>  		/* Power-on default value: 168 at 12bits. */
>  		blackLevel_ = 2688;
> +		exposureMargin_ = 4;
>  	}
>  
>  	uint32_t gainCode(double gain) const override
> @@ -474,6 +494,11 @@ REGISTER_CAMERA_SENSOR_HELPER("ar0144", CameraSensorHelperAr0144)
>  class CameraSensorHelperAr0521 : public CameraSensorHelper
>  {
>  public:
> +	CameraSensorHelperAr0521()
> +	{
> +		exposureMargin_ = 4;
> +	}
> +
>  	uint32_t gainCode(double gain) const override
>  	{
>  		gain = std::clamp(gain, 1.0, 15.5);
> @@ -504,6 +529,7 @@ public:
>  		/* From datasheet: 64 at 10bits. */
>  		blackLevel_ = 4096;
>  		gain_ = AnalogueGainLinear{ 100, 0, 0, 1024 };
> +		exposureMargin_ = 4;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("gc05a2", CameraSensorHelperGc05a2)
> @@ -516,6 +542,7 @@ public:
>  		/* From datasheet: 64 at 10bits. */
>  		blackLevel_ = 4096;
>  		gain_ = AnalogueGainLinear{ 100, 0, 0, 1024 };
> +		exposureMargin_ = 16;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("gc08a3", CameraSensorHelperGc08a3)
> @@ -526,6 +553,7 @@ public:
>  	CameraSensorHelperHm1246()
>  	{
>  		gain_ = AnalogueGainLinear{ 1, 16, 0, 16 };
> +		exposureMargin_ = 2;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("hm1246", CameraSensorHelperHm1246)
> @@ -538,6 +566,7 @@ public:
>  		/* From datasheet: 64 at 10bits. */
>  		blackLevel_ = 4096;
>  		gain_ = AnalogueGainLinear{ 0, 512, -1, 512 };
> +		exposureMargin_ = 10;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("imx214", CameraSensorHelperImx214)
> @@ -550,6 +579,7 @@ public:
>  		/* From datasheet: 64 at 10bits. */
>  		blackLevel_ = 4096;
>  		gain_ = AnalogueGainLinear{ 0, 256, -1, 256 };
> +		exposureMargin_ = 4;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("imx219", CameraSensorHelperImx219)
> @@ -562,6 +592,7 @@ public:
>  		/* From datasheet: 0x40 at 10bits. */
>  		blackLevel_ = 4096;
>  		gain_ = AnalogueGainLinear{ 0, 512, -1, 512 };
> +		exposureMargin_ = 10;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("imx258", CameraSensorHelperImx258)
> @@ -574,6 +605,7 @@ public:
>  		/* From datasheet: 0x32 at 10bits. */
>  		blackLevel_ = 3200;
>  		gain_ = AnalogueGainLinear{ 0, 2048, -1, 2048 };
> +		exposureMargin_ = 4;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("imx283", CameraSensorHelperImx283)
> @@ -586,6 +618,7 @@ public:
>  		/* From datasheet: 0xf0 at 12bits. */
>  		blackLevel_ = 3840;
>  		gain_ = AnalogueGainExp{ 1.0, expGainDb(0.3) };
> +		exposureMargin_ = 2;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("imx290", CameraSensorHelperImx290)
> @@ -596,6 +629,11 @@ public:
>  	CameraSensorHelperImx296()
>  	{
>  		gain_ = AnalogueGainExp{ 1.0, expGainDb(0.1) };
> +		/*
> +		 * The driver doesn't apply any margin. Use the value
> +		 * in RPi's CamHelper.
> +		 */
> +		exposureMargin_ = 4;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("imx296", CameraSensorHelperImx296)
> @@ -613,6 +651,7 @@ public:
>  		/* From datasheet: 0x32 at 10bits. */
>  		blackLevel_ = 3200;
>  		gain_ = AnalogueGainExp{ 1.0, expGainDb(0.3) };
> +		exposureMargin_ = 9;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("imx335", CameraSensorHelperImx335)
> @@ -623,6 +662,7 @@ public:
>  	CameraSensorHelperImx415()
>  	{
>  		gain_ = AnalogueGainExp{ 1.0, expGainDb(0.3) };
> +		exposureMargin_ = 8;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("imx415", CameraSensorHelperImx415)
> @@ -638,6 +678,7 @@ public:
>  	CameraSensorHelperImx477()
>  	{
>  		gain_ = AnalogueGainLinear{ 0, 1024, -1, 1024 };
> +		exposureMargin_ = 22;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("imx477", CameraSensorHelperImx477)
> @@ -652,6 +693,7 @@ public:
>  		 * This has been validated with some empirical testing only.
>  		 */
>  		gain_ = AnalogueGainLinear{ 1, 0, 0, 128 };
> +		exposureMargin_ = 4;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("ov2685", CameraSensorHelperOv2685)
> @@ -662,6 +704,7 @@ public:
>  	CameraSensorHelperOv2740()
>  	{
>  		gain_ = AnalogueGainLinear{ 1, 0, 0, 128 };
> +		exposureMargin_ = 8;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("ov2740", CameraSensorHelperOv2740)
> @@ -674,6 +717,7 @@ public:
>  		/* From datasheet: 0x40 at 12bits. */
>  		blackLevel_ = 1024;
>  		gain_ = AnalogueGainLinear{ 1, 0, 0, 128 };
> +		exposureMargin_ = 4;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("ov4689", CameraSensorHelperOv4689)
> @@ -686,6 +730,14 @@ public:
>  		/* From datasheet: 0x10 at 10bits. */
>  		blackLevel_ = 1024;
>  		gain_ = AnalogueGainLinear{ 1, 0, 0, 16 };
> +		/*
> +		 * Very convoluted in the driver that however applies a margin
> +		 * of 4 lines when setting vts.
> +		 *
> +		 * 	cap_vts = cap_shutter + 4;
> +		 * 	ret = ov5640_set_vts(sensor, cap_vts);
> +		 */
> +		exposureMargin_ = 4;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("ov5640", CameraSensorHelperOv5640)
> @@ -696,6 +748,7 @@ public:
>  	CameraSensorHelperOv5647()
>  	{
>  		gain_ = AnalogueGainLinear{ 1, 0, 0, 16 };
> +		exposureMargin_ = 4;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("ov5647", CameraSensorHelperOv5647)
> @@ -706,6 +759,7 @@ public:
>  	CameraSensorHelperOv5670()
>  	{
>  		gain_ = AnalogueGainLinear{ 1, 0, 0, 128 };
> +		exposureMargin_ = 8;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("ov5670", CameraSensorHelperOv5670)
> @@ -718,6 +772,7 @@ public:
>  		/* From Linux kernel driver: 0x40 at 10bits. */
>  		blackLevel_ = 4096;
>  		gain_ = AnalogueGainLinear{ 1, 0, 0, 128 };
> +		exposureMargin_ = 4;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("ov5675", CameraSensorHelperOv5675)
> @@ -728,6 +783,7 @@ public:
>  	CameraSensorHelperOv5693()
>  	{
>  		gain_ = AnalogueGainLinear{ 1, 0, 0, 16 };
> +		exposureMargin_ = 8;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("ov5693", CameraSensorHelperOv5693)
> @@ -738,6 +794,7 @@ public:
>  	CameraSensorHelperOv64a40()
>  	{
>  		gain_ = AnalogueGainLinear{ 1, 0, 0, 128 };
> +		exposureMargin_ = 32;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("ov64a40", CameraSensorHelperOv64a40)
> @@ -754,6 +811,7 @@ public:
>  		 * See: https://patchwork.linuxtv.org/project/linux-media/patch/20221106171129.166892-2-nicholas@rothemail.net/#142267
>  		 */
>  		gain_ = AnalogueGainLinear{ 1, 0, 0, 128 };
> +		exposureMargin_ = 4;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("ov8858", CameraSensorHelperOv8858)
> @@ -764,6 +822,7 @@ public:
>  	CameraSensorHelperOv8865()
>  	{
>  		gain_ = AnalogueGainLinear{ 1, 0, 0, 128 };
> +		exposureMargin_ = 8;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("ov8865", CameraSensorHelperOv8865)
> @@ -774,6 +833,7 @@ public:
>  	CameraSensorHelperOv13858()
>  	{
>  		gain_ = AnalogueGainLinear{ 1, 0, 0, 128 };
> +		exposureMargin_ = 8;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("ov13858", CameraSensorHelperOv13858)
> @@ -786,6 +846,7 @@ public:
>  		/* From datasheet: 0x40 at 10bits. */
>  		blackLevel_ = 4096;
>  		gain_ = AnalogueGainLinear{ 0, 32, -1, 32 };
> +		exposureMargin_ = 64;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("vd55g1", CameraSensorHelperVd55g1)
> @@ -798,6 +859,17 @@ public:
>  		/* From datasheet: 0x40 at 10bits. */
>  		blackLevel_ = 4096;
>  		gain_ = AnalogueGainLinear{ 0, 32, -1, 32 };
> +		/*
> +		 * The mainline driver version has
> +		 * #define VD56G3_EXPOSURE_MARGIN		75
> +		 * while the frameIntegrationDiff value in the RPi cam
> +		 * helper for this sensor has
> +		 * static constexpr int frameIntegrationDiff = 61;
> +		 *
> +		 * Trust the driver and report 75 which is also larger and
> +		 * hence "safer"
> +		 */

I think you've clarified on IRC that 75 is the right value, so I
wouldn't mention the RPi helpers here. A patch to fix the RPi helper
would be good.

> +		exposureMargin_ = 75;
>  	}
>  };
>  REGISTER_CAMERA_SENSOR_HELPER("vd56g3", CameraSensorHelperVd56g3)
> diff --git a/src/ipa/libipa/camera_sensor_helper.h b/src/ipa/libipa/camera_sensor_helper.h
> index bd3d0beec77f006b68fecf45eee850c5283fefa5..57dd4c37ed76733710f1673a98e82ae47475baa7 100644
> --- a/src/ipa/libipa/camera_sensor_helper.h
> +++ b/src/ipa/libipa/camera_sensor_helper.h
> @@ -30,6 +30,7 @@ public:
>  	virtual uint32_t gainCode(double gain) const;
>  	virtual double gain(uint32_t gainCode) const;
>  	double quantizeGain(double gain, double *quantizationGain) const;
> +	std::optional<uint32_t> exposureMargin() const { return exposureMargin_; }
>  
>  protected:
>  	struct AnalogueGainLinear {
> @@ -46,6 +47,7 @@ protected:
>  
>  	std::optional<int16_t> blackLevel_;
>  	std::variant<std::monostate, AnalogueGainLinear, AnalogueGainExp> gain_;
> +	std::optional<uint32_t> exposureMargin_;
>  
>  private:
>  	LIBCAMERA_DISABLE_COPY_AND_MOVE(CameraSensorHelper)
Barnabás Pőcze Nov. 3, 2025, 11:44 a.m. UTC | #2
2025. 11. 02. 20:48 keltezéssel, Laurent Pinchart írta:
> Hi Jacopo,
> 
> Thank you for the patch.
> 
> On Tue, Oct 28, 2025 at 10:31:47AM +0100, Jacopo Mondi wrote:
>> There is a margin between the maximum achievable exposure time
>> and the frame length. The value is sensor-dependent and should
>> be used to adjust the maximum achievable exposure time whenever
>> the frame length changes.
> 
> The line length is short, incorrect text editor configuration ?
> 
>>
>> Introduce in the CameraSensorHelper an exposureMargin() function
>> that returns a std::optional<> with the sensor-specific margin.
>> Use a std::optional<> to easily identify CameraSensorHelper
>> implementations that do not populate exposureMargin_ and warn
>> about possibly sub-optimal results in exposure time calculations.
>>
>> This feature is already implemented in the RPi CamHelper class
>> hierarchy with the name of frameIntegrationDiff.
> 
> It's private information there, used in the virtual
> CamHelper::getBlanking() function. I wonder if we should expose the raw
> value, or move the calculations that use it to the CameraSensorHelper
> class, the same way we have gain() and gainCode() functions. Can we
> guarantee that all sensors use a fixed margin ?
> 
>> Populate the CameraSensorHelper instances with an
>> exposureMargin_. The value of the exposure margin comes from
>> the mainline driver version of each sensor, and it has been
>> compared with the frameIntegrationDiff_ value in the rpi
>> camera helpers.
>>
>> The only outliner is the Vd56g3 sensor, whose exposure margin from
>> the driver differs from the RPi camera helper.
>>
>> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
>> ---
>>   src/ipa/libipa/camera_sensor_helper.cpp | 72 +++++++++++++++++++++++++++++++++
>>   src/ipa/libipa/camera_sensor_helper.h   |  2 +
>>   2 files changed, 74 insertions(+)
>>
>> diff --git a/src/ipa/libipa/camera_sensor_helper.cpp b/src/ipa/libipa/camera_sensor_helper.cpp
>> index ef3bd0d621e6bead2b3f8a7fe6988f642e4791f7..6d14405657903704d6ffee72eb336ec8b78db1cd 100644
>> --- a/src/ipa/libipa/camera_sensor_helper.cpp
>> +++ b/src/ipa/libipa/camera_sensor_helper.cpp
> [...]
>> @@ -798,6 +859,17 @@ public:
>>   		/* From datasheet: 0x40 at 10bits. */
>>   		blackLevel_ = 4096;
>>   		gain_ = AnalogueGainLinear{ 0, 32, -1, 32 };
>> +		/*
>> +		 * The mainline driver version has
>> +		 * #define VD56G3_EXPOSURE_MARGIN		75
>> +		 * while the frameIntegrationDiff value in the RPi cam
>> +		 * helper for this sensor has
>> +		 * static constexpr int frameIntegrationDiff = 61;
>> +		 *
>> +		 * Trust the driver and report 75 which is also larger and
>> +		 * hence "safer"
>> +		 */
> 
> I think you've clarified on IRC that 75 is the right value, so I
> wouldn't mention the RPi helpers here. A patch to fix the RPi helper
> would be good.

I believe that has already been done: b1f09c013a01a82c739f0e30b71fd8d000ef5655
("ipa: rpi: vd56g3: Fix frameIntegrationDiff value")
( https://patchwork.libcamera.org/patch/24837/ )


> 
>> +		exposureMargin_ = 75;
>>   	}
>>   };
>>   REGISTER_CAMERA_SENSOR_HELPER("vd56g3", CameraSensorHelperVd56g3)
> [...]

Patch
diff mbox series

diff --git a/src/ipa/libipa/camera_sensor_helper.cpp b/src/ipa/libipa/camera_sensor_helper.cpp
index ef3bd0d621e6bead2b3f8a7fe6988f642e4791f7..6d14405657903704d6ffee72eb336ec8b78db1cd 100644
--- a/src/ipa/libipa/camera_sensor_helper.cpp
+++ b/src/ipa/libipa/camera_sensor_helper.cpp
@@ -154,6 +154,18 @@  double CameraSensorHelper::quantizeGain(double _gain, double *quantizationGain)
 	return g;
 }
 
+/**
+ * \fn CameraSensorHelper::exposureMargin()
+ * \brief Fetch the integration time margin
+ *
+ * This function returns the number of lines that represent the minimum
+ * difference between the frame length and the maximum achievable integration
+ * time. If it is unknown an empty optional is returned.
+ *
+ * \return The minimum difference between the exposure time and the frame
+ * length in lines, or an empty optional
+ */
+
 /**
  * \struct CameraSensorHelper::AnalogueGainLinear
  * \brief Analogue gain constants for the linear gain model
@@ -229,6 +241,13 @@  double CameraSensorHelper::quantizeGain(double _gain, double *quantizationGain)
  * sensor specific. Use this variable to store the values at init time.
  */
 
+/**
+ * \var CameraSensorHelper::exposureMargin_
+ * \brief The smallest margin between the integration time and the frame lenght
+ * in lines
+ * \sa CameraSensorHelper::exposureMargin()
+ */
+
 /**
  * \class CameraSensorHelperFactoryBase
  * \brief Base class for camera sensor helper factories
@@ -385,6 +404,7 @@  public:
 	{
 		/* Power-on default value: 168 at 12bits. */
 		blackLevel_ = 2688;
+		exposureMargin_ = 4;
 	}
 
 	uint32_t gainCode(double gain) const override
@@ -474,6 +494,11 @@  REGISTER_CAMERA_SENSOR_HELPER("ar0144", CameraSensorHelperAr0144)
 class CameraSensorHelperAr0521 : public CameraSensorHelper
 {
 public:
+	CameraSensorHelperAr0521()
+	{
+		exposureMargin_ = 4;
+	}
+
 	uint32_t gainCode(double gain) const override
 	{
 		gain = std::clamp(gain, 1.0, 15.5);
@@ -504,6 +529,7 @@  public:
 		/* From datasheet: 64 at 10bits. */
 		blackLevel_ = 4096;
 		gain_ = AnalogueGainLinear{ 100, 0, 0, 1024 };
+		exposureMargin_ = 4;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("gc05a2", CameraSensorHelperGc05a2)
@@ -516,6 +542,7 @@  public:
 		/* From datasheet: 64 at 10bits. */
 		blackLevel_ = 4096;
 		gain_ = AnalogueGainLinear{ 100, 0, 0, 1024 };
+		exposureMargin_ = 16;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("gc08a3", CameraSensorHelperGc08a3)
@@ -526,6 +553,7 @@  public:
 	CameraSensorHelperHm1246()
 	{
 		gain_ = AnalogueGainLinear{ 1, 16, 0, 16 };
+		exposureMargin_ = 2;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("hm1246", CameraSensorHelperHm1246)
@@ -538,6 +566,7 @@  public:
 		/* From datasheet: 64 at 10bits. */
 		blackLevel_ = 4096;
 		gain_ = AnalogueGainLinear{ 0, 512, -1, 512 };
+		exposureMargin_ = 10;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("imx214", CameraSensorHelperImx214)
@@ -550,6 +579,7 @@  public:
 		/* From datasheet: 64 at 10bits. */
 		blackLevel_ = 4096;
 		gain_ = AnalogueGainLinear{ 0, 256, -1, 256 };
+		exposureMargin_ = 4;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("imx219", CameraSensorHelperImx219)
@@ -562,6 +592,7 @@  public:
 		/* From datasheet: 0x40 at 10bits. */
 		blackLevel_ = 4096;
 		gain_ = AnalogueGainLinear{ 0, 512, -1, 512 };
+		exposureMargin_ = 10;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("imx258", CameraSensorHelperImx258)
@@ -574,6 +605,7 @@  public:
 		/* From datasheet: 0x32 at 10bits. */
 		blackLevel_ = 3200;
 		gain_ = AnalogueGainLinear{ 0, 2048, -1, 2048 };
+		exposureMargin_ = 4;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("imx283", CameraSensorHelperImx283)
@@ -586,6 +618,7 @@  public:
 		/* From datasheet: 0xf0 at 12bits. */
 		blackLevel_ = 3840;
 		gain_ = AnalogueGainExp{ 1.0, expGainDb(0.3) };
+		exposureMargin_ = 2;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("imx290", CameraSensorHelperImx290)
@@ -596,6 +629,11 @@  public:
 	CameraSensorHelperImx296()
 	{
 		gain_ = AnalogueGainExp{ 1.0, expGainDb(0.1) };
+		/*
+		 * The driver doesn't apply any margin. Use the value
+		 * in RPi's CamHelper.
+		 */
+		exposureMargin_ = 4;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("imx296", CameraSensorHelperImx296)
@@ -613,6 +651,7 @@  public:
 		/* From datasheet: 0x32 at 10bits. */
 		blackLevel_ = 3200;
 		gain_ = AnalogueGainExp{ 1.0, expGainDb(0.3) };
+		exposureMargin_ = 9;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("imx335", CameraSensorHelperImx335)
@@ -623,6 +662,7 @@  public:
 	CameraSensorHelperImx415()
 	{
 		gain_ = AnalogueGainExp{ 1.0, expGainDb(0.3) };
+		exposureMargin_ = 8;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("imx415", CameraSensorHelperImx415)
@@ -638,6 +678,7 @@  public:
 	CameraSensorHelperImx477()
 	{
 		gain_ = AnalogueGainLinear{ 0, 1024, -1, 1024 };
+		exposureMargin_ = 22;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("imx477", CameraSensorHelperImx477)
@@ -652,6 +693,7 @@  public:
 		 * This has been validated with some empirical testing only.
 		 */
 		gain_ = AnalogueGainLinear{ 1, 0, 0, 128 };
+		exposureMargin_ = 4;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("ov2685", CameraSensorHelperOv2685)
@@ -662,6 +704,7 @@  public:
 	CameraSensorHelperOv2740()
 	{
 		gain_ = AnalogueGainLinear{ 1, 0, 0, 128 };
+		exposureMargin_ = 8;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("ov2740", CameraSensorHelperOv2740)
@@ -674,6 +717,7 @@  public:
 		/* From datasheet: 0x40 at 12bits. */
 		blackLevel_ = 1024;
 		gain_ = AnalogueGainLinear{ 1, 0, 0, 128 };
+		exposureMargin_ = 4;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("ov4689", CameraSensorHelperOv4689)
@@ -686,6 +730,14 @@  public:
 		/* From datasheet: 0x10 at 10bits. */
 		blackLevel_ = 1024;
 		gain_ = AnalogueGainLinear{ 1, 0, 0, 16 };
+		/*
+		 * Very convoluted in the driver that however applies a margin
+		 * of 4 lines when setting vts.
+		 *
+		 * 	cap_vts = cap_shutter + 4;
+		 * 	ret = ov5640_set_vts(sensor, cap_vts);
+		 */
+		exposureMargin_ = 4;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("ov5640", CameraSensorHelperOv5640)
@@ -696,6 +748,7 @@  public:
 	CameraSensorHelperOv5647()
 	{
 		gain_ = AnalogueGainLinear{ 1, 0, 0, 16 };
+		exposureMargin_ = 4;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("ov5647", CameraSensorHelperOv5647)
@@ -706,6 +759,7 @@  public:
 	CameraSensorHelperOv5670()
 	{
 		gain_ = AnalogueGainLinear{ 1, 0, 0, 128 };
+		exposureMargin_ = 8;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("ov5670", CameraSensorHelperOv5670)
@@ -718,6 +772,7 @@  public:
 		/* From Linux kernel driver: 0x40 at 10bits. */
 		blackLevel_ = 4096;
 		gain_ = AnalogueGainLinear{ 1, 0, 0, 128 };
+		exposureMargin_ = 4;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("ov5675", CameraSensorHelperOv5675)
@@ -728,6 +783,7 @@  public:
 	CameraSensorHelperOv5693()
 	{
 		gain_ = AnalogueGainLinear{ 1, 0, 0, 16 };
+		exposureMargin_ = 8;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("ov5693", CameraSensorHelperOv5693)
@@ -738,6 +794,7 @@  public:
 	CameraSensorHelperOv64a40()
 	{
 		gain_ = AnalogueGainLinear{ 1, 0, 0, 128 };
+		exposureMargin_ = 32;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("ov64a40", CameraSensorHelperOv64a40)
@@ -754,6 +811,7 @@  public:
 		 * See: https://patchwork.linuxtv.org/project/linux-media/patch/20221106171129.166892-2-nicholas@rothemail.net/#142267
 		 */
 		gain_ = AnalogueGainLinear{ 1, 0, 0, 128 };
+		exposureMargin_ = 4;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("ov8858", CameraSensorHelperOv8858)
@@ -764,6 +822,7 @@  public:
 	CameraSensorHelperOv8865()
 	{
 		gain_ = AnalogueGainLinear{ 1, 0, 0, 128 };
+		exposureMargin_ = 8;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("ov8865", CameraSensorHelperOv8865)
@@ -774,6 +833,7 @@  public:
 	CameraSensorHelperOv13858()
 	{
 		gain_ = AnalogueGainLinear{ 1, 0, 0, 128 };
+		exposureMargin_ = 8;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("ov13858", CameraSensorHelperOv13858)
@@ -786,6 +846,7 @@  public:
 		/* From datasheet: 0x40 at 10bits. */
 		blackLevel_ = 4096;
 		gain_ = AnalogueGainLinear{ 0, 32, -1, 32 };
+		exposureMargin_ = 64;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("vd55g1", CameraSensorHelperVd55g1)
@@ -798,6 +859,17 @@  public:
 		/* From datasheet: 0x40 at 10bits. */
 		blackLevel_ = 4096;
 		gain_ = AnalogueGainLinear{ 0, 32, -1, 32 };
+		/*
+		 * The mainline driver version has
+		 * #define VD56G3_EXPOSURE_MARGIN		75
+		 * while the frameIntegrationDiff value in the RPi cam
+		 * helper for this sensor has
+		 * static constexpr int frameIntegrationDiff = 61;
+		 *
+		 * Trust the driver and report 75 which is also larger and
+		 * hence "safer"
+		 */
+		exposureMargin_ = 75;
 	}
 };
 REGISTER_CAMERA_SENSOR_HELPER("vd56g3", CameraSensorHelperVd56g3)
diff --git a/src/ipa/libipa/camera_sensor_helper.h b/src/ipa/libipa/camera_sensor_helper.h
index bd3d0beec77f006b68fecf45eee850c5283fefa5..57dd4c37ed76733710f1673a98e82ae47475baa7 100644
--- a/src/ipa/libipa/camera_sensor_helper.h
+++ b/src/ipa/libipa/camera_sensor_helper.h
@@ -30,6 +30,7 @@  public:
 	virtual uint32_t gainCode(double gain) const;
 	virtual double gain(uint32_t gainCode) const;
 	double quantizeGain(double gain, double *quantizationGain) const;
+	std::optional<uint32_t> exposureMargin() const { return exposureMargin_; }
 
 protected:
 	struct AnalogueGainLinear {
@@ -46,6 +47,7 @@  protected:
 
 	std::optional<int16_t> blackLevel_;
 	std::variant<std::monostate, AnalogueGainLinear, AnalogueGainExp> gain_;
+	std::optional<uint32_t> exposureMargin_;
 
 private:
 	LIBCAMERA_DISABLE_COPY_AND_MOVE(CameraSensorHelper)