diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp
index 51aa4e51841ca8c6363c0211819a7d14cc3baec6..34cb31a3ef4ff8a4774351e1d1a8b5a46aca33cc 100644
--- a/src/ipa/ipu3/algorithms/agc.cpp
+++ b/src/ipa/ipu3/algorithms/agc.cpp
@@ -236,8 +236,9 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
 	utils::Duration effectiveExposureValue = exposureTime * analogueGain;
 
 	utils::Duration newExposureTime;
+	utils::Duration frameDuration;
 	double aGain, qGain, dGain;
-	std::tie(newExposureTime, aGain, qGain, dGain) =
+	std::tie(newExposureTime, frameDuration, aGain, qGain, dGain) =
 		calculateNewEv(context.activeState.agc.constraintMode,
 			       context.activeState.agc.exposureMode, hist,
 			       effectiveExposureValue);
@@ -254,12 +255,13 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
 	metadata.set(controls::AnalogueGain, frameContext.sensor.gain);
 	metadata.set(controls::ExposureTime, exposureTime.get<std::micro>());
 
-	/* \todo Use VBlank value calculated from each frame exposure. */
+	/* \todo Use frameDuration to calculate the right vblank. */
+
 	uint32_t vTotal = context.configuration.sensor.size.height
 			+ context.configuration.sensor.defVBlank;
-	utils::Duration frameDuration = context.configuration.sensor.lineDuration
-				      * vTotal;
-	metadata.set(controls::FrameDuration, frameDuration.get<std::micro>());
+	utils::Duration defFrameDuration = context.configuration.sensor.lineDuration
+					 * vTotal;
+	metadata.set(controls::FrameDuration, defFrameDuration.get<std::micro>());
 }
 
 REGISTER_IPA_ALGORITHM(Agc, "Agc")
diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp
index 36bdf76dca45c837d5ef4eb03244a43d1262c675..3722602921d6f1716f4cfedcae84023e4bea02a6 100644
--- a/src/ipa/libipa/agc_mean_luminance.cpp
+++ b/src/ipa/libipa/agc_mean_luminance.cpp
@@ -646,10 +646,10 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue)
  * exposure value is filtered to prevent rapid changes from frame to frame, and
  * divided into exposure time, analogue, quantization and digital gain.
  *
- * \return Tuple of exposure time, analogue gain, quantization gain and digital
- * gain
+ * \return Tuple of exposure time, frame duration analogue gain, quantization
+ * gain and digital gain
  */
-std::tuple<utils::Duration, double, double, double>
+std::tuple<utils::Duration, utils::Duration, double, double, double>
 AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex,
 				 uint32_t exposureModeIndex,
 				 const Histogram &yHist,
diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h
index b00772760035a477152d638809ac71fcbc66cb68..71c4951080c728e87530caf189fbd52f92b3d033 100644
--- a/src/ipa/libipa/agc_mean_luminance.h
+++ b/src/ipa/libipa/agc_mean_luminance.h
@@ -80,7 +80,7 @@ public:
 		return controls_;
 	}
 
-	std::tuple<utils::Duration, double, double, double>
+	std::tuple<utils::Duration, utils::Duration, double, double, double>
 	calculateNewEv(uint32_t constraintModeIndex, uint32_t exposureModeIndex,
 		       const Histogram &yHist, utils::Duration effectiveExposureValue);
 
diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp
index 46ba535c67d92bd384095b250b925cdc0da40b38..e9a37a3771cdfbb35ad4c8a373d3189f8a468c7b 100644
--- a/src/ipa/libipa/exposure_mode_helper.cpp
+++ b/src/ipa/libipa/exposure_mode_helper.cpp
@@ -100,9 +100,10 @@ void ExposureModeHelper::setMaxExposure(utils::Duration minExposureTime,
 			<< "Exposure margin not known. Default to 4";
 		margin = { 4 };
 	}
+	exposureMargin_ = margin.value() * lineDuration_;
 
 	maxExposureTime_ = minExposureTime != maxExposureTime
-			 ? maxFrameDuration - margin.value() * lineDuration_
+			 ? maxFrameDuration - exposureMargin_
 			 : minExposureTime;
 }
 
@@ -152,6 +153,7 @@ void ExposureModeHelper::configure(utils::Duration lineDuration,
 	setMaxExposure(minExposureTime, maxExposureTime, frameDuration);
 
 	maxFrameDuration_ = *maxFrameDuration = frameDuration;
+	minFrameDuration_ = minFrameDuration;
 }
 
 /**
@@ -207,6 +209,17 @@ double ExposureModeHelper::clampGain(double gain, double *quantizationGain) cons
 	return sensorHelper_->quantizeGain(clamped, quantizationGain);
 }
 
+utils::Duration ExposureModeHelper::frameDurationFromExposure(utils::Duration exposureTime) const
+{
+	/*
+	 * The maximum exposure value has already been clamped to the maximum
+	 * frame duration. Re-apply the exposure margin and make sure we don't
+	 * go below the minium frame duration.
+	 */
+	utils::Duration frameDuration = exposureTime + exposureMargin_;
+	return std::max(frameDuration, minFrameDuration_);
+}
+
 /**
  * \brief Split exposure into exposure time and gain
  * \param[in] exposure Exposure value
@@ -244,7 +257,7 @@ double ExposureModeHelper::clampGain(double gain, double *quantizationGain) cons
  * \return Tuple of exposure time, analogue gain, quantization gain and digital
  * gain
  */
-std::tuple<utils::Duration, double, double, double>
+std::tuple<utils::Duration, utils::Duration, double, double, double>
 ExposureModeHelper::splitExposure(utils::Duration exposure) const
 {
 	ASSERT(maxExposureTime_);
@@ -266,8 +279,8 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const
 		gain = clampGain(minGain_, &quantGain2);
 		quantGain *= quantGain2;
 
-		return { exposureTime, gain, quantGain,
-			 exposure / (exposureTime * gain * quantGain) };
+		return { exposureTime, frameDurationFromExposure(exposureTime),
+			 gain, quantGain, exposure / (exposureTime * gain * quantGain) };
 	}
 
 	double stageGain = clampGain(1.0);
@@ -292,8 +305,8 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const
 			gain = clampGain(exposure / exposureTime, &quantGain2);
 			quantGain *= quantGain2;
 
-			return { exposureTime, gain, quantGain,
-				 exposure / (exposureTime * gain * quantGain) };
+			return { exposureTime, frameDurationFromExposure(exposureTime),
+				 gain, quantGain, exposure / (exposureTime * gain * quantGain) };
 		}
 
 		/* Clamp the exposureTime to stageExposureTime and regulate gain. */
@@ -302,8 +315,8 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const
 			gain = clampGain(exposure / exposureTime, &quantGain2);
 			quantGain *= quantGain2;
 
-			return { exposureTime, gain, quantGain,
-				 exposure / (exposureTime * gain * quantGain) };
+			return { exposureTime, frameDurationFromExposure(exposureTime),
+				 gain, quantGain, exposure / (exposureTime * gain * quantGain) };
 		}
 
 		lastStageGain = stageGain;
@@ -320,8 +333,8 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const
 	gain = clampGain(exposure / exposureTime, &quantGain2);
 	quantGain *= quantGain2;
 
-	return { exposureTime, gain, quantGain,
-		 exposure / (exposureTime * gain * quantGain) };
+	return { exposureTime, frameDurationFromExposure(exposureTime),
+		 gain, quantGain, exposure / (exposureTime * gain * quantGain) };
 }
 
 /**
diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h
index 492d2787f75945fb487b351a60f13756caec66e0..8dc94cf808945e0ff3cea080046b1cafd453a014 100644
--- a/src/ipa/libipa/exposure_mode_helper.h
+++ b/src/ipa/libipa/exposure_mode_helper.h
@@ -34,7 +34,7 @@ public:
 		       utils::Duration maxFrameDuration,
 		       double minGain, double maxGain);
 
-	std::tuple<utils::Duration, double, double, double>
+	std::tuple<utils::Duration, utils::Duration, double, double, double>
 	splitExposure(utils::Duration exposure) const;
 
 	utils::Duration minExposureTime() const { return minExposureTime_; }
@@ -50,6 +50,7 @@ private:
 	utils::Duration clampExposureTime(utils::Duration exposureTime,
 					  double *quantizationGain = nullptr) const;
 	double clampGain(double gain, double *quantizationGain = nullptr) const;
+	utils::Duration frameDurationFromExposure(utils::Duration exposureTime) const;
 
 	std::vector<utils::Duration> exposureTimes_;
 	std::vector<double> gains_;
@@ -58,6 +59,8 @@ private:
 	utils::Duration minExposureTime_;
 	utils::Duration maxExposureTime_;
 	utils::Duration maxFrameDuration_;
+	utils::Duration minFrameDuration_;
+	utils::Duration exposureMargin_;
 	double minGain_;
 	double maxGain_;
 	const CameraSensorHelper *sensorHelper_;
diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp
index 4e2fa4386be621c63d992b75d3d9efc525c3d69c..6ecb18d959c7f978966d619beb43e1c2cbfeec5f 100644
--- a/src/ipa/mali-c55/algorithms/agc.cpp
+++ b/src/ipa/mali-c55/algorithms/agc.cpp
@@ -391,9 +391,10 @@ void Agc::process(IPAContext &context,
 	utils::Duration currentShutter = exposure * configuration.sensor.lineDuration;
 	utils::Duration effectiveExposureValue = currentShutter * totalGain;
 
+	utils::Duration frameDuration;
 	utils::Duration shutterTime;
 	double aGain, qGain, dGain;
-	std::tie(shutterTime, aGain, qGain, dGain) =
+	std::tie(shutterTime, frameDuration, aGain, qGain, dGain) =
 		calculateNewEv(activeState.agc.constraintMode,
 			       activeState.agc.exposureMode, statistics_.yHist,
 			       effectiveExposureValue);
diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
index a983efbecbd84fa9e4af8b9642fbdda124ba0b9f..c8a34c3d1bf2ed71b9cf203e48c4b42910eea797 100644
--- a/src/ipa/rkisp1/algorithms/agc.cpp
+++ b/src/ipa/rkisp1/algorithms/agc.cpp
@@ -625,15 +625,17 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
 	setExposureCompensation(pow(2.0, frameContext.agc.exposureValue));
 
 	utils::Duration newExposureTime;
+	utils::Duration frameDuration;
 	double aGain, qGain, dGain;
-	std::tie(newExposureTime, aGain, qGain, dGain) =
+	std::tie(newExposureTime, frameDuration, aGain, qGain, dGain) =
 		calculateNewEv(frameContext.agc.constraintMode,
 			       frameContext.agc.exposureMode,
 			       hist, effectiveExposureValue);
 
 	LOG(RkISP1Agc, Debug)
-		<< "Divided up exposure time, analogue gain, quantization gain"
-		<< " and digital gain are " << newExposureTime << ", " << aGain
+		<< "Divided up exposure time, frame duration, analogue gain,"
+		<< "quantization gain and digital gain are "
+		<< newExposureTime << ", " << frameDuration << ", " << aGain
 		<< ", " << qGain << " and " << dGain;
 
 	IPAActiveState &activeState = context.activeState;
@@ -646,8 +648,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
 	 * Expand the target frame duration so that we do not run faster than
 	 * the minimum frame duration when we have short exposures.
 	 */
-	processFrameDuration(context, frameContext,
-			     std::max(frameContext.agc.minFrameDuration, newExposureTime));
+	processFrameDuration(context, frameContext, frameDuration);
 
 	fillMetadata(context, frameContext, metadata);
 	expMeans_ = {};
