| Message ID | 20260810103846.1075936-22-barnabas.pocze@ideasonboard.com |
|---|---|
| State | New |
| Headers | show |
| Series |
|
| Related | show |
Hi Barnabás On Mon, Aug 10, 2026 at 12:38:17PM +0200, Barnabás Pőcze wrote: > Add a class that implements the `Algorithm` interface using `AgcMeanLuminance` > based on the rkisp1 `Agc` algorithm, with slight adjustments. > > Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> > Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > --- > src/ipa/libipa/agc.cpp | 644 +++++++++++++++++++++++++++++++++++++ > src/ipa/libipa/agc.h | 100 ++++++ > src/ipa/libipa/meson.build | 1 + > 3 files changed, 745 insertions(+) > create mode 100644 src/ipa/libipa/agc.cpp > > diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp > new file mode 100644 > index 0000000000..4825cdb48e > --- /dev/null > +++ b/src/ipa/libipa/agc.cpp > @@ -0,0 +1,644 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2021-2022, 2026 Ideas On Board > + * > + * Generic AGC algorithm > + */ > + > +#include "agc.h" > + > +#include <algorithm> > +#include <array> > +#include <chrono> > +#include <optional> > + > +#include <linux/v4l2-controls.h> > + > +#include <libcamera/base/log.h> > + > +#include <libcamera/control_ids.h> > +#include <libcamera/controls.h> > + > +namespace libcamera { > + > +namespace ipa { > + > +using namespace std::chrono_literals; > + > +LOG_DEFINE_CATEGORY(Agc) > + > +/** > + * \class AgcAlgorithm > + * \brief AgcMeanLuminance wrapper for implementing the Algorithm interface > + * > + * \todo DigitalGain, DigitalGainMode > + */ > + > +/** > + * \struct agc::Session > + * \brief Session configuration for AgcAlgorithm > + * > + * \var agc::Session::minExposureTime > + * \brief Minimum exposure time for the streaming session > + * > + * \var agc::Session::maxExposureTime > + * \brief Maximum exposure time for the streaming session > + * > + * \var agc::Session::minAnalogueGain > + * \brief Minimum analogue gain for the streaming session > + * > + * \var agc::Session::maxAnalogueGain > + * \brief Maximum analogue gain for the streaming session > + * > + * \var agc::Session::minFrameDuration > + * \brief Minimum frame duration for the streaming session > + * > + * \var agc::Session::maxFrameDuration > + * \brief Maximum frame duration for the streaming session > + * > + * \var agc::Session::lineDuration > + * \brief Line duration for the streaming session > + * > + * \var agc::Session::sensor > + * \brief Details of the sensor configuration > + * > + * \var agc::Session::sensor.outputSize > + * \brief Configured output size of the sensor > + * > + * \var agc::Session::autoAllowed > + * \copybrief AgcAlgorithm::ConfigurationParams::autoAllowed > + * \sa AgcAlgorithm::ConfigurationParams::autoAllowed > + */ > + > +/** > + * \struct agc::ActiveState > + * \brief Active state for AgcAlgorithm > + * > + * The \a automatic variables track the latest values computed by algorithm > + * based on the latest processed statistics. All other variables track the > + * consolidated controls requested in queued requests. > + * > + * \var agc::ActiveState::manual > + * \brief Manual exposure time and analog gain (set through requests) > + * > + * \var agc::ActiveState::manual.exposure > + * \brief Manual exposure time expressed as a number of lines as set by the > + * ExposureTime control > + * > + * \var agc::ActiveState::manual.gain > + * \brief Manual analogue gain as set by the AnalogueGain control > + * > + * \var agc::ActiveState::automatic > + * \brief Automatic exposure time and analog gain (computed by the algorithm) > + * > + * \var agc::ActiveState::automatic.exposure > + * \brief Automatic exposure time expressed as a number of lines > + * > + * \var agc::ActiveState::automatic.gain > + * \brief Automatic analogue gain multiplier > + * > + * \var agc::ActiveState::automatic.quantizationGain > + * \brief Automatic quantization gain multiplier > + * > + * \var agc::ActiveState::automatic.yTarget > + * \brief Automatically determined luminance target > + * > + * \var agc::ActiveState::autoExposureEnabled > + * \brief Whether automatic exposure control is enabled by the ExposureTimeMode control > + * > + * \var agc::ActiveState::autoGainEnabled > + * \brief Whether automatic gain control is enabled by the AnalogueGainMode control > + * > + * \var agc::ActiveState::exposureValue > + * \brief Exposure value as set by the ExposureValue control > + * > + * \var agc::ActiveState::constraintMode > + * \brief Constraint mode as set by the AeConstraintMode control > + * > + * \var agc::ActiveState::exposureMode > + * \brief Exposure mode as set by the AeExposureMode control > + * > + * \var agc::ActiveState::minFrameDuration > + * \brief Minimum frame duration as set by the FrameDurationLimits control > + * > + * \var agc::ActiveState::maxFrameDuration > + * \brief Maximum frame duration as set by the FrameDurationLimits control > + */ > + > +/** > + * \struct agc::FrameContext > + * \brief Per-frame context for AgcAlgorithm > + * > + * \var agc::FrameContext::exposure > + * \brief Exposure time expressed as a number of lines computed by the algorithm > + * > + * \var agc::FrameContext::gain > + * \brief Analogue gain multiplier computed by the algorithm > + * > + * The gain should be translated to the sensor specific gain code before applying. > + * > + * \var agc::FrameContext::quantizationGain > + * \brief Quantization gain multiplier computed by the algorithm > + * > + * \var agc::FrameContext::exposureValue > + * \brief Exposure value as set by the ExposureValue control > + * > + * \var agc::FrameContext::yTarget > + * \brief Luminance target computed by the algorithm > + * > + * \var agc::FrameContext::vblank > + * \brief Vertical blanking parameter computed by the algorithm > + * > + * \var agc::FrameContext::autoExposureEnabled > + * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control > + * > + * \var agc::FrameContext::autoGainEnabled > + * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control > + * > + * \var agc::FrameContext::constraintMode > + * \brief Constraint mode as set by the AeConstraintMode control > + * > + * \var agc::FrameContext::exposureMode > + * \brief Exposure mode as set by the AeExposureMode control > + * > + * \var agc::FrameContext::minFrameDuration > + * \brief Minimum frame duration as set by the FrameDurationLimits control > + * > + * \var agc::FrameContext::maxFrameDuration > + * \brief Maximum frame duration as set by the FrameDurationLimits control > + * > + * \var agc::FrameContext::frameDuration > + * \brief The actual FrameDuration used by the algorithm for the frame > + * > + * \var agc::FrameContext::autoExposureModeChange > + * \brief Indicate if autoExposureEnabled has changed from true in the previous > + * frame to false in the current frame, and no manual exposure value has been > + * supplied in the current frame > + * > + * \var agc::FrameContext::autoGainModeChange > + * \brief Indicate if autoGainEnabled has changed from true in the previous > + * frame to false in the current frame, and no manual gain value has been > + * supplied in the current frame > + */ > + > +/** > + * \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 > + * > + * \var AgcAlgorithm::ConfigurationParams::sensorControls > + * \brief ControlInfoMap of the sensor > + * > + * \var AgcAlgorithm::ConfigurationParams::ctrlMap > + * \brief ControlInfoMap::Map to update with controls > + * > + * \var AgcAlgorithm::ConfigurationParams::autoAllowed > + * \brief Whether to enable auto controls > + * > + * If \a false, the algorithm is set up for manual exposure and gain > + * control only, without automatic adjustments. In this mode statistics > + * must not be provided to AgcAlgorithm::process(), and ExposureTimeMode > + * and AnalogueGainMode will only advertise manual control. > + */ > + > +/** > + * \struct AgcAlgorithm::ProcessParams > + * \brief Parameters for AgcAlgorithm::process() > + * > + * \var AgcAlgorithm::ProcessParams::traits > + * \brief Implementation of AgcMeanLuminance::Traits > + * > + * \var AgcAlgorithm::ProcessParams::yHist > + * \brief Luminance histogram of the frame > + * > + * \var AgcAlgorithm::ProcessParams::exposure > + * \brief Effective exposure of the frame > + * > + * \var AgcAlgorithm::ProcessParams::gain > + * \brief Effective gain of the frame > + * > + * \var AgcAlgorithm::ProcessParams::additionalConstraints > + * \brief Additional AgcMeanLuminance::AgcConstraints to apply > + * > + * \var AgcAlgorithm::ProcessParams::lux > + * \brief Effective lux value of the frame > + */ > + > +/** > + * \brief Load tuning data > + */ > +int AgcAlgorithm::init(const ValueNode &tuningData) > +{ > + int ret = impl_.parseTuningData(tuningData); > + if (ret) > + return ret; > + > + return 0; > +} > + > +/** > + * \brief Initialize the session configuration and active state I think we should clearly say the IPA needs to call this as part of init(). > + */ > +int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, > + const ConfigurationParams &config) > +{ > + session = {}; > + session.autoAllowed = config.autoAllowed; > + session.lineDuration = > + config.sensorInfo.minLineLength * 1.0s / config.sensorInfo.pixelRate; > + session.sensor.outputSize = config.sensorInfo.outputSize; > + > + const double lineDurationUs = session.lineDuration.get<std::micro>(); > + > + /* > + * Compute exposure time limits from the V4L2_CID_EXPOSURE control > + * limits and the line duration. > + */ > + > + 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. */ > + 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>()); > + > + LOG(Agc, Debug) > + << "Exposure: [" << minExposure << ", " << maxExposure > + << "], gain: [" << minGain << ", " << maxGain << "]"; > + > + /* > + * Compute the frame duration limits. > + * > + * The frame length is computed assuming a fixed line length combined > + * with the vertical frame sizes. > + */ > + const ControlInfo &v4l2HBlank = config.sensorControls.find(V4L2_CID_HBLANK)->second; > + uint32_t hblank = v4l2HBlank.def().get<int32_t>(); > + uint32_t lineLength = config.sensorInfo.outputSize.width + hblank; > + > + const ControlInfo &v4l2VBlank = config.sensorControls.find(V4L2_CID_VBLANK)->second; > + std::array<uint32_t, 3> frameHeights{ > + v4l2VBlank.min().get<int32_t>() + config.sensorInfo.outputSize.height, > + v4l2VBlank.max().get<int32_t>() + config.sensorInfo.outputSize.height, > + v4l2VBlank.def().get<int32_t>() + config.sensorInfo.outputSize.height, > + }; > + > + std::array<int64_t, 3> frameDurations; > + for (unsigned int i = 0; i < frameHeights.size(); ++i) { > + uint64_t frameSize = lineLength * frameHeights[i]; > + frameDurations[i] = frameSize / (config.sensorInfo.pixelRate / 1000000U); > + } > + > + /* > + * 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.minExposureTime = minExposure * session.lineDuration; > + session.maxExposureTime = maxExposure * session.lineDuration; > + session.minAnalogueGain = minGain; > + session.maxAnalogueGain = maxGain; > + session.minFrameDuration = std::chrono::microseconds(frameDurations[0]); > + session.maxFrameDuration = std::chrono::microseconds(frameDurations[1]); > + > + impl_.configure(session.lineDuration, config.sensor); > + impl_.setLimits(session.minExposureTime, session.maxExposureTime, > + session.minAnalogueGain, session.maxAnalogueGain, > + {}); > + impl_.resetFrameCount(); > + > + /* Configure the default exposure and gain. */ > + state = {}; > + state.automatic.gain = session.minAnalogueGain; > + state.automatic.exposure = 10ms / session.lineDuration; > + state.automatic.quantizationGain = 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; > + > + const auto add = [&](const ControlId &cid, const auto &automatic, const auto &manual) { > + std::array<ControlValue, 2> values; > + size_t count = 0; > + > + if (session.autoAllowed) > + values[count++] = ControlValue(automatic); > + > + values[count++] = ControlValue(manual); > + > + config.ctrlMap[&cid] = ControlInfo{ > + { values.data(), count }, > + ControlValue(session.autoAllowed ? automatic : manual), > + }; > + }; > + > + add(controls::ExposureTimeMode, > + controls::ExposureTimeModeAuto, controls::ExposureTimeModeManual); > + add(controls::AnalogueGainMode, > + controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual); > + > + /* \todo Move this to the `Camera` class. */ > + config.ctrlMap[&controls::AeEnable] = ControlInfo{ > + false, > + session.autoAllowed, > + session.autoAllowed, > + }; > + config.ctrlMap[&controls::AnalogueGain] = ControlInfo{ > + 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), > + }; > + config.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ > + frameDurations[0], > + frameDurations[1], > + Span<const int64_t, 2>{ { frameDurations[2], frameDurations[2] } }, > + }; > + > + if (session.autoAllowed) { > + config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); > + > + for (const auto &[id, info] : impl_.controls()) > + config.ctrlMap[id] = info; > + } else { > + /* The IPA control maps keep their states, so the removal is necessary. */ > + > + config.ctrlMap.erase(&controls::ExposureValue); > + > + for (const auto &[id, info] : impl_.controls()) > + config.ctrlMap.erase(id); > + } > + > + return 0; > +} > + > +/** > + * \brief Handle a \a queueRequest operation > + */ > +void AgcAlgorithm::queueRequest(const agc::Session &session, agc::ActiveState &state, > + agc::FrameContext &frameContext, const ControlList &controls) > +{ > + if (session.autoAllowed) { > + const auto &aeEnable = controls.get(controls::ExposureTimeMode); > + if (aeEnable && > + (*aeEnable == controls::ExposureTimeModeAuto) != state.autoExposureEnabled) { > + state.autoExposureEnabled = (*aeEnable == controls::ExposureTimeModeAuto); > + > + LOG(Agc, Debug) > + << (state.autoExposureEnabled ? "Enabling" : "Disabling") > + << " AGC (exposure)"; > + > + /* > + * If we go from auto -> manual with no manual control > + * set, use the last computed value, which we don't > + * know until prepare() so save this information. > + * > + * \todo Check the previous frame at prepare() time > + * instead of saving a flag here > + */ > + if (!state.autoExposureEnabled && !controls.get(controls::ExposureTime)) > + frameContext.autoExposureModeChange = true; > + } > + > + const auto &agEnable = controls.get(controls::AnalogueGainMode); > + if (agEnable && > + (*agEnable == controls::AnalogueGainModeAuto) != state.autoGainEnabled) { > + state.autoGainEnabled = (*agEnable == controls::AnalogueGainModeAuto); > + > + LOG(Agc, Debug) > + << (state.autoGainEnabled ? "Enabling" : "Disabling") > + << " AGC (gain)"; > + /* > + * If we go from auto -> manual with no manual control > + * set, use the last computed value, which we don't > + * know until prepare() so save this information. > + */ > + if (!state.autoGainEnabled && !controls.get(controls::AnalogueGain)) > + frameContext.autoGainModeChange = true; > + } > + } > + > + const auto &exposure = controls.get(controls::ExposureTime); > + if (exposure && !state.autoExposureEnabled) { > + state.manual.exposure = *exposure * 1.0us / session.lineDuration; > + > + LOG(Agc, Debug) > + << "Set exposure to " << state.manual.exposure; > + } > + > + const auto &gain = controls.get(controls::AnalogueGain); > + if (gain && !state.autoGainEnabled) { > + state.manual.gain = *gain; > + > + LOG(Agc, Debug) << "Set gain to " << state.manual.gain; > + } > + > + frameContext.autoExposureEnabled = state.autoExposureEnabled; > + frameContext.autoGainEnabled = state.autoGainEnabled; > + > + if (!frameContext.autoExposureEnabled) > + frameContext.exposure = state.manual.exposure; > + if (!frameContext.autoGainEnabled) > + frameContext.gain = state.manual.gain; > + > + if (!frameContext.autoExposureEnabled && > + !frameContext.autoGainEnabled) > + frameContext.quantizationGain = 1.0; > + > + const auto &exposureMode = controls.get(controls::AeExposureMode); > + if (exposureMode) > + state.exposureMode = > + static_cast<controls::AeExposureModeEnum>(*exposureMode); > + frameContext.exposureMode = state.exposureMode; > + > + const auto &constraintMode = controls.get(controls::AeConstraintMode); > + if (constraintMode) > + state.constraintMode = > + static_cast<controls::AeConstraintModeEnum>(*constraintMode); > + frameContext.constraintMode = state.constraintMode; > + > + const auto &exposureValue = controls.get(controls::ExposureValue); > + if (exposureValue) > + state.exposureValue = *exposureValue; > + frameContext.exposureValue = state.exposureValue; > + > + const auto &frameDurationLimits = controls.get(controls::FrameDurationLimits); > + if (frameDurationLimits) { > + /* Limit the control value to the limits in ControlInfo */ > + state.minFrameDuration = std::clamp<utils::Duration>( > + std::chrono::microseconds((*frameDurationLimits).front()), > + session.minFrameDuration, session.maxFrameDuration); > + > + state.maxFrameDuration = std::clamp<utils::Duration>( > + std::chrono::microseconds((*frameDurationLimits).back()), > + session.minFrameDuration, session.maxFrameDuration); > + } > + frameContext.minFrameDuration = state.minFrameDuration; > + frameContext.maxFrameDuration = state.maxFrameDuration; > +} > + > +/** > + * \brief Handle a \a prepare operation > + */ > +void AgcAlgorithm::prepare(agc::ActiveState &state, agc::FrameContext &frameContext) > +{ > + uint32_t activeAutoExposure = state.automatic.exposure; > + double activeAutoGain = state.automatic.gain; > + double activeAutoQGain = state.automatic.quantizationGain; > + > + /* Populate exposure and gain in auto mode */ > + if (frameContext.autoExposureEnabled) { > + frameContext.exposure = activeAutoExposure; > + frameContext.quantizationGain = activeAutoQGain; > + } > + if (frameContext.autoGainEnabled) { > + frameContext.gain = activeAutoGain; > + frameContext.quantizationGain = activeAutoQGain; > + } > + > + /* > + * Populate manual exposure and gain from the active auto values when > + * transitioning from auto to manual > + */ > + if (!frameContext.autoExposureEnabled && frameContext.autoExposureModeChange) { > + state.manual.exposure = activeAutoExposure; > + frameContext.exposure = activeAutoExposure; > + } > + if (!frameContext.autoGainEnabled && frameContext.autoGainModeChange) { > + state.manual.gain = activeAutoGain; > + frameContext.gain = activeAutoGain; > + frameContext.quantizationGain = activeAutoQGain; > + } > + > + frameContext.yTarget = state.automatic.yTarget; > +} > + > +/** > + * \brief Handle a \a process operation > + */ > +void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, > + agc::FrameContext &frameContext, std::optional<ProcessParams> &¶ms, > + ControlList &metadata) > +{ > + const utils::Duration &lineDuration = session.lineDuration; > + utils::Duration newExposureTime = {}; > + > + if (params) { > + ASSERT(session.autoAllowed); > + > + /* > + * Set the AGC limits using the fixed exposure time and/or gain in > + * manual mode, or the sensor limits in auto mode. > + */ > + utils::Duration minExposureTime; > + utils::Duration maxExposureTime; > + double minAnalogueGain; > + double maxAnalogueGain; > + > + if (frameContext.autoExposureEnabled) { > + minExposureTime = session.minExposureTime; > + maxExposureTime = std::clamp(frameContext.maxFrameDuration, > + session.minExposureTime, > + session.maxExposureTime); > + } else { > + minExposureTime = lineDuration * frameContext.exposure; > + maxExposureTime = minExposureTime; > + } > + > + if (frameContext.autoGainEnabled) { > + minAnalogueGain = session.minAnalogueGain; > + maxAnalogueGain = session.maxAnalogueGain; > + } else { > + minAnalogueGain = frameContext.gain; > + 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.yTarget = newEv.yTarget; > + > + newExposureTime = newEv.exposureTime; > + > + LOG(Agc, Debug) > + << "Divided up exposure time, analogue gain, quantization gain" > + << " and digital gain are " << utils::Duration(state.automatic.exposure * lineDuration) > + << ", " << state.automatic.gain << ", " << state.automatic.quantizationGain > + << " and " << newEv.digitalGain; > + } > + > + /* > + * Expand the target frame duration so that we do not run faster than > + * the minimum frame duration when we have short exposures. > + */ > + const auto frameDuration = std::max(frameContext.minFrameDuration, newExposureTime); > + frameContext.vblank = (frameDuration / lineDuration) - session.sensor.outputSize.height; > + > + /* Update frame duration accounting for line length quantization. */ > + frameContext.frameDuration = > + (session.sensor.outputSize.height + frameContext.vblank) * lineDuration; > + > + metadata.set(controls::AnalogueGain, frameContext.gain); > + metadata.set(controls::ExposureTime, > + utils::Duration(lineDuration * frameContext.exposure).get<std::micro>()); > + metadata.set(controls::FrameDuration, frameContext.frameDuration.get<std::micro>()); > + metadata.set(controls::ExposureTimeMode, frameContext.autoExposureEnabled > + ? controls::ExposureTimeModeAuto > + : controls::ExposureTimeModeManual); > + metadata.set(controls::AnalogueGainMode, frameContext.autoGainEnabled > + ? controls::AnalogueGainModeAuto > + : controls::AnalogueGainModeManual); > + > + metadata.set(controls::AeExposureMode, frameContext.exposureMode); > + metadata.set(controls::AeConstraintMode, frameContext.constraintMode); > + metadata.set(controls::ExposureValue, frameContext.exposureValue); > +} > + > +} /* namespace ipa */ > + > +} /* namespace libcamera */ > diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h > index 386442bae1..679d7bffce 100644 > --- a/src/ipa/libipa/agc.h > +++ b/src/ipa/libipa/agc.h > @@ -7,13 +7,19 @@ > > #pragma once > > +#include <optional> > #include <utility> > > #include <linux/v4l2-controls.h> > > +#include <libcamera/control_ids.h> > #include <libcamera/controls.h> > > +#include <libcamera/ipa/core_ipa_interface.h> > + > +#include "agc_mean_luminance.h" > #include "camera_sensor_helper.h" > +#include "histogram.h" > > namespace libcamera { > > @@ -45,8 +51,102 @@ prepareControls(ControlList &controls, const CameraSensorHelper *sensor, > : static_cast<uint32_t>(gain))); > } > > +struct Session { > + utils::Duration minExposureTime; > + utils::Duration maxExposureTime; > + double minAnalogueGain; > + double maxAnalogueGain; > + utils::Duration minFrameDuration; > + utils::Duration maxFrameDuration; > + > + utils::Duration lineDuration; > + > + struct { > + Size outputSize; > + } sensor; > + > + bool autoAllowed; > +}; > + > +struct ActiveState { > + struct { > + uint32_t exposure; > + double gain; > + } manual; > + struct { > + uint32_t exposure; > + double gain; > + double quantizationGain; > + double yTarget; > + } automatic; > + > + bool autoExposureEnabled; > + bool autoGainEnabled; > + double exposureValue; > + controls::AeConstraintModeEnum constraintMode; > + controls::AeExposureModeEnum exposureMode; > + utils::Duration minFrameDuration; > + utils::Duration maxFrameDuration; > +}; > + > +struct FrameContext { > + uint32_t exposure; > + double gain; > + double quantizationGain; > + double exposureValue; > + double yTarget; > + uint32_t vblank; > + bool autoExposureEnabled; > + bool autoGainEnabled; > + controls::AeConstraintModeEnum constraintMode; > + controls::AeExposureModeEnum exposureMode; > + utils::Duration minFrameDuration; > + utils::Duration maxFrameDuration; > + utils::Duration frameDuration; > + bool autoExposureModeChange; > + bool autoGainModeChange; > +}; > + > } /* namespace agc */ > > +class AgcAlgorithm > +{ > +public: > + struct ConfigurationParams { > + const CameraSensorHelper *sensor; > + const IPACameraSensorInfo &sensorInfo; > + const ControlInfoMap &sensorControls; > + ControlInfoMap::Map &ctrlMap; > + bool autoAllowed = true; > + }; > + > + int init(const ValueNode &tuningData); > + > + int configure(agc::Session &session, agc::ActiveState &state, > + const ConfigurationParams &config); > + > + void queueRequest(const agc::Session &session, agc::ActiveState &state, > + agc::FrameContext &frameContext, const ControlList &controls); > + > + void prepare(agc::ActiveState &state, agc::FrameContext &frameContext); > + > + struct ProcessParams { > + const AgcMeanLuminance::Traits &traits; > + const Histogram &yHist; > + uint32_t exposure; > + double gain; > + std::vector<AgcMeanLuminance::AgcConstraint> &&additionalConstraints = {}; > + double lux = 0; > + }; > + > + void process(const agc::Session &session, agc::ActiveState &state, > + agc::FrameContext &frameContext, std::optional<ProcessParams> &¶ms, > + ControlList &metadata); > + > +private: > + AgcMeanLuminance impl_; > +}; > + > } /* namespace ipa */ > > } /* namespace libcamera */ > diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build > index d17ccf8c54..fa49abcd55 100644 > --- a/src/ipa/libipa/meson.build > +++ b/src/ipa/libipa/meson.build > @@ -28,6 +28,7 @@ libipa_headers = files([ > ]) > > libipa_sources = files([ > + 'agc.cpp', > 'agc_mean_luminance.cpp', > 'algorithm.cpp', > 'awb_bayes.cpp', > -- > 2.55.0 >
diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp new file mode 100644 index 0000000000..4825cdb48e --- /dev/null +++ b/src/ipa/libipa/agc.cpp @@ -0,0 +1,644 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2021-2022, 2026 Ideas On Board + * + * Generic AGC algorithm + */ + +#include "agc.h" + +#include <algorithm> +#include <array> +#include <chrono> +#include <optional> + +#include <linux/v4l2-controls.h> + +#include <libcamera/base/log.h> + +#include <libcamera/control_ids.h> +#include <libcamera/controls.h> + +namespace libcamera { + +namespace ipa { + +using namespace std::chrono_literals; + +LOG_DEFINE_CATEGORY(Agc) + +/** + * \class AgcAlgorithm + * \brief AgcMeanLuminance wrapper for implementing the Algorithm interface + * + * \todo DigitalGain, DigitalGainMode + */ + +/** + * \struct agc::Session + * \brief Session configuration for AgcAlgorithm + * + * \var agc::Session::minExposureTime + * \brief Minimum exposure time for the streaming session + * + * \var agc::Session::maxExposureTime + * \brief Maximum exposure time for the streaming session + * + * \var agc::Session::minAnalogueGain + * \brief Minimum analogue gain for the streaming session + * + * \var agc::Session::maxAnalogueGain + * \brief Maximum analogue gain for the streaming session + * + * \var agc::Session::minFrameDuration + * \brief Minimum frame duration for the streaming session + * + * \var agc::Session::maxFrameDuration + * \brief Maximum frame duration for the streaming session + * + * \var agc::Session::lineDuration + * \brief Line duration for the streaming session + * + * \var agc::Session::sensor + * \brief Details of the sensor configuration + * + * \var agc::Session::sensor.outputSize + * \brief Configured output size of the sensor + * + * \var agc::Session::autoAllowed + * \copybrief AgcAlgorithm::ConfigurationParams::autoAllowed + * \sa AgcAlgorithm::ConfigurationParams::autoAllowed + */ + +/** + * \struct agc::ActiveState + * \brief Active state for AgcAlgorithm + * + * The \a automatic variables track the latest values computed by algorithm + * based on the latest processed statistics. All other variables track the + * consolidated controls requested in queued requests. + * + * \var agc::ActiveState::manual + * \brief Manual exposure time and analog gain (set through requests) + * + * \var agc::ActiveState::manual.exposure + * \brief Manual exposure time expressed as a number of lines as set by the + * ExposureTime control + * + * \var agc::ActiveState::manual.gain + * \brief Manual analogue gain as set by the AnalogueGain control + * + * \var agc::ActiveState::automatic + * \brief Automatic exposure time and analog gain (computed by the algorithm) + * + * \var agc::ActiveState::automatic.exposure + * \brief Automatic exposure time expressed as a number of lines + * + * \var agc::ActiveState::automatic.gain + * \brief Automatic analogue gain multiplier + * + * \var agc::ActiveState::automatic.quantizationGain + * \brief Automatic quantization gain multiplier + * + * \var agc::ActiveState::automatic.yTarget + * \brief Automatically determined luminance target + * + * \var agc::ActiveState::autoExposureEnabled + * \brief Whether automatic exposure control is enabled by the ExposureTimeMode control + * + * \var agc::ActiveState::autoGainEnabled + * \brief Whether automatic gain control is enabled by the AnalogueGainMode control + * + * \var agc::ActiveState::exposureValue + * \brief Exposure value as set by the ExposureValue control + * + * \var agc::ActiveState::constraintMode + * \brief Constraint mode as set by the AeConstraintMode control + * + * \var agc::ActiveState::exposureMode + * \brief Exposure mode as set by the AeExposureMode control + * + * \var agc::ActiveState::minFrameDuration + * \brief Minimum frame duration as set by the FrameDurationLimits control + * + * \var agc::ActiveState::maxFrameDuration + * \brief Maximum frame duration as set by the FrameDurationLimits control + */ + +/** + * \struct agc::FrameContext + * \brief Per-frame context for AgcAlgorithm + * + * \var agc::FrameContext::exposure + * \brief Exposure time expressed as a number of lines computed by the algorithm + * + * \var agc::FrameContext::gain + * \brief Analogue gain multiplier computed by the algorithm + * + * The gain should be translated to the sensor specific gain code before applying. + * + * \var agc::FrameContext::quantizationGain + * \brief Quantization gain multiplier computed by the algorithm + * + * \var agc::FrameContext::exposureValue + * \brief Exposure value as set by the ExposureValue control + * + * \var agc::FrameContext::yTarget + * \brief Luminance target computed by the algorithm + * + * \var agc::FrameContext::vblank + * \brief Vertical blanking parameter computed by the algorithm + * + * \var agc::FrameContext::autoExposureEnabled + * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control + * + * \var agc::FrameContext::autoGainEnabled + * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control + * + * \var agc::FrameContext::constraintMode + * \brief Constraint mode as set by the AeConstraintMode control + * + * \var agc::FrameContext::exposureMode + * \brief Exposure mode as set by the AeExposureMode control + * + * \var agc::FrameContext::minFrameDuration + * \brief Minimum frame duration as set by the FrameDurationLimits control + * + * \var agc::FrameContext::maxFrameDuration + * \brief Maximum frame duration as set by the FrameDurationLimits control + * + * \var agc::FrameContext::frameDuration + * \brief The actual FrameDuration used by the algorithm for the frame + * + * \var agc::FrameContext::autoExposureModeChange + * \brief Indicate if autoExposureEnabled has changed from true in the previous + * frame to false in the current frame, and no manual exposure value has been + * supplied in the current frame + * + * \var agc::FrameContext::autoGainModeChange + * \brief Indicate if autoGainEnabled has changed from true in the previous + * frame to false in the current frame, and no manual gain value has been + * supplied in the current frame + */ + +/** + * \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 + * + * \var AgcAlgorithm::ConfigurationParams::sensorControls + * \brief ControlInfoMap of the sensor + * + * \var AgcAlgorithm::ConfigurationParams::ctrlMap + * \brief ControlInfoMap::Map to update with controls + * + * \var AgcAlgorithm::ConfigurationParams::autoAllowed + * \brief Whether to enable auto controls + * + * If \a false, the algorithm is set up for manual exposure and gain + * control only, without automatic adjustments. In this mode statistics + * must not be provided to AgcAlgorithm::process(), and ExposureTimeMode + * and AnalogueGainMode will only advertise manual control. + */ + +/** + * \struct AgcAlgorithm::ProcessParams + * \brief Parameters for AgcAlgorithm::process() + * + * \var AgcAlgorithm::ProcessParams::traits + * \brief Implementation of AgcMeanLuminance::Traits + * + * \var AgcAlgorithm::ProcessParams::yHist + * \brief Luminance histogram of the frame + * + * \var AgcAlgorithm::ProcessParams::exposure + * \brief Effective exposure of the frame + * + * \var AgcAlgorithm::ProcessParams::gain + * \brief Effective gain of the frame + * + * \var AgcAlgorithm::ProcessParams::additionalConstraints + * \brief Additional AgcMeanLuminance::AgcConstraints to apply + * + * \var AgcAlgorithm::ProcessParams::lux + * \brief Effective lux value of the frame + */ + +/** + * \brief Load tuning data + */ +int AgcAlgorithm::init(const ValueNode &tuningData) +{ + int ret = impl_.parseTuningData(tuningData); + if (ret) + return ret; + + return 0; +} + +/** + * \brief Initialize the session configuration and active state + */ +int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, + const ConfigurationParams &config) +{ + session = {}; + session.autoAllowed = config.autoAllowed; + session.lineDuration = + config.sensorInfo.minLineLength * 1.0s / config.sensorInfo.pixelRate; + session.sensor.outputSize = config.sensorInfo.outputSize; + + const double lineDurationUs = session.lineDuration.get<std::micro>(); + + /* + * Compute exposure time limits from the V4L2_CID_EXPOSURE control + * limits and the line duration. + */ + + 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. */ + 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>()); + + LOG(Agc, Debug) + << "Exposure: [" << minExposure << ", " << maxExposure + << "], gain: [" << minGain << ", " << maxGain << "]"; + + /* + * Compute the frame duration limits. + * + * The frame length is computed assuming a fixed line length combined + * with the vertical frame sizes. + */ + const ControlInfo &v4l2HBlank = config.sensorControls.find(V4L2_CID_HBLANK)->second; + uint32_t hblank = v4l2HBlank.def().get<int32_t>(); + uint32_t lineLength = config.sensorInfo.outputSize.width + hblank; + + const ControlInfo &v4l2VBlank = config.sensorControls.find(V4L2_CID_VBLANK)->second; + std::array<uint32_t, 3> frameHeights{ + v4l2VBlank.min().get<int32_t>() + config.sensorInfo.outputSize.height, + v4l2VBlank.max().get<int32_t>() + config.sensorInfo.outputSize.height, + v4l2VBlank.def().get<int32_t>() + config.sensorInfo.outputSize.height, + }; + + std::array<int64_t, 3> frameDurations; + for (unsigned int i = 0; i < frameHeights.size(); ++i) { + uint64_t frameSize = lineLength * frameHeights[i]; + frameDurations[i] = frameSize / (config.sensorInfo.pixelRate / 1000000U); + } + + /* + * 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.minExposureTime = minExposure * session.lineDuration; + session.maxExposureTime = maxExposure * session.lineDuration; + session.minAnalogueGain = minGain; + session.maxAnalogueGain = maxGain; + session.minFrameDuration = std::chrono::microseconds(frameDurations[0]); + session.maxFrameDuration = std::chrono::microseconds(frameDurations[1]); + + impl_.configure(session.lineDuration, config.sensor); + impl_.setLimits(session.minExposureTime, session.maxExposureTime, + session.minAnalogueGain, session.maxAnalogueGain, + {}); + impl_.resetFrameCount(); + + /* Configure the default exposure and gain. */ + state = {}; + state.automatic.gain = session.minAnalogueGain; + state.automatic.exposure = 10ms / session.lineDuration; + state.automatic.quantizationGain = 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; + + const auto add = [&](const ControlId &cid, const auto &automatic, const auto &manual) { + std::array<ControlValue, 2> values; + size_t count = 0; + + if (session.autoAllowed) + values[count++] = ControlValue(automatic); + + values[count++] = ControlValue(manual); + + config.ctrlMap[&cid] = ControlInfo{ + { values.data(), count }, + ControlValue(session.autoAllowed ? automatic : manual), + }; + }; + + add(controls::ExposureTimeMode, + controls::ExposureTimeModeAuto, controls::ExposureTimeModeManual); + add(controls::AnalogueGainMode, + controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual); + + /* \todo Move this to the `Camera` class. */ + config.ctrlMap[&controls::AeEnable] = ControlInfo{ + false, + session.autoAllowed, + session.autoAllowed, + }; + config.ctrlMap[&controls::AnalogueGain] = ControlInfo{ + 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), + }; + config.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ + frameDurations[0], + frameDurations[1], + Span<const int64_t, 2>{ { frameDurations[2], frameDurations[2] } }, + }; + + if (session.autoAllowed) { + config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); + + for (const auto &[id, info] : impl_.controls()) + config.ctrlMap[id] = info; + } else { + /* The IPA control maps keep their states, so the removal is necessary. */ + + config.ctrlMap.erase(&controls::ExposureValue); + + for (const auto &[id, info] : impl_.controls()) + config.ctrlMap.erase(id); + } + + return 0; +} + +/** + * \brief Handle a \a queueRequest operation + */ +void AgcAlgorithm::queueRequest(const agc::Session &session, agc::ActiveState &state, + agc::FrameContext &frameContext, const ControlList &controls) +{ + if (session.autoAllowed) { + const auto &aeEnable = controls.get(controls::ExposureTimeMode); + if (aeEnable && + (*aeEnable == controls::ExposureTimeModeAuto) != state.autoExposureEnabled) { + state.autoExposureEnabled = (*aeEnable == controls::ExposureTimeModeAuto); + + LOG(Agc, Debug) + << (state.autoExposureEnabled ? "Enabling" : "Disabling") + << " AGC (exposure)"; + + /* + * If we go from auto -> manual with no manual control + * set, use the last computed value, which we don't + * know until prepare() so save this information. + * + * \todo Check the previous frame at prepare() time + * instead of saving a flag here + */ + if (!state.autoExposureEnabled && !controls.get(controls::ExposureTime)) + frameContext.autoExposureModeChange = true; + } + + const auto &agEnable = controls.get(controls::AnalogueGainMode); + if (agEnable && + (*agEnable == controls::AnalogueGainModeAuto) != state.autoGainEnabled) { + state.autoGainEnabled = (*agEnable == controls::AnalogueGainModeAuto); + + LOG(Agc, Debug) + << (state.autoGainEnabled ? "Enabling" : "Disabling") + << " AGC (gain)"; + /* + * If we go from auto -> manual with no manual control + * set, use the last computed value, which we don't + * know until prepare() so save this information. + */ + if (!state.autoGainEnabled && !controls.get(controls::AnalogueGain)) + frameContext.autoGainModeChange = true; + } + } + + const auto &exposure = controls.get(controls::ExposureTime); + if (exposure && !state.autoExposureEnabled) { + state.manual.exposure = *exposure * 1.0us / session.lineDuration; + + LOG(Agc, Debug) + << "Set exposure to " << state.manual.exposure; + } + + const auto &gain = controls.get(controls::AnalogueGain); + if (gain && !state.autoGainEnabled) { + state.manual.gain = *gain; + + LOG(Agc, Debug) << "Set gain to " << state.manual.gain; + } + + frameContext.autoExposureEnabled = state.autoExposureEnabled; + frameContext.autoGainEnabled = state.autoGainEnabled; + + if (!frameContext.autoExposureEnabled) + frameContext.exposure = state.manual.exposure; + if (!frameContext.autoGainEnabled) + frameContext.gain = state.manual.gain; + + if (!frameContext.autoExposureEnabled && + !frameContext.autoGainEnabled) + frameContext.quantizationGain = 1.0; + + const auto &exposureMode = controls.get(controls::AeExposureMode); + if (exposureMode) + state.exposureMode = + static_cast<controls::AeExposureModeEnum>(*exposureMode); + frameContext.exposureMode = state.exposureMode; + + const auto &constraintMode = controls.get(controls::AeConstraintMode); + if (constraintMode) + state.constraintMode = + static_cast<controls::AeConstraintModeEnum>(*constraintMode); + frameContext.constraintMode = state.constraintMode; + + const auto &exposureValue = controls.get(controls::ExposureValue); + if (exposureValue) + state.exposureValue = *exposureValue; + frameContext.exposureValue = state.exposureValue; + + const auto &frameDurationLimits = controls.get(controls::FrameDurationLimits); + if (frameDurationLimits) { + /* Limit the control value to the limits in ControlInfo */ + state.minFrameDuration = std::clamp<utils::Duration>( + std::chrono::microseconds((*frameDurationLimits).front()), + session.minFrameDuration, session.maxFrameDuration); + + state.maxFrameDuration = std::clamp<utils::Duration>( + std::chrono::microseconds((*frameDurationLimits).back()), + session.minFrameDuration, session.maxFrameDuration); + } + frameContext.minFrameDuration = state.minFrameDuration; + frameContext.maxFrameDuration = state.maxFrameDuration; +} + +/** + * \brief Handle a \a prepare operation + */ +void AgcAlgorithm::prepare(agc::ActiveState &state, agc::FrameContext &frameContext) +{ + uint32_t activeAutoExposure = state.automatic.exposure; + double activeAutoGain = state.automatic.gain; + double activeAutoQGain = state.automatic.quantizationGain; + + /* Populate exposure and gain in auto mode */ + if (frameContext.autoExposureEnabled) { + frameContext.exposure = activeAutoExposure; + frameContext.quantizationGain = activeAutoQGain; + } + if (frameContext.autoGainEnabled) { + frameContext.gain = activeAutoGain; + frameContext.quantizationGain = activeAutoQGain; + } + + /* + * Populate manual exposure and gain from the active auto values when + * transitioning from auto to manual + */ + if (!frameContext.autoExposureEnabled && frameContext.autoExposureModeChange) { + state.manual.exposure = activeAutoExposure; + frameContext.exposure = activeAutoExposure; + } + if (!frameContext.autoGainEnabled && frameContext.autoGainModeChange) { + state.manual.gain = activeAutoGain; + frameContext.gain = activeAutoGain; + frameContext.quantizationGain = activeAutoQGain; + } + + frameContext.yTarget = state.automatic.yTarget; +} + +/** + * \brief Handle a \a process operation + */ +void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, + agc::FrameContext &frameContext, std::optional<ProcessParams> &¶ms, + ControlList &metadata) +{ + const utils::Duration &lineDuration = session.lineDuration; + utils::Duration newExposureTime = {}; + + if (params) { + ASSERT(session.autoAllowed); + + /* + * Set the AGC limits using the fixed exposure time and/or gain in + * manual mode, or the sensor limits in auto mode. + */ + utils::Duration minExposureTime; + utils::Duration maxExposureTime; + double minAnalogueGain; + double maxAnalogueGain; + + if (frameContext.autoExposureEnabled) { + minExposureTime = session.minExposureTime; + maxExposureTime = std::clamp(frameContext.maxFrameDuration, + session.minExposureTime, + session.maxExposureTime); + } else { + minExposureTime = lineDuration * frameContext.exposure; + maxExposureTime = minExposureTime; + } + + if (frameContext.autoGainEnabled) { + minAnalogueGain = session.minAnalogueGain; + maxAnalogueGain = session.maxAnalogueGain; + } else { + minAnalogueGain = frameContext.gain; + 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.yTarget = newEv.yTarget; + + newExposureTime = newEv.exposureTime; + + LOG(Agc, Debug) + << "Divided up exposure time, analogue gain, quantization gain" + << " and digital gain are " << utils::Duration(state.automatic.exposure * lineDuration) + << ", " << state.automatic.gain << ", " << state.automatic.quantizationGain + << " and " << newEv.digitalGain; + } + + /* + * Expand the target frame duration so that we do not run faster than + * the minimum frame duration when we have short exposures. + */ + const auto frameDuration = std::max(frameContext.minFrameDuration, newExposureTime); + frameContext.vblank = (frameDuration / lineDuration) - session.sensor.outputSize.height; + + /* Update frame duration accounting for line length quantization. */ + frameContext.frameDuration = + (session.sensor.outputSize.height + frameContext.vblank) * lineDuration; + + metadata.set(controls::AnalogueGain, frameContext.gain); + metadata.set(controls::ExposureTime, + utils::Duration(lineDuration * frameContext.exposure).get<std::micro>()); + metadata.set(controls::FrameDuration, frameContext.frameDuration.get<std::micro>()); + metadata.set(controls::ExposureTimeMode, frameContext.autoExposureEnabled + ? controls::ExposureTimeModeAuto + : controls::ExposureTimeModeManual); + metadata.set(controls::AnalogueGainMode, frameContext.autoGainEnabled + ? controls::AnalogueGainModeAuto + : controls::AnalogueGainModeManual); + + metadata.set(controls::AeExposureMode, frameContext.exposureMode); + metadata.set(controls::AeConstraintMode, frameContext.constraintMode); + metadata.set(controls::ExposureValue, frameContext.exposureValue); +} + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h index 386442bae1..679d7bffce 100644 --- a/src/ipa/libipa/agc.h +++ b/src/ipa/libipa/agc.h @@ -7,13 +7,19 @@ #pragma once +#include <optional> #include <utility> #include <linux/v4l2-controls.h> +#include <libcamera/control_ids.h> #include <libcamera/controls.h> +#include <libcamera/ipa/core_ipa_interface.h> + +#include "agc_mean_luminance.h" #include "camera_sensor_helper.h" +#include "histogram.h" namespace libcamera { @@ -45,8 +51,102 @@ prepareControls(ControlList &controls, const CameraSensorHelper *sensor, : static_cast<uint32_t>(gain))); } +struct Session { + utils::Duration minExposureTime; + utils::Duration maxExposureTime; + double minAnalogueGain; + double maxAnalogueGain; + utils::Duration minFrameDuration; + utils::Duration maxFrameDuration; + + utils::Duration lineDuration; + + struct { + Size outputSize; + } sensor; + + bool autoAllowed; +}; + +struct ActiveState { + struct { + uint32_t exposure; + double gain; + } manual; + struct { + uint32_t exposure; + double gain; + double quantizationGain; + double yTarget; + } automatic; + + bool autoExposureEnabled; + bool autoGainEnabled; + double exposureValue; + controls::AeConstraintModeEnum constraintMode; + controls::AeExposureModeEnum exposureMode; + utils::Duration minFrameDuration; + utils::Duration maxFrameDuration; +}; + +struct FrameContext { + uint32_t exposure; + double gain; + double quantizationGain; + double exposureValue; + double yTarget; + uint32_t vblank; + bool autoExposureEnabled; + bool autoGainEnabled; + controls::AeConstraintModeEnum constraintMode; + controls::AeExposureModeEnum exposureMode; + utils::Duration minFrameDuration; + utils::Duration maxFrameDuration; + utils::Duration frameDuration; + bool autoExposureModeChange; + bool autoGainModeChange; +}; + } /* namespace agc */ +class AgcAlgorithm +{ +public: + struct ConfigurationParams { + const CameraSensorHelper *sensor; + const IPACameraSensorInfo &sensorInfo; + const ControlInfoMap &sensorControls; + ControlInfoMap::Map &ctrlMap; + bool autoAllowed = true; + }; + + int init(const ValueNode &tuningData); + + int configure(agc::Session &session, agc::ActiveState &state, + const ConfigurationParams &config); + + void queueRequest(const agc::Session &session, agc::ActiveState &state, + agc::FrameContext &frameContext, const ControlList &controls); + + void prepare(agc::ActiveState &state, agc::FrameContext &frameContext); + + struct ProcessParams { + const AgcMeanLuminance::Traits &traits; + const Histogram &yHist; + uint32_t exposure; + double gain; + std::vector<AgcMeanLuminance::AgcConstraint> &&additionalConstraints = {}; + double lux = 0; + }; + + void process(const agc::Session &session, agc::ActiveState &state, + agc::FrameContext &frameContext, std::optional<ProcessParams> &¶ms, + ControlList &metadata); + +private: + AgcMeanLuminance impl_; +}; + } /* namespace ipa */ } /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index d17ccf8c54..fa49abcd55 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -28,6 +28,7 @@ libipa_headers = files([ ]) libipa_sources = files([ + 'agc.cpp', 'agc_mean_luminance.cpp', 'algorithm.cpp', 'awb_bayes.cpp',