@@ -72,12 +72,12 @@ namespace agc {
* \struct Session
* \brief Session configuration for AgcAlgorithm
*
+ * \var Session::minExposure
+ * \brief Minimum exposure (in lines) for the streaming session
+ *
* \var Session::minExposureTime
* \brief Minimum exposure time for the streaming session
*
- * \var Session::maxExposureTime
- * \brief Maximum exposure time for the streaming session
- *
* \var Session::minAnalogueGain
* \brief Minimum analogue gain for the streaming session
*
@@ -102,6 +102,11 @@ namespace agc {
* \var Session::sensor.outputSize
* \brief Configured output size of the sensor
*
+ * \var Session::sensor.exposureMargin
+ * \brief Exposure margin of the sensor
+ *
+ * \sa CameraSensorHelper::exposureMargin()
+ *
* \var Session::autoAllowed
* \copybrief AgcAlgorithm::ConfigurationParams::autoAllowed
* \sa AgcAlgorithm::ConfigurationParams::autoAllowed
@@ -473,6 +478,23 @@ void fillMissing(AgcMeanLuminance::Configuration &config)
}
}
+[[nodiscard]]
+uint32_t clampExposure(const agc::Session &session, const agc::ActiveState &state,
+ uint32_t exposure)
+{
+ const uint32_t maxFrame = state.maxFrameDuration / session.lineDuration;
+ const uint32_t maxExposure = maxFrame - session.sensor.exposureMargin;
+
+ return std::clamp(exposure, session.minExposure, maxExposure);
+}
+
+[[nodiscard]]
+uint32_t clampExposure(const agc::Session &session, const agc::ActiveState &state,
+ utils::Duration exposureTime)
+{
+ return clampExposure(session, state, exposureTime / session.lineDuration);
+}
+
} /* namespace */
/**
@@ -620,7 +642,12 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state,
session.lineDuration = lineLength * 1.0s / config.sensorInfo.pixelRate;
session.sensor.outputSize = config.sensorInfo.outputSize;
- const double lineDurationUs = session.lineDuration.get<std::micro>();
+ auto exposureMargin = sensor_ ? sensor_->exposureMargin() : std::nullopt;
+ session.sensor.exposureMargin = exposureMargin.value_or(4);
+ if (!exposureMargin)
+ LOG(Agc, Warning)
+ << "Sensor exposure margin not available, using "
+ << session.sensor.exposureMargin;
/*
* Compute exposure time limits from the V4L2_CID_EXPOSURE control
@@ -629,7 +656,6 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state,
const ControlInfo &v4l2Exposure = config.sensorControls.find(V4L2_CID_EXPOSURE)->second;
int32_t minExposure = v4l2Exposure.min().get<int32_t>();
- int32_t maxExposure = v4l2Exposure.max().get<int32_t>();
int32_t defExposure = v4l2Exposure.def().get<int32_t>();
/* Compute the analogue gain limits. */
@@ -642,28 +668,46 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state,
float maxGain = extractGain(v4l2Gain.max());
float defGain = extractGain(v4l2Gain.def());
- LOG(Agc, Debug)
- << "exposure: [" << minExposure << ',' << maxExposure << "], "
- << "gain: [" << minGain << ',' << maxGain << "], "
- << "line-duration: " << session.lineDuration << ", "
- << "sensor-output: " << session.sensor.outputSize;
-
-
/*
* When the AGC computes the new exposure values for a frame, it needs
* to know the limits for exposure time and analogue gain. As it depends
* on the sensor, update it with the controls.
- *
- * \todo take VBLANK into account for maximum exposure time
*/
+ session.minExposure = minExposure;
session.minExposureTime = minExposure * session.lineDuration;
- session.maxExposureTime = maxExposure * session.lineDuration;
session.minAnalogueGain = minGain;
session.maxAnalogueGain = maxGain;
session.defAnalogueGain = defGain;
session.minFrameDuration = config.sensorInfo.minFrameLength * session.lineDuration;
session.maxFrameDuration = config.sensorInfo.maxFrameLength * session.lineDuration;
+ /*
+ * The assumption is that the minimum exposure time is independent of vblank,
+ * only the maximum exposure time is affected by it.
+ */
+ const uint32_t maxExposure = config.sensorInfo.maxFrameLength - session.sensor.exposureMargin;
+ const utils::Duration maxExposureTime = maxExposure * session.lineDuration;
+
+ LOG(Agc, Debug)
+ << "exposure: [" << session.minExposure << ',' << maxExposure << "], "
+ << "exposure-time: [" << session.minExposureTime << ',' << maxExposureTime << "], "
+ << "gain: [" << session.minAnalogueGain << ',' << session.maxAnalogueGain << "], "
+ << "line-length: " << lineLength << ", "
+ << "line-duration: " << session.lineDuration << ", "
+ << "frame-height: [" << config.sensorInfo.minFrameLength << ',' << config.sensorInfo.maxFrameLength << "], "
+ << "sensor-output: " << session.sensor.outputSize << ", "
+ << "sensor-exposure-margin: " << session.sensor.exposureMargin;
+
+ /*
+ * This is a sanity check. If this fails, something is very likely off.
+ * Equality could theoretically be allowed, but it is not so that
+ * `(frameDuration / lineDuration) - exposureMargin` is no less
+ * than the minimum exposure time even if the division is slightly
+ * inaccurate.
+ */
+ if (session.minExposure + session.sensor.exposureMargin >= config.sensorInfo.minFrameLength)
+ return -EINVAL;
+
/* Configure the default exposure and gain. */
state = {};
state.automatic.gain = session.minAnalogueGain;
@@ -755,9 +799,10 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state,
minGain, maxGain, defGain
};
config.ctrlMap[&controls::ExposureTime] = ControlInfo{
- static_cast<int32_t>(minExposure * lineDurationUs),
- static_cast<int32_t>(maxExposure * lineDurationUs),
- static_cast<int32_t>(defExposure * lineDurationUs),
+ static_cast<int32_t>(session.minExposureTime.get<std::micro>()),
+ static_cast<int32_t>(maxExposureTime.get<std::micro>()),
+ static_cast<int32_t>(state.automatic.exposure *
+ session.lineDuration.get<std::micro>()),
};
config.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{
static_cast<int64_t>(session.minFrameDuration.get<std::micro>()),
@@ -864,7 +909,7 @@ void AgcAlgorithm::queueRequest(const agc::Session &session, agc::ActiveState &s
const auto &exposure = controls.get(controls::ExposureTime);
if (exposure && !state.autoExposureEnabled) {
- state.manual.exposure = *exposure * 1.0us / session.lineDuration;
+ state.manual.exposure = clampExposure(session, state, *exposure * 1.0us);
LOG(Agc, Debug) << "Set exposure to " << state.manual.exposure;
}
@@ -1002,9 +1047,8 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state,
if (state.autoExposureEnabled) {
minExposureTime = session.minExposureTime;
- maxExposureTime = std::clamp(state.maxFrameDuration,
- session.minExposureTime,
- session.maxExposureTime);
+ maxExposureTime = state.maxFrameDuration -
+ session.sensor.exposureMargin * lineDuration;
} else {
minExposureTime = lineDuration * state.manual.exposure;
maxExposureTime = minExposureTime;
@@ -1076,6 +1120,8 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state,
},
}, impl_);
+ state.automatic.exposure = clampExposure(session, state, state.automatic.exposure);
+
const utils::Duration newExposureTime = state.automatic.exposure * lineDuration;
LOG(Agc, Debug)
@@ -1106,7 +1152,7 @@ void AgcAlgorithm::processFrameDuration(const agc::Session &session,
const auto frameLength = std::max<uint32_t>(
std::min(std::ceil(frameContext.minFrameDuration / lineDuration),
frameContext.maxFrameDuration / lineDuration),
- frameContext.exposure);
+ frameContext.exposure + session.sensor.exposureMargin);
frameContext.vblank = frameLength - session.sensor.outputSize.height;
@@ -36,8 +36,8 @@ class Histogram;
namespace agc {
struct Session {
+ uint32_t minExposure;
utils::Duration minExposureTime;
- utils::Duration maxExposureTime;
double minAnalogueGain;
double maxAnalogueGain;
double defAnalogueGain;
@@ -47,6 +47,7 @@ struct Session {
struct {
Size outputSize;
+ uint32_t exposureMargin;
} sensor;
bool autoAllowed;