| Message ID | 20260817114349.994123-22-barnabas.pocze@ideasonboard.com |
|---|---|
| State | Superseded |
| Headers | show |
| Series |
|
| Related | show |
Hi Barnabás On Mon, Aug 17, 2026 at 01:43:22PM +0200, Barnabás Pőcze wrote: > Add a class that implements the `Algorithm` interface using `AgcMeanLuminance` > based on the rkisp1 `Agc` algorithm, with the following main adjustments: > > * the parameters for `process()` have been made optional to handle > the cases where statistics are not available; > * the "raw" capture check has been replaced with the "autoAllowed" > session parameter; > * the controls are only provided after `configure()`. > > Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> > --- > src/ipa/libipa/agc.cpp | 637 +++++++++++++++++++++++++++++- > src/ipa/libipa/agc.h | 106 +++++ > src/ipa/rkisp1/algorithms/agc.cpp | 464 +++------------------- > src/ipa/rkisp1/algorithms/agc.h | 10 +- > src/ipa/rkisp1/algorithms/lux.cpp | 2 +- > src/ipa/rkisp1/ipa_context.cpp | 98 ----- > src/ipa/rkisp1/ipa_context.h | 47 +-- > src/ipa/rkisp1/rkisp1.cpp | 4 - > 8 files changed, 805 insertions(+), 563 deletions(-) > > diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp > index 415a6d831f..3c6b12e452 100644 > --- a/src/ipa/libipa/agc.cpp > +++ b/src/ipa/libipa/agc.cpp > @@ -1,16 +1,32 @@ > /* SPDX-License-Identifier: LGPL-2.1-or-later */ > /* > - * Copyright (C) 2026 Ideas On Board > + * Copyright (C) 2021-2026 Ideas On Board > * > * Auto exposure/gain algorithm for implementing the IPA-specific AGC algorithms > */ > > #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) > + > namespace agc { > > /** > @@ -40,6 +56,625 @@ namespace agc { > > } /* namespace 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 > + * > + * \note The IPA algorithm implementation will most likely need to call > + * this in its Algorithm::init() implementation in order to provide > + * the initial controls for the camera. > + */ > +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; > + > + /* \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] } }, > + }; > + config.ctrlMap[&controls::ExposureTimeMode] = ControlInfo{ > + {{ controls::ExposureTimeModeAuto, controls::ExposureTimeModeManual }}, > + controls::ExposureTimeModeAuto, > + }; > + config.ctrlMap[&controls::AnalogueGainMode] = ControlInfo{ > + {{ controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual }}, > + controls::AnalogueGainModeAuto, > + }; > + config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); > + config.ctrlMap.merge(impl_.controls()); > + > + 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) > +{ > + if (!params) { > + processFrameDuration(session, frameContext, frameContext.minFrameDuration); > + fillMetadata(session, frameContext, metadata); > + return; > + } > + > + ASSERT(session.autoAllowed); > + > + const utils::Duration &lineDuration = session.lineDuration; > + > + /* > + * 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; > + > + LOG(Agc, Debug) > + << "Divided up exposure time, analogue gain, quantization gain" > + << " and digital gain are " << newEv.exposureTime > + << ", " << 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. > + */ > + processFrameDuration(session, frameContext, > + std::max(frameContext.minFrameDuration, newEv.exposureTime)); > + > + fillMetadata(session, frameContext, metadata); > +} > + > +/** > + * \brief Process frame duration and compute vblank > + * \param[in] session The session parameters > + * \param[in] frameContext The current frame context > + * \param[in] frameDuration The target frame duration > + * > + * Compute and populate vblank from the target frame duration. > + */ > +void AgcAlgorithm::processFrameDuration(const agc::Session &session, > + agc::FrameContext &frameContext, > + utils::Duration frameDuration) > +{ > + const utils::Duration &lineDuration = session.lineDuration; > + > + 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; > +} > + > +void AgcAlgorithm::fillMetadata(const agc::Session &session, > + const agc::FrameContext &frameContext, > + ControlList &metadata) > +{ > + > + metadata.set(controls::AnalogueGain, frameContext.gain); > + metadata.set(controls::ExposureTime, > + utils::Duration(session.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 4789c06ef8..66aa0eacb0 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 { > > @@ -21,6 +27,61 @@ namespace ipa { > > namespace agc { > > +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; > +}; > + > [[nodiscard]] > inline std::pair<uint32_t, double> > extractControls(const ControlList &controls, const CameraSensorHelper *sensor) > @@ -47,6 +108,51 @@ prepareControls(ControlList &controls, const CameraSensorHelper *sensor, > > } /* namespace agc */ > > +class AgcAlgorithm > +{ > +public: > + struct ConfigurationParams { > + const CameraSensorHelper *sensor; > + const IPACameraSensorInfo &sensorInfo; > + const ControlInfoMap &sensorControls; > + ControlInfoMap::Map &ctrlMap; > + bool autoAllowed = true; > + }; > + > + struct ProcessParams { > + const AgcMeanLuminance::Traits &traits; > + const Histogram &yHist; > + uint32_t exposure; > + double gain; > + std::vector<AgcMeanLuminance::AgcConstraint> &&additionalConstraints = {}; > + double lux = 0; > + }; > + > + 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); > + > + void process(const agc::Session &session, agc::ActiveState &state, > + agc::FrameContext &frameContext, std::optional<ProcessParams> &¶ms, > + ControlList &metadata); > + > +private: > + void processFrameDuration(const agc::Session &session, > + agc::FrameContext &frameContext, > + utils::Duration frameDuration); > + void fillMetadata(const agc::Session &session, > + const agc::FrameContext &frameContext, > + ControlList &metadata); > + > + AgcMeanLuminance impl_; > +}; > + > } /* namespace ipa */ > > } /* namespace libcamera */ > diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp > index fc228452c3..4c2a066e86 100644 > --- a/src/ipa/rkisp1/algorithms/agc.cpp > +++ b/src/ipa/rkisp1/algorithms/agc.cpp > @@ -8,9 +8,7 @@ > #include "agc.h" > > #include <algorithm> > -#include <chrono> > #include <cmath> > -#include <tuple> > #include <vector> > > #include <libcamera/base/log.h> > @@ -35,89 +33,6 @@ namespace ipa::rkisp1::algorithms { > > LOG_DEFINE_CATEGORY(RkISP1Agc) > > -namespace { > - > -void reconfigure(IPAContext &context) > -{ > - context.configuration.sensor.lineDuration = > - context.sensorInfo.minLineLength * 1.0s / context.sensorInfo.pixelRate; > - > - double lineDurationUs = context.configuration.sensor.lineDuration.get<std::micro>(); > - > - /* > - * Compute exposure time limits from the V4L2_CID_EXPOSURE control > - * limits and the line duration. > - */ > - > - const ControlInfo &v4l2Exposure = context.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>(); > - context.ctrlMap[&controls::ExposureTime] = ControlInfo{ > - static_cast<int32_t>(minExposure * lineDurationUs), > - static_cast<int32_t>(maxExposure * lineDurationUs), > - static_cast<int32_t>(defExposure * lineDurationUs), > - }; > - > - /* Compute the analogue gain limits. */ > - const ControlInfo &v4l2Gain = context.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; > - float minGain = context.camHelper->gain(v4l2Gain.min().get<int32_t>()); > - float maxGain = context.camHelper->gain(v4l2Gain.max().get<int32_t>()); > - float defGain = context.camHelper->gain(v4l2Gain.def().get<int32_t>()); > - context.ctrlMap[&controls::AnalogueGain] = ControlInfo{ > - minGain, > - maxGain, > - defGain, > - }; > - > - LOG(RkISP1Agc, 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 = context.sensorControls.find(V4L2_CID_HBLANK)->second; > - uint32_t hblank = v4l2HBlank.def().get<int32_t>(); > - uint32_t lineLength = context.sensorInfo.outputSize.width + hblank; > - > - const ControlInfo &v4l2VBlank = context.sensorControls.find(V4L2_CID_VBLANK)->second; > - std::array<uint32_t, 3> frameHeights{ > - v4l2VBlank.min().get<int32_t>() + context.sensorInfo.outputSize.height, > - v4l2VBlank.max().get<int32_t>() + context.sensorInfo.outputSize.height, > - v4l2VBlank.def().get<int32_t>() + context.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 / (context.sensorInfo.pixelRate / 1000000U); > - } > - > - context.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ > - frameDurations[0], > - frameDurations[1], > - Span<const int64_t, 2>{ { frameDurations[2], frameDurations[2] } }, > - }; > - > - /* > - * 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 > - */ > - context.configuration.sensor.minExposureTime = minExposure * context.configuration.sensor.lineDuration; > - context.configuration.sensor.maxExposureTime = maxExposure * context.configuration.sensor.lineDuration; > - context.configuration.sensor.minAnalogueGain = minGain; > - context.configuration.sensor.maxAnalogueGain = maxGain; > -} > - > -} /* namespace */ > - > /** > * \class Agc > * \brief A mean-based auto-exposure algorithm > @@ -221,7 +136,16 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) > { > int ret; > > - ret = agc_.parseTuningData(tuningData); > + ret = agc_.init(tuningData); > + if (ret) > + return ret; > + > + ret = agc_.configure(context.configuration.agc, context.activeState.agc, { > + .sensor = context.camHelper.get(), > + .sensorInfo = context.sensorInfo, > + .sensorControls = context.sensorControls, > + .ctrlMap = context.ctrlMap, > + }); The only comment, which might eventually be addressed as an on-top change, is about the requirement to call AgcAlgorithm::init() and configure() in the IPA init function. What if the paramters required for configure() are passed to AgcAlgorithm::init() and this function calls AgcAlgorithm::configure() internally ? Apart from this: Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> Thanks j > if (ret) > return ret; > > @@ -230,21 +154,6 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) > if (ret) > return ret; > > - context.ctrlMap[&controls::ExposureTimeMode] = > - ControlInfo({ { ControlValue(controls::ExposureTimeModeAuto), > - ControlValue(controls::ExposureTimeModeManual) } }, > - ControlValue(controls::ExposureTimeModeAuto)); > - context.ctrlMap[&controls::AnalogueGainMode] = > - ControlInfo({ { ControlValue(controls::AnalogueGainModeAuto), > - ControlValue(controls::AnalogueGainModeManual) } }, > - ControlValue(controls::AnalogueGainModeAuto)); > - /* \todo Move this to the Camera class */ > - context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true, true); > - context.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); > - context.ctrlMap.merge(agc_.controls()); > - > - reconfigure(context); > - > return 0; > } > > @@ -257,47 +166,24 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) > */ > int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) > { > - reconfigure(context); > - > - /* Configure the default exposure and gain. */ > - context.activeState.agc.automatic.gain = context.configuration.sensor.minAnalogueGain; > - context.activeState.agc.automatic.exposure = > - 10ms / context.configuration.sensor.lineDuration; > - context.activeState.agc.automatic.quantizationGain = 1.0; > - context.activeState.agc.manual.gain = context.activeState.agc.automatic.gain; > - context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure; > - context.activeState.agc.autoExposureEnabled = !context.configuration.raw; > - context.activeState.agc.autoGainEnabled = !context.configuration.raw; > - context.activeState.agc.exposureValue = 0.0; > - > - context.activeState.agc.constraintMode = > - static_cast<controls::AeConstraintModeEnum>(agc_.constraintModes().begin()->first); > - context.activeState.agc.exposureMode = > - static_cast<controls::AeExposureModeEnum>(agc_.exposureModeHelpers().begin()->first); > + int ret = agc_.configure(context.configuration.agc, context.activeState.agc, { > + .sensor = context.camHelper.get(), > + .sensorInfo = context.sensorInfo, > + .sensorControls = context.sensorControls, > + .ctrlMap = context.ctrlMap, > + .autoAllowed = !context.configuration.raw, > + }); > + if (ret) > + return ret; > + > context.activeState.agc.meteringMode = > static_cast<controls::AeMeteringModeEnum>(meteringModes_.begin()->first); > > - /* Limit the frame duration to match current initialisation */ > - ControlInfo &frameDurationLimits = context.ctrlMap[&controls::FrameDurationLimits]; > - context.activeState.agc.minFrameDuration = std::chrono::microseconds(frameDurationLimits.min().get<int64_t>()); > - context.activeState.agc.maxFrameDuration = std::chrono::microseconds(frameDurationLimits.max().get<int64_t>()); > - > context.configuration.agc.measureWindow.h_offs = 0; > context.configuration.agc.measureWindow.v_offs = 0; > context.configuration.agc.measureWindow.h_size = configInfo.outputSize.width; > context.configuration.agc.measureWindow.v_size = configInfo.outputSize.height; > > - agc_.configure(context.configuration.sensor.lineDuration, context.camHelper.get()); > - > - agc_.setLimits(context.configuration.sensor.minExposureTime, > - context.configuration.sensor.maxExposureTime, > - context.configuration.sensor.minAnalogueGain, > - context.configuration.sensor.maxAnalogueGain, {}); > - > - context.activeState.agc.automatic.yTarget = agc_.effectiveYTarget(0, 1); > - > - agc_.resetFrameCount(); > - > return 0; > } > > @@ -311,73 +197,7 @@ void Agc::queueRequest(IPAContext &context, > { > auto &agc = context.activeState.agc; > > - if (!context.configuration.raw) { > - const auto &aeEnable = controls.get(controls::ExposureTimeMode); > - if (aeEnable && > - (*aeEnable == controls::ExposureTimeModeAuto) != agc.autoExposureEnabled) { > - agc.autoExposureEnabled = (*aeEnable == controls::ExposureTimeModeAuto); > - > - LOG(RkISP1Agc, Debug) > - << (agc.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 (!agc.autoExposureEnabled && !controls.get(controls::ExposureTime)) > - frameContext.agc.autoExposureModeChange = true; > - } > - > - const auto &agEnable = controls.get(controls::AnalogueGainMode); > - if (agEnable && > - (*agEnable == controls::AnalogueGainModeAuto) != agc.autoGainEnabled) { > - agc.autoGainEnabled = (*agEnable == controls::AnalogueGainModeAuto); > - > - LOG(RkISP1Agc, Debug) > - << (agc.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 (!agc.autoGainEnabled && !controls.get(controls::AnalogueGain)) > - frameContext.agc.autoGainModeChange = true; > - } > - } > - > - const auto &exposure = controls.get(controls::ExposureTime); > - if (exposure && !agc.autoExposureEnabled) { > - agc.manual.exposure = *exposure * 1.0us > - / context.configuration.sensor.lineDuration; > - > - LOG(RkISP1Agc, Debug) > - << "Set exposure to " << agc.manual.exposure; > - } > - > - const auto &gain = controls.get(controls::AnalogueGain); > - if (gain && !agc.autoGainEnabled) { > - agc.manual.gain = *gain; > - > - LOG(RkISP1Agc, Debug) << "Set gain to " << agc.manual.gain; > - } > - > - frameContext.agc.autoExposureEnabled = agc.autoExposureEnabled; > - frameContext.agc.autoGainEnabled = agc.autoGainEnabled; > - > - if (!frameContext.agc.autoExposureEnabled) > - frameContext.agc.exposure = agc.manual.exposure; > - if (!frameContext.agc.autoGainEnabled) > - frameContext.agc.gain = agc.manual.gain; > - > - if (!frameContext.agc.autoExposureEnabled && > - !frameContext.agc.autoGainEnabled) > - frameContext.agc.quantizationGain = 1.0; > + agc_.queueRequest(context.configuration.agc, agc, frameContext.agc, controls); > > const auto &meteringMode = controls.get(controls::AeMeteringMode); > if (meteringMode) { > @@ -386,42 +206,6 @@ void Agc::queueRequest(IPAContext &context, > static_cast<controls::AeMeteringModeEnum>(*meteringMode); > } > frameContext.agc.meteringMode = agc.meteringMode; > - > - const auto &exposureMode = controls.get(controls::AeExposureMode); > - if (exposureMode) > - agc.exposureMode = > - static_cast<controls::AeExposureModeEnum>(*exposureMode); > - frameContext.agc.exposureMode = agc.exposureMode; > - > - const auto &constraintMode = controls.get(controls::AeConstraintMode); > - if (constraintMode) > - agc.constraintMode = > - static_cast<controls::AeConstraintModeEnum>(*constraintMode); > - frameContext.agc.constraintMode = agc.constraintMode; > - > - const auto &exposureValue = controls.get(controls::ExposureValue); > - if (exposureValue) > - agc.exposureValue = *exposureValue; > - frameContext.agc.exposureValue = agc.exposureValue; > - > - const auto &frameDurationLimits = controls.get(controls::FrameDurationLimits); > - if (frameDurationLimits) { > - /* Limit the control value to the limits in ControlInfo */ > - ControlInfo &limits = context.ctrlMap[&controls::FrameDurationLimits]; > - int64_t minFrameDuration = > - std::clamp((*frameDurationLimits).front(), > - limits.min().get<int64_t>(), > - limits.max().get<int64_t>()); > - int64_t maxFrameDuration = > - std::clamp((*frameDurationLimits).back(), > - limits.min().get<int64_t>(), > - limits.max().get<int64_t>()); > - > - agc.minFrameDuration = std::chrono::microseconds(minFrameDuration); > - agc.maxFrameDuration = std::chrono::microseconds(maxFrameDuration); > - } > - frameContext.agc.minFrameDuration = agc.minFrameDuration; > - frameContext.agc.maxFrameDuration = agc.maxFrameDuration; > } > > /** > @@ -430,41 +214,13 @@ void Agc::queueRequest(IPAContext &context, > void Agc::prepare(IPAContext &context, const uint32_t frame, > IPAFrameContext &frameContext, RkISP1Params *params) > { > - uint32_t activeAutoExposure = context.activeState.agc.automatic.exposure; > - double activeAutoGain = context.activeState.agc.automatic.gain; > - double activeAutoQGain = context.activeState.agc.automatic.quantizationGain; > - > - /* Populate exposure and gain in auto mode */ > - if (frameContext.agc.autoExposureEnabled) { > - frameContext.agc.exposure = activeAutoExposure; > - frameContext.agc.quantizationGain = activeAutoQGain; > - } > - if (frameContext.agc.autoGainEnabled) { > - frameContext.agc.gain = activeAutoGain; > - frameContext.agc.quantizationGain = activeAutoQGain; > - } > - > - /* > - * Populate manual exposure and gain from the active auto values when > - * transitioning from auto to manual > - */ > - if (!frameContext.agc.autoExposureEnabled && frameContext.agc.autoExposureModeChange) { > - context.activeState.agc.manual.exposure = activeAutoExposure; > - frameContext.agc.exposure = activeAutoExposure; > - } > - if (!frameContext.agc.autoGainEnabled && frameContext.agc.autoGainModeChange) { > - context.activeState.agc.manual.gain = activeAutoGain; > - frameContext.agc.gain = activeAutoGain; > - frameContext.agc.quantizationGain = activeAutoQGain; > - } > + agc_.prepare(context.activeState.agc, frameContext.agc); > > if (context.configuration.compress.supported) { > frameContext.compress.enable = true; > frameContext.compress.gain = frameContext.agc.quantizationGain; > } > > - frameContext.agc.yTarget = context.activeState.agc.automatic.yTarget; > - > if (frame > 0 && !frameContext.agc.updateMetering) > return; > > @@ -520,50 +276,6 @@ void Agc::prepare(IPAContext &context, const uint32_t frame, > static_cast<rkisp1_cif_isp_histogram_mode>(hstConfig->mode)); > } > > -void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext, > - ControlList &metadata) > -{ > - utils::Duration exposureTime = context.configuration.sensor.lineDuration > - * frameContext.sensor.exposure; > - metadata.set(controls::AnalogueGain, frameContext.sensor.gain); > - metadata.set(controls::ExposureTime, exposureTime.get<std::micro>()); > - metadata.set(controls::FrameDuration, frameContext.agc.frameDuration.get<std::micro>()); > - metadata.set(controls::ExposureTimeMode, > - frameContext.agc.autoExposureEnabled > - ? controls::ExposureTimeModeAuto > - : controls::ExposureTimeModeManual); > - metadata.set(controls::AnalogueGainMode, > - frameContext.agc.autoGainEnabled > - ? controls::AnalogueGainModeAuto > - : controls::AnalogueGainModeManual); > - > - metadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode); > - metadata.set(controls::AeExposureMode, frameContext.agc.exposureMode); > - metadata.set(controls::AeConstraintMode, frameContext.agc.constraintMode); > - metadata.set(controls::ExposureValue, frameContext.agc.exposureValue); > -} > - > -/** > - * \brief Process frame duration and compute vblank > - * \param[in] context The shared IPA context > - * \param[in] frameContext The current frame context > - * \param[in] frameDuration The target frame duration > - * > - * Compute and populate vblank from the target frame duration. > - */ > -void Agc::processFrameDuration(IPAContext &context, > - IPAFrameContext &frameContext, > - utils::Duration frameDuration) > -{ > - IPACameraSensorInfo &sensorInfo = context.sensorInfo; > - utils::Duration lineDuration = context.configuration.sensor.lineDuration; > - > - frameContext.agc.vblank = (frameDuration / lineDuration) - sensorInfo.outputSize.height; > - > - /* Update frame duration accounting for line length quantization. */ > - frameContext.agc.frameDuration = (sensorInfo.outputSize.height + frameContext.agc.vblank) * lineDuration; > -} > - > namespace { > > class AgcTraits final : public AgcMeanLuminance::Traits > @@ -637,21 +349,6 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, > IPAFrameContext &frameContext, const rkisp1_stat_buffer *stats, > ControlList &metadata) > { > - if (!stats) { > - processFrameDuration(context, frameContext, > - frameContext.agc.minFrameDuration); > - fillMetadata(context, frameContext, metadata); > - return; > - } > - > - if (!(stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP)) { > - fillMetadata(context, frameContext, metadata); > - LOG(RkISP1Agc, Error) << "AUTOEXP data is missing in statistics"; > - return; > - } > - > - const utils::Duration &lineDuration = context.configuration.sensor.lineDuration; > - > /* > * \todo Verify that the exposure and gain applied by the sensor for > * this frame match what has been requested. This isn't a hard > @@ -660,95 +357,46 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, > * we receive), but is important in manual mode. > */ > > - const rkisp1_cif_isp_stat *params = &stats->params; > + const rkisp1_cif_isp_stat *params = nullptr; > > - /* > - * 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.agc.autoExposureEnabled) { > - minExposureTime = context.configuration.sensor.minExposureTime; > - maxExposureTime = std::clamp(frameContext.agc.maxFrameDuration, > - context.configuration.sensor.minExposureTime, > - context.configuration.sensor.maxExposureTime); > - } else { > - minExposureTime = context.configuration.sensor.lineDuration > - * frameContext.agc.exposure; > - maxExposureTime = minExposureTime; > + if (stats) { > + if (stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP) > + params = &stats->params; > + else > + LOG(RkISP1Agc, Error) << "AUTOEXP data is missing in statistics"; > } > > - if (frameContext.agc.autoGainEnabled) { > - minAnalogueGain = context.configuration.sensor.minAnalogueGain; > - maxAnalogueGain = context.configuration.sensor.maxAnalogueGain; > + if (params) { > + std::vector<AgcMeanLuminance::AgcConstraint> additionalConstraints; > + if (context.activeState.wdr.mode != controls::WdrOff) > + additionalConstraints.push_back(context.activeState.wdr.constraint); > + > + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {{ > + .traits = AgcTraits{ > + { params->ae.exp_mean, context.hw.numAeCells }, > + meteringModes_.at(frameContext.agc.meteringMode), > + }, > + .yHist = { > + /* The lower 4 bits are fractional and meant to be discarded. */ > + { params->hist.hist_bins, context.hw.numHistogramBins }, > + [](uint32_t x) { return x >> 4; }, > + }, > + .exposure = frameContext.sensor.exposure, > + /* > + * Include the quantization gain if it was applied. Do not use > + * compress.gain because it will include gains that shall not be > + * reported to the user when HDR is implemented. > + */ > + .gain = frameContext.sensor.gain > + * (frameContext.compress.enable ? frameContext.agc.quantizationGain : 1), > + .additionalConstraints = std::move(additionalConstraints), > + .lux = frameContext.lux.lux, > + }}, metadata); > } else { > - minAnalogueGain = frameContext.agc.gain; > - maxAnalogueGain = frameContext.agc.gain; > + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {}, metadata); > } > > - std::vector<AgcMeanLuminance::AgcConstraint> additionalConstraints; > - if (context.activeState.wdr.mode != controls::WdrOff) > - additionalConstraints.push_back(context.activeState.wdr.constraint); > - > - agc_.setLimits(minExposureTime, maxExposureTime, minAnalogueGain, maxAnalogueGain, > - std::move(additionalConstraints)); > - > - /* > - * The Agc algorithm needs to know the effective exposure value that was > - * applied to the sensor when the statistics were collected. > - */ > - utils::Duration exposureTime = lineDuration * frameContext.sensor.exposure; > - double analogueGain = frameContext.sensor.gain; > - utils::Duration effectiveExposureValue = exposureTime * analogueGain; > - > - /* > - * Include the quantization gain if it was applied. Do not use > - * compress.gain because it will include gains that shall not be > - * reported to the user when HDR is implemented. > - */ > - if (frameContext.compress.enable) > - effectiveExposureValue *= frameContext.agc.quantizationGain; > - > - /* The lower 4 bits are fractional and meant to be discarded. */ > - Histogram hist({ params->hist.hist_bins, context.hw.numHistogramBins }, > - [](uint32_t x) { return x >> 4; }); > - > - const auto &newEv = agc_.calculateNewEv({ > - .traits = AgcTraits{ > - { params->ae.exp_mean, context.hw.numAeCells }, > - meteringModes_.at(frameContext.agc.meteringMode), > - }, > - .yHist = hist, > - .effectiveExposureValue = effectiveExposureValue, > - .constraintModeIndex = frameContext.agc.constraintMode, > - .exposureModeIndex = frameContext.agc.exposureMode, > - .lux = frameContext.lux.lux, > - .exposureCompensation = pow(2.0, frameContext.agc.exposureValue), > - }); > - > - LOG(RkISP1Agc, Debug) > - << "Divided up exposure time, analogue gain, quantization gain" > - << " and digital gain are " << newEv.exposureTime << ", " << newEv.analogueGain > - << ", " << newEv.quantizationGain << " and " << newEv.digitalGain; > - > - IPAActiveState &activeState = context.activeState; > - /* Update the estimated exposure and gain. */ > - activeState.agc.automatic.exposure = newEv.exposureTime / lineDuration; > - activeState.agc.automatic.gain = newEv.analogueGain; > - activeState.agc.automatic.quantizationGain = newEv.quantizationGain; > - activeState.agc.automatic.yTarget = newEv.yTarget; > - /* > - * 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, newEv.exposureTime)); > - > - fillMetadata(context, frameContext, metadata); > + metadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode); > } > > REGISTER_IPA_ALGORITHM(Agc, "Agc") > diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h > index 0527ca0d5f..3a4d7bc546 100644 > --- a/src/ipa/rkisp1/algorithms/agc.h > +++ b/src/ipa/rkisp1/algorithms/agc.h > @@ -14,7 +14,7 @@ > > #include <libcamera/geometry.h> > > -#include "libipa/agc_mean_luminance.h" > +#include "libipa/agc.h" > > #include "algorithm.h" > > @@ -47,14 +47,8 @@ private: > uint8_t computeHistogramPredivider(const Size &size, > enum rkisp1_cif_isp_histogram_mode mode); > > - void fillMetadata(IPAContext &context, IPAFrameContext &frameContext, > - ControlList &metadata); > - void processFrameDuration(IPAContext &context, > - IPAFrameContext &frameContext, > - utils::Duration frameDuration); > - > std::map<int32_t, std::vector<uint8_t>> meteringModes_; > - AgcMeanLuminance agc_; > + AgcAlgorithm agc_; > }; > > } /* namespace ipa::rkisp1::algorithms */ > diff --git a/src/ipa/rkisp1/algorithms/lux.cpp b/src/ipa/rkisp1/algorithms/lux.cpp > index 86e46c492f..ce6928a55d 100644 > --- a/src/ipa/rkisp1/algorithms/lux.cpp > +++ b/src/ipa/rkisp1/algorithms/lux.cpp > @@ -74,7 +74,7 @@ void Lux::process(IPAContext &context, > if (!stats) > return; > > - utils::Duration exposureTime = context.configuration.sensor.lineDuration * > + utils::Duration exposureTime = context.configuration.agc.lineDuration * > frameContext.sensor.exposure; > double gain = frameContext.sensor.gain; > > diff --git a/src/ipa/rkisp1/ipa_context.cpp b/src/ipa/rkisp1/ipa_context.cpp > index 1f94afda6b..47691674ad 100644 > --- a/src/ipa/rkisp1/ipa_context.cpp > +++ b/src/ipa/rkisp1/ipa_context.cpp > @@ -86,21 +86,6 @@ namespace libcamera::ipa::rkisp1 { > * \var IPASessionConfiguration::sensor > * \brief Sensor-specific configuration of the IPA > * > - * \var IPASessionConfiguration::sensor.minExposureTime > - * \brief Minimum exposure time supported with the sensor > - * > - * \var IPASessionConfiguration::sensor.maxExposureTime > - * \brief Maximum exposure time supported with the sensor > - * > - * \var IPASessionConfiguration::sensor.minAnalogueGain > - * \brief Minimum analogue gain supported with the sensor > - * > - * \var IPASessionConfiguration::sensor.maxAnalogueGain > - * \brief Maximum analogue gain supported with the sensor > - * > - * \var IPASessionConfiguration::sensor.lineDuration > - * \brief Line duration in microseconds > - * > * \var IPASessionConfiguration::sensor.size > * \brief Sensor output resolution > */ > @@ -147,49 +132,8 @@ namespace libcamera::ipa::rkisp1 { > * \var IPAActiveState::agc > * \brief State for the Automatic Gain Control algorithm > * > - * 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. > - * > - * \struct IPAActiveState::agc.manual > - * \brief Manual exposure time and analog gain (set through requests) > - * > - * \var IPAActiveState::agc.manual.exposure > - * \brief Manual exposure time expressed as a number of lines as set by the > - * ExposureTime control > - * > - * \var IPAActiveState::agc.manual.gain > - * \brief Manual analogue gain as set by the AnalogueGain control > - * > - * \struct IPAActiveState::agc.automatic > - * \brief Automatic exposure time and analog gain (computed by the algorithm) > - * > - * \var IPAActiveState::agc.automatic.exposure > - * \brief Automatic exposure time expressed as a number of lines > - * > - * \var IPAActiveState::agc.automatic.gain > - * \brief Automatic analogue gain multiplier > - * > - * \var IPAActiveState::agc.autoExposureEnabled > - * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control > - * > - * \var IPAActiveState::agc.autoGainEnabled > - * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control > - * > - * \var IPAActiveState::agc.constraintMode > - * \brief Constraint mode as set by the AeConstraintMode control > - * > - * \var IPAActiveState::agc.exposureMode > - * \brief Exposure mode as set by the AeExposureMode control > - * > * \var IPAActiveState::agc.meteringMode > * \brief Metering mode as set by the AeMeteringMode control > - * > - * \var IPAActiveState::agc.minFrameDuration > - * \brief Minimum frame duration as set by the FrameDurationLimits control > - * > - * \var IPAActiveState::agc.maxFrameDuration > - * \brief Maximum frame duration as set by the FrameDurationLimits control > */ > > /** > @@ -314,53 +258,11 @@ namespace libcamera::ipa::rkisp1 { > * the vertical blanking period is determined to maintain a consistent frame > * rate matched to the FrameDurationLimits as set by the user. > * > - * \var IPAFrameContext::agc.exposure > - * \brief Exposure time expressed as a number of lines computed by the algorithm > - * > - * \var IPAFrameContext::agc.gain > - * \brief Analogue gain multiplier computed by the algorithm > - * > - * The gain should be adapted to the sensor specific gain code before applying. > - * > - * \var IPAFrameContext::agc.vblank > - * \brief Vertical blanking parameter computed by the algorithm > - * > - * \var IPAFrameContext::agc.autoExposureEnabled > - * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control > - * > - * \var IPAFrameContext::agc.autoGainEnabled > - * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control > - * > - * \var IPAFrameContext::agc.constraintMode > - * \brief Constraint mode as set by the AeConstraintMode control > - * > - * \var IPAFrameContext::agc.exposureMode > - * \brief Exposure mode as set by the AeExposureMode control > - * > * \var IPAFrameContext::agc.meteringMode > * \brief Metering mode as set by the AeMeteringMode control > * > - * \var IPAFrameContext::agc.minFrameDuration > - * \brief Minimum frame duration as set by the FrameDurationLimits control > - * > - * \var IPAFrameContext::agc.maxFrameDuration > - * \brief Maximum frame duration as set by the FrameDurationLimits control > - * > - * \var IPAFrameContext::agc.frameDuration > - * \brief The actual FrameDuration used by the algorithm for the frame > - * > * \var IPAFrameContext::agc.updateMetering > * \brief Indicate if new ISP AGC metering parameters need to be applied > - * > - * \var IPAFrameContext::agc.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 IPAFrameContext::agc.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. > */ > > /** > diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h > index cd213dd991..cc07bb9462 100644 > --- a/src/ipa/rkisp1/ipa_context.h > +++ b/src/ipa/rkisp1/ipa_context.h > @@ -24,7 +24,7 @@ > #include "libcamera/internal/matrix.h" > #include "libcamera/internal/vector.h" > > -#include "libipa/agc_mean_luminance.h" > +#include "libipa/agc.h" > #include "libipa/awb.h" > #include "libipa/camera_sensor_helper.h" > #include "libipa/ccm.h" > @@ -57,7 +57,7 @@ struct RKISP1AwbSession { > }; > > struct IPASessionConfiguration { > - struct { > + struct Agc : agc::Session { > struct rkisp1_cif_isp_window measureWindow; > } agc; > > @@ -68,12 +68,6 @@ struct IPASessionConfiguration { > } compress; > > struct { > - utils::Duration minExposureTime; > - utils::Duration maxExposureTime; > - double minAnalogueGain; > - double maxAnalogueGain; > - > - utils::Duration lineDuration; > Size size; > } sensor; > > @@ -82,26 +76,8 @@ struct IPASessionConfiguration { > }; > > struct IPAActiveState { > - struct { > - 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; > + struct Agc : agc::ActiveState { > controls::AeMeteringModeEnum meteringMode; > - utils::Duration minFrameDuration; > - utils::Duration maxFrameDuration; > } agc; > > ipa::awb::ActiveState awb; > @@ -145,24 +121,9 @@ struct IPAActiveState { > }; > > struct IPAFrameContext : public FrameContext { > - struct { > - uint32_t exposure; > - double gain; > - double exposureValue; > - double quantizationGain; > - uint32_t vblank; > - double yTarget; > - bool autoExposureEnabled; > - bool autoGainEnabled; > - controls::AeConstraintModeEnum constraintMode; > - controls::AeExposureModeEnum exposureMode; > + struct Agc : agc::FrameContext { > controls::AeMeteringModeEnum meteringMode; > - utils::Duration minFrameDuration; > - utils::Duration maxFrameDuration; > - utils::Duration frameDuration; > bool updateMetering; > - bool autoExposureModeChange; > - bool autoGainModeChange; > } agc; > > ipa::awb::FrameContext awb; > diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp > index 98ec5a5748..731a362cee 100644 > --- a/src/ipa/rkisp1/rkisp1.cpp > +++ b/src/ipa/rkisp1/rkisp1.cpp > @@ -6,8 +6,6 @@ > */ > > #include <algorithm> > -#include <array> > -#include <chrono> > #include <stdint.h> > #include <string.h> > > @@ -40,8 +38,6 @@ namespace libcamera { > > LOG_DEFINE_CATEGORY(IPARkISP1) > > -using namespace std::literals::chrono_literals; > - > namespace ipa::rkisp1 { > > /* Maximum number of frame contexts to be held */ > -- > 2.55.0 >
Hi Barnabás, Thank you for the patch. Quoting Barnabás Pőcze (2026-08-17 13:43:22) > Add a class that implements the `Algorithm` interface using `AgcMeanLuminance` > based on the rkisp1 `Agc` algorithm, with the following main adjustments: > > * the parameters for `process()` have been made optional to handle > the cases where statistics are not available; > * the "raw" capture check has been replaced with the "autoAllowed" > session parameter; > * the controls are only provided after `configure()`. Eek I completely missed that in v4. Thanks for pointing it out. I thought that this was problematic as cam showed only controls that were available before configure. But testing it reveals that this is not the case. So either I remember incorrectly or cam got fixed :-) > > Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> > --- > src/ipa/libipa/agc.cpp | 637 +++++++++++++++++++++++++++++- > src/ipa/libipa/agc.h | 106 +++++ > src/ipa/rkisp1/algorithms/agc.cpp | 464 +++------------------- > src/ipa/rkisp1/algorithms/agc.h | 10 +- > src/ipa/rkisp1/algorithms/lux.cpp | 2 +- > src/ipa/rkisp1/ipa_context.cpp | 98 ----- > src/ipa/rkisp1/ipa_context.h | 47 +-- > src/ipa/rkisp1/rkisp1.cpp | 4 - > 8 files changed, 805 insertions(+), 563 deletions(-) > > diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp > index 415a6d831f..3c6b12e452 100644 > --- a/src/ipa/libipa/agc.cpp > +++ b/src/ipa/libipa/agc.cpp > @@ -1,16 +1,32 @@ > /* SPDX-License-Identifier: LGPL-2.1-or-later */ > /* > - * Copyright (C) 2026 Ideas On Board > + * Copyright (C) 2021-2026 Ideas On Board > * > * Auto exposure/gain algorithm for implementing the IPA-specific AGC algorithms Nit: line break > */ > > #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) > + > namespace agc { > > /** > @@ -40,6 +56,625 @@ namespace agc { > > } /* namespace 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 > + */ > + Moving these variables into agc::FrameContext and agx::ActiveState in a separate preparatory patch might have reduced the size of this patch by quite a bit. I don't want to send a new Yak, so I won't dwell on it :-) > +/** > + * \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 > + * > + * \note The IPA algorithm implementation will most likely need to call > + * this in its Algorithm::init() implementation in order to provide > + * the initial controls for the camera. > + */ > +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; > + > + /* \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] } }, > + }; > + config.ctrlMap[&controls::ExposureTimeMode] = ControlInfo{ > + {{ controls::ExposureTimeModeAuto, controls::ExposureTimeModeManual }}, > + controls::ExposureTimeModeAuto, > + }; > + config.ctrlMap[&controls::AnalogueGainMode] = ControlInfo{ > + {{ controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual }}, > + controls::AnalogueGainModeAuto, > + }; > + config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); > + config.ctrlMap.merge(impl_.controls()); > + > + 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) > +{ > + if (!params) { > + processFrameDuration(session, frameContext, frameContext.minFrameDuration); > + fillMetadata(session, frameContext, metadata); > + return; > + } > + > + ASSERT(session.autoAllowed); > + > + const utils::Duration &lineDuration = session.lineDuration; > + > + /* > + * 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; > + > + LOG(Agc, Debug) > + << "Divided up exposure time, analogue gain, quantization gain" > + << " and digital gain are " << newEv.exposureTime > + << ", " << 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. > + */ > + processFrameDuration(session, frameContext, > + std::max(frameContext.minFrameDuration, newEv.exposureTime)); > + > + fillMetadata(session, frameContext, metadata); > +} > + > +/** > + * \brief Process frame duration and compute vblank > + * \param[in] session The session parameters > + * \param[in] frameContext The current frame context > + * \param[in] frameDuration The target frame duration > + * > + * Compute and populate vblank from the target frame duration. > + */ > +void AgcAlgorithm::processFrameDuration(const agc::Session &session, > + agc::FrameContext &frameContext, > + utils::Duration frameDuration) > +{ > + const utils::Duration &lineDuration = session.lineDuration; > + > + 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; > +} > + > +void AgcAlgorithm::fillMetadata(const agc::Session &session, Does this one need documentation as it lives in libipa now? > + const agc::FrameContext &frameContext, > + ControlList &metadata) > +{ > + > + metadata.set(controls::AnalogueGain, frameContext.gain); > + metadata.set(controls::ExposureTime, > + utils::Duration(session.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 4789c06ef8..66aa0eacb0 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 { > > @@ -21,6 +27,61 @@ namespace ipa { > > namespace agc { > > +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; > +}; > + > [[nodiscard]] > inline std::pair<uint32_t, double> > extractControls(const ControlList &controls, const CameraSensorHelper *sensor) > @@ -47,6 +108,51 @@ prepareControls(ControlList &controls, const CameraSensorHelper *sensor, > > } /* namespace agc */ > > +class AgcAlgorithm > +{ > +public: > + struct ConfigurationParams { > + const CameraSensorHelper *sensor; > + const IPACameraSensorInfo &sensorInfo; > + const ControlInfoMap &sensorControls; > + ControlInfoMap::Map &ctrlMap; > + bool autoAllowed = true; > + }; > + > + struct ProcessParams { > + const AgcMeanLuminance::Traits &traits; > + const Histogram &yHist; > + uint32_t exposure; > + double gain; > + std::vector<AgcMeanLuminance::AgcConstraint> &&additionalConstraints = {}; > + double lux = 0; > + }; > + > + 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); > + > + void process(const agc::Session &session, agc::ActiveState &state, > + agc::FrameContext &frameContext, std::optional<ProcessParams> &¶ms, > + ControlList &metadata); > + > +private: > + void processFrameDuration(const agc::Session &session, > + agc::FrameContext &frameContext, > + utils::Duration frameDuration); > + void fillMetadata(const agc::Session &session, > + const agc::FrameContext &frameContext, > + ControlList &metadata); > + > + AgcMeanLuminance impl_; > +}; > + > } /* namespace ipa */ > > } /* namespace libcamera */ > diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp > index fc228452c3..4c2a066e86 100644 > --- a/src/ipa/rkisp1/algorithms/agc.cpp > +++ b/src/ipa/rkisp1/algorithms/agc.cpp > @@ -8,9 +8,7 @@ > #include "agc.h" > > #include <algorithm> > -#include <chrono> > #include <cmath> > -#include <tuple> > #include <vector> > > #include <libcamera/base/log.h> > @@ -35,89 +33,6 @@ namespace ipa::rkisp1::algorithms { > > LOG_DEFINE_CATEGORY(RkISP1Agc) > > -namespace { > - > -void reconfigure(IPAContext &context) > -{ > - context.configuration.sensor.lineDuration = > - context.sensorInfo.minLineLength * 1.0s / context.sensorInfo.pixelRate; > - > - double lineDurationUs = context.configuration.sensor.lineDuration.get<std::micro>(); > - > - /* > - * Compute exposure time limits from the V4L2_CID_EXPOSURE control > - * limits and the line duration. > - */ > - > - const ControlInfo &v4l2Exposure = context.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>(); > - context.ctrlMap[&controls::ExposureTime] = ControlInfo{ > - static_cast<int32_t>(minExposure * lineDurationUs), > - static_cast<int32_t>(maxExposure * lineDurationUs), > - static_cast<int32_t>(defExposure * lineDurationUs), > - }; > - > - /* Compute the analogue gain limits. */ > - const ControlInfo &v4l2Gain = context.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; > - float minGain = context.camHelper->gain(v4l2Gain.min().get<int32_t>()); > - float maxGain = context.camHelper->gain(v4l2Gain.max().get<int32_t>()); > - float defGain = context.camHelper->gain(v4l2Gain.def().get<int32_t>()); > - context.ctrlMap[&controls::AnalogueGain] = ControlInfo{ > - minGain, > - maxGain, > - defGain, > - }; > - > - LOG(RkISP1Agc, 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 = context.sensorControls.find(V4L2_CID_HBLANK)->second; > - uint32_t hblank = v4l2HBlank.def().get<int32_t>(); > - uint32_t lineLength = context.sensorInfo.outputSize.width + hblank; > - > - const ControlInfo &v4l2VBlank = context.sensorControls.find(V4L2_CID_VBLANK)->second; > - std::array<uint32_t, 3> frameHeights{ > - v4l2VBlank.min().get<int32_t>() + context.sensorInfo.outputSize.height, > - v4l2VBlank.max().get<int32_t>() + context.sensorInfo.outputSize.height, > - v4l2VBlank.def().get<int32_t>() + context.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 / (context.sensorInfo.pixelRate / 1000000U); > - } > - > - context.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ > - frameDurations[0], > - frameDurations[1], > - Span<const int64_t, 2>{ { frameDurations[2], frameDurations[2] } }, > - }; > - > - /* > - * 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 > - */ > - context.configuration.sensor.minExposureTime = minExposure * context.configuration.sensor.lineDuration; > - context.configuration.sensor.maxExposureTime = maxExposure * context.configuration.sensor.lineDuration; > - context.configuration.sensor.minAnalogueGain = minGain; > - context.configuration.sensor.maxAnalogueGain = maxGain; > -} > - > -} /* namespace */ > - > /** > * \class Agc > * \brief A mean-based auto-exposure algorithm > @@ -221,7 +136,16 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) > { > int ret; > > - ret = agc_.parseTuningData(tuningData); > + ret = agc_.init(tuningData); > + if (ret) > + return ret; > + > + ret = agc_.configure(context.configuration.agc, context.activeState.agc, { > + .sensor = context.camHelper.get(), > + .sensorInfo = context.sensorInfo, > + .sensorControls = context.sensorControls, > + .ctrlMap = context.ctrlMap, > + }); > if (ret) > return ret; > > @@ -230,21 +154,6 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) > if (ret) > return ret; > > - context.ctrlMap[&controls::ExposureTimeMode] = > - ControlInfo({ { ControlValue(controls::ExposureTimeModeAuto), > - ControlValue(controls::ExposureTimeModeManual) } }, > - ControlValue(controls::ExposureTimeModeAuto)); > - context.ctrlMap[&controls::AnalogueGainMode] = > - ControlInfo({ { ControlValue(controls::AnalogueGainModeAuto), > - ControlValue(controls::AnalogueGainModeManual) } }, > - ControlValue(controls::AnalogueGainModeAuto)); > - /* \todo Move this to the Camera class */ > - context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true, true); > - context.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); > - context.ctrlMap.merge(agc_.controls()); > - > - reconfigure(context); > - > return 0; > } > > @@ -257,47 +166,24 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) > */ > int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) > { > - reconfigure(context); > - > - /* Configure the default exposure and gain. */ > - context.activeState.agc.automatic.gain = context.configuration.sensor.minAnalogueGain; > - context.activeState.agc.automatic.exposure = > - 10ms / context.configuration.sensor.lineDuration; > - context.activeState.agc.automatic.quantizationGain = 1.0; > - context.activeState.agc.manual.gain = context.activeState.agc.automatic.gain; > - context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure; > - context.activeState.agc.autoExposureEnabled = !context.configuration.raw; > - context.activeState.agc.autoGainEnabled = !context.configuration.raw; > - context.activeState.agc.exposureValue = 0.0; > - > - context.activeState.agc.constraintMode = > - static_cast<controls::AeConstraintModeEnum>(agc_.constraintModes().begin()->first); > - context.activeState.agc.exposureMode = > - static_cast<controls::AeExposureModeEnum>(agc_.exposureModeHelpers().begin()->first); > + int ret = agc_.configure(context.configuration.agc, context.activeState.agc, { > + .sensor = context.camHelper.get(), > + .sensorInfo = context.sensorInfo, > + .sensorControls = context.sensorControls, > + .ctrlMap = context.ctrlMap, > + .autoAllowed = !context.configuration.raw, > + }); > + if (ret) > + return ret; > + > context.activeState.agc.meteringMode = > static_cast<controls::AeMeteringModeEnum>(meteringModes_.begin()->first); > > - /* Limit the frame duration to match current initialisation */ > - ControlInfo &frameDurationLimits = context.ctrlMap[&controls::FrameDurationLimits]; > - context.activeState.agc.minFrameDuration = std::chrono::microseconds(frameDurationLimits.min().get<int64_t>()); > - context.activeState.agc.maxFrameDuration = std::chrono::microseconds(frameDurationLimits.max().get<int64_t>()); > - > context.configuration.agc.measureWindow.h_offs = 0; > context.configuration.agc.measureWindow.v_offs = 0; > context.configuration.agc.measureWindow.h_size = configInfo.outputSize.width; > context.configuration.agc.measureWindow.v_size = configInfo.outputSize.height; > > - agc_.configure(context.configuration.sensor.lineDuration, context.camHelper.get()); > - > - agc_.setLimits(context.configuration.sensor.minExposureTime, > - context.configuration.sensor.maxExposureTime, > - context.configuration.sensor.minAnalogueGain, > - context.configuration.sensor.maxAnalogueGain, {}); > - > - context.activeState.agc.automatic.yTarget = agc_.effectiveYTarget(0, 1); > - > - agc_.resetFrameCount(); > - > return 0; > } > > @@ -311,73 +197,7 @@ void Agc::queueRequest(IPAContext &context, > { > auto &agc = context.activeState.agc; > > - if (!context.configuration.raw) { > - const auto &aeEnable = controls.get(controls::ExposureTimeMode); > - if (aeEnable && > - (*aeEnable == controls::ExposureTimeModeAuto) != agc.autoExposureEnabled) { > - agc.autoExposureEnabled = (*aeEnable == controls::ExposureTimeModeAuto); > - > - LOG(RkISP1Agc, Debug) > - << (agc.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 (!agc.autoExposureEnabled && !controls.get(controls::ExposureTime)) > - frameContext.agc.autoExposureModeChange = true; > - } > - > - const auto &agEnable = controls.get(controls::AnalogueGainMode); > - if (agEnable && > - (*agEnable == controls::AnalogueGainModeAuto) != agc.autoGainEnabled) { > - agc.autoGainEnabled = (*agEnable == controls::AnalogueGainModeAuto); > - > - LOG(RkISP1Agc, Debug) > - << (agc.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 (!agc.autoGainEnabled && !controls.get(controls::AnalogueGain)) > - frameContext.agc.autoGainModeChange = true; > - } > - } > - > - const auto &exposure = controls.get(controls::ExposureTime); > - if (exposure && !agc.autoExposureEnabled) { > - agc.manual.exposure = *exposure * 1.0us > - / context.configuration.sensor.lineDuration; > - > - LOG(RkISP1Agc, Debug) > - << "Set exposure to " << agc.manual.exposure; > - } > - > - const auto &gain = controls.get(controls::AnalogueGain); > - if (gain && !agc.autoGainEnabled) { > - agc.manual.gain = *gain; > - > - LOG(RkISP1Agc, Debug) << "Set gain to " << agc.manual.gain; > - } > - > - frameContext.agc.autoExposureEnabled = agc.autoExposureEnabled; > - frameContext.agc.autoGainEnabled = agc.autoGainEnabled; > - > - if (!frameContext.agc.autoExposureEnabled) > - frameContext.agc.exposure = agc.manual.exposure; > - if (!frameContext.agc.autoGainEnabled) > - frameContext.agc.gain = agc.manual.gain; > - > - if (!frameContext.agc.autoExposureEnabled && > - !frameContext.agc.autoGainEnabled) > - frameContext.agc.quantizationGain = 1.0; > + agc_.queueRequest(context.configuration.agc, agc, frameContext.agc, controls); > > const auto &meteringMode = controls.get(controls::AeMeteringMode); > if (meteringMode) { > @@ -386,42 +206,6 @@ void Agc::queueRequest(IPAContext &context, > static_cast<controls::AeMeteringModeEnum>(*meteringMode); > } > frameContext.agc.meteringMode = agc.meteringMode; > - > - const auto &exposureMode = controls.get(controls::AeExposureMode); > - if (exposureMode) > - agc.exposureMode = > - static_cast<controls::AeExposureModeEnum>(*exposureMode); > - frameContext.agc.exposureMode = agc.exposureMode; > - > - const auto &constraintMode = controls.get(controls::AeConstraintMode); > - if (constraintMode) > - agc.constraintMode = > - static_cast<controls::AeConstraintModeEnum>(*constraintMode); > - frameContext.agc.constraintMode = agc.constraintMode; > - > - const auto &exposureValue = controls.get(controls::ExposureValue); > - if (exposureValue) > - agc.exposureValue = *exposureValue; > - frameContext.agc.exposureValue = agc.exposureValue; > - > - const auto &frameDurationLimits = controls.get(controls::FrameDurationLimits); > - if (frameDurationLimits) { > - /* Limit the control value to the limits in ControlInfo */ > - ControlInfo &limits = context.ctrlMap[&controls::FrameDurationLimits]; > - int64_t minFrameDuration = > - std::clamp((*frameDurationLimits).front(), > - limits.min().get<int64_t>(), > - limits.max().get<int64_t>()); > - int64_t maxFrameDuration = > - std::clamp((*frameDurationLimits).back(), > - limits.min().get<int64_t>(), > - limits.max().get<int64_t>()); > - > - agc.minFrameDuration = std::chrono::microseconds(minFrameDuration); > - agc.maxFrameDuration = std::chrono::microseconds(maxFrameDuration); > - } > - frameContext.agc.minFrameDuration = agc.minFrameDuration; > - frameContext.agc.maxFrameDuration = agc.maxFrameDuration; > } > > /** > @@ -430,41 +214,13 @@ void Agc::queueRequest(IPAContext &context, > void Agc::prepare(IPAContext &context, const uint32_t frame, > IPAFrameContext &frameContext, RkISP1Params *params) > { > - uint32_t activeAutoExposure = context.activeState.agc.automatic.exposure; > - double activeAutoGain = context.activeState.agc.automatic.gain; > - double activeAutoQGain = context.activeState.agc.automatic.quantizationGain; > - > - /* Populate exposure and gain in auto mode */ > - if (frameContext.agc.autoExposureEnabled) { > - frameContext.agc.exposure = activeAutoExposure; > - frameContext.agc.quantizationGain = activeAutoQGain; > - } > - if (frameContext.agc.autoGainEnabled) { > - frameContext.agc.gain = activeAutoGain; > - frameContext.agc.quantizationGain = activeAutoQGain; > - } > - > - /* > - * Populate manual exposure and gain from the active auto values when > - * transitioning from auto to manual > - */ > - if (!frameContext.agc.autoExposureEnabled && frameContext.agc.autoExposureModeChange) { > - context.activeState.agc.manual.exposure = activeAutoExposure; > - frameContext.agc.exposure = activeAutoExposure; > - } > - if (!frameContext.agc.autoGainEnabled && frameContext.agc.autoGainModeChange) { > - context.activeState.agc.manual.gain = activeAutoGain; > - frameContext.agc.gain = activeAutoGain; > - frameContext.agc.quantizationGain = activeAutoQGain; > - } > + agc_.prepare(context.activeState.agc, frameContext.agc); > > if (context.configuration.compress.supported) { > frameContext.compress.enable = true; > frameContext.compress.gain = frameContext.agc.quantizationGain; > } > > - frameContext.agc.yTarget = context.activeState.agc.automatic.yTarget; > - > if (frame > 0 && !frameContext.agc.updateMetering) > return; > > @@ -520,50 +276,6 @@ void Agc::prepare(IPAContext &context, const uint32_t frame, > static_cast<rkisp1_cif_isp_histogram_mode>(hstConfig->mode)); > } > > -void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext, > - ControlList &metadata) > -{ > - utils::Duration exposureTime = context.configuration.sensor.lineDuration > - * frameContext.sensor.exposure; > - metadata.set(controls::AnalogueGain, frameContext.sensor.gain); > - metadata.set(controls::ExposureTime, exposureTime.get<std::micro>()); > - metadata.set(controls::FrameDuration, frameContext.agc.frameDuration.get<std::micro>()); > - metadata.set(controls::ExposureTimeMode, > - frameContext.agc.autoExposureEnabled > - ? controls::ExposureTimeModeAuto > - : controls::ExposureTimeModeManual); > - metadata.set(controls::AnalogueGainMode, > - frameContext.agc.autoGainEnabled > - ? controls::AnalogueGainModeAuto > - : controls::AnalogueGainModeManual); > - > - metadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode); > - metadata.set(controls::AeExposureMode, frameContext.agc.exposureMode); > - metadata.set(controls::AeConstraintMode, frameContext.agc.constraintMode); > - metadata.set(controls::ExposureValue, frameContext.agc.exposureValue); > -} > - > -/** > - * \brief Process frame duration and compute vblank > - * \param[in] context The shared IPA context > - * \param[in] frameContext The current frame context > - * \param[in] frameDuration The target frame duration > - * > - * Compute and populate vblank from the target frame duration. > - */ > -void Agc::processFrameDuration(IPAContext &context, > - IPAFrameContext &frameContext, > - utils::Duration frameDuration) > -{ > - IPACameraSensorInfo &sensorInfo = context.sensorInfo; > - utils::Duration lineDuration = context.configuration.sensor.lineDuration; > - > - frameContext.agc.vblank = (frameDuration / lineDuration) - sensorInfo.outputSize.height; > - > - /* Update frame duration accounting for line length quantization. */ > - frameContext.agc.frameDuration = (sensorInfo.outputSize.height + frameContext.agc.vblank) * lineDuration; > -} > - > namespace { > > class AgcTraits final : public AgcMeanLuminance::Traits > @@ -637,21 +349,6 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, > IPAFrameContext &frameContext, const rkisp1_stat_buffer *stats, > ControlList &metadata) > { > - if (!stats) { > - processFrameDuration(context, frameContext, > - frameContext.agc.minFrameDuration); > - fillMetadata(context, frameContext, metadata); > - return; > - } > - > - if (!(stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP)) { > - fillMetadata(context, frameContext, metadata); > - LOG(RkISP1Agc, Error) << "AUTOEXP data is missing in statistics"; > - return; > - } > - > - const utils::Duration &lineDuration = context.configuration.sensor.lineDuration; > - > /* > * \todo Verify that the exposure and gain applied by the sensor for > * this frame match what has been requested. This isn't a hard > @@ -660,95 +357,46 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, > * we receive), but is important in manual mode. > */ > > - const rkisp1_cif_isp_stat *params = &stats->params; > + const rkisp1_cif_isp_stat *params = nullptr; > > - /* > - * 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.agc.autoExposureEnabled) { > - minExposureTime = context.configuration.sensor.minExposureTime; > - maxExposureTime = std::clamp(frameContext.agc.maxFrameDuration, > - context.configuration.sensor.minExposureTime, > - context.configuration.sensor.maxExposureTime); > - } else { > - minExposureTime = context.configuration.sensor.lineDuration > - * frameContext.agc.exposure; > - maxExposureTime = minExposureTime; > + if (stats) { > + if (stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP) > + params = &stats->params; > + else > + LOG(RkISP1Agc, Error) << "AUTOEXP data is missing in statistics"; > } > > - if (frameContext.agc.autoGainEnabled) { > - minAnalogueGain = context.configuration.sensor.minAnalogueGain; > - maxAnalogueGain = context.configuration.sensor.maxAnalogueGain; > + if (params) { > + std::vector<AgcMeanLuminance::AgcConstraint> additionalConstraints; > + if (context.activeState.wdr.mode != controls::WdrOff) > + additionalConstraints.push_back(context.activeState.wdr.constraint); > + > + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {{ > + .traits = AgcTraits{ > + { params->ae.exp_mean, context.hw.numAeCells }, > + meteringModes_.at(frameContext.agc.meteringMode), > + }, > + .yHist = { > + /* The lower 4 bits are fractional and meant to be discarded. */ > + { params->hist.hist_bins, context.hw.numHistogramBins }, > + [](uint32_t x) { return x >> 4; }, > + }, > + .exposure = frameContext.sensor.exposure, > + /* > + * Include the quantization gain if it was applied. Do not use > + * compress.gain because it will include gains that shall not be > + * reported to the user when HDR is implemented. > + */ > + .gain = frameContext.sensor.gain > + * (frameContext.compress.enable ? frameContext.agc.quantizationGain : 1), > + .additionalConstraints = std::move(additionalConstraints), > + .lux = frameContext.lux.lux, > + }}, metadata); > } else { > - minAnalogueGain = frameContext.agc.gain; > - maxAnalogueGain = frameContext.agc.gain; > + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {}, metadata); > } > > - std::vector<AgcMeanLuminance::AgcConstraint> additionalConstraints; > - if (context.activeState.wdr.mode != controls::WdrOff) > - additionalConstraints.push_back(context.activeState.wdr.constraint); > - > - agc_.setLimits(minExposureTime, maxExposureTime, minAnalogueGain, maxAnalogueGain, > - std::move(additionalConstraints)); > - > - /* > - * The Agc algorithm needs to know the effective exposure value that was > - * applied to the sensor when the statistics were collected. > - */ > - utils::Duration exposureTime = lineDuration * frameContext.sensor.exposure; > - double analogueGain = frameContext.sensor.gain; > - utils::Duration effectiveExposureValue = exposureTime * analogueGain; > - > - /* > - * Include the quantization gain if it was applied. Do not use > - * compress.gain because it will include gains that shall not be > - * reported to the user when HDR is implemented. > - */ > - if (frameContext.compress.enable) > - effectiveExposureValue *= frameContext.agc.quantizationGain; > - > - /* The lower 4 bits are fractional and meant to be discarded. */ > - Histogram hist({ params->hist.hist_bins, context.hw.numHistogramBins }, > - [](uint32_t x) { return x >> 4; }); > - > - const auto &newEv = agc_.calculateNewEv({ > - .traits = AgcTraits{ > - { params->ae.exp_mean, context.hw.numAeCells }, > - meteringModes_.at(frameContext.agc.meteringMode), > - }, > - .yHist = hist, > - .effectiveExposureValue = effectiveExposureValue, > - .constraintModeIndex = frameContext.agc.constraintMode, > - .exposureModeIndex = frameContext.agc.exposureMode, > - .lux = frameContext.lux.lux, > - .exposureCompensation = pow(2.0, frameContext.agc.exposureValue), > - }); > - > - LOG(RkISP1Agc, Debug) > - << "Divided up exposure time, analogue gain, quantization gain" > - << " and digital gain are " << newEv.exposureTime << ", " << newEv.analogueGain > - << ", " << newEv.quantizationGain << " and " << newEv.digitalGain; > - > - IPAActiveState &activeState = context.activeState; > - /* Update the estimated exposure and gain. */ > - activeState.agc.automatic.exposure = newEv.exposureTime / lineDuration; > - activeState.agc.automatic.gain = newEv.analogueGain; > - activeState.agc.automatic.quantizationGain = newEv.quantizationGain; > - activeState.agc.automatic.yTarget = newEv.yTarget; > - /* > - * 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, newEv.exposureTime)); > - > - fillMetadata(context, frameContext, metadata); > + metadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode); > } > > REGISTER_IPA_ALGORITHM(Agc, "Agc") > diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h > index 0527ca0d5f..3a4d7bc546 100644 > --- a/src/ipa/rkisp1/algorithms/agc.h > +++ b/src/ipa/rkisp1/algorithms/agc.h > @@ -14,7 +14,7 @@ > > #include <libcamera/geometry.h> > > -#include "libipa/agc_mean_luminance.h" > +#include "libipa/agc.h" > > #include "algorithm.h" > > @@ -47,14 +47,8 @@ private: > uint8_t computeHistogramPredivider(const Size &size, > enum rkisp1_cif_isp_histogram_mode mode); > > - void fillMetadata(IPAContext &context, IPAFrameContext &frameContext, > - ControlList &metadata); > - void processFrameDuration(IPAContext &context, > - IPAFrameContext &frameContext, > - utils::Duration frameDuration); > - > std::map<int32_t, std::vector<uint8_t>> meteringModes_; > - AgcMeanLuminance agc_; > + AgcAlgorithm agc_; > }; > > } /* namespace ipa::rkisp1::algorithms */ > diff --git a/src/ipa/rkisp1/algorithms/lux.cpp b/src/ipa/rkisp1/algorithms/lux.cpp > index 86e46c492f..ce6928a55d 100644 > --- a/src/ipa/rkisp1/algorithms/lux.cpp > +++ b/src/ipa/rkisp1/algorithms/lux.cpp > @@ -74,7 +74,7 @@ void Lux::process(IPAContext &context, > if (!stats) > return; > > - utils::Duration exposureTime = context.configuration.sensor.lineDuration * > + utils::Duration exposureTime = context.configuration.agc.lineDuration * > frameContext.sensor.exposure; > double gain = frameContext.sensor.gain; > > diff --git a/src/ipa/rkisp1/ipa_context.cpp b/src/ipa/rkisp1/ipa_context.cpp > index 1f94afda6b..47691674ad 100644 > --- a/src/ipa/rkisp1/ipa_context.cpp > +++ b/src/ipa/rkisp1/ipa_context.cpp > @@ -86,21 +86,6 @@ namespace libcamera::ipa::rkisp1 { > * \var IPASessionConfiguration::sensor > * \brief Sensor-specific configuration of the IPA > * > - * \var IPASessionConfiguration::sensor.minExposureTime > - * \brief Minimum exposure time supported with the sensor > - * > - * \var IPASessionConfiguration::sensor.maxExposureTime > - * \brief Maximum exposure time supported with the sensor > - * > - * \var IPASessionConfiguration::sensor.minAnalogueGain > - * \brief Minimum analogue gain supported with the sensor > - * > - * \var IPASessionConfiguration::sensor.maxAnalogueGain > - * \brief Maximum analogue gain supported with the sensor > - * > - * \var IPASessionConfiguration::sensor.lineDuration > - * \brief Line duration in microseconds > - * > * \var IPASessionConfiguration::sensor.size > * \brief Sensor output resolution > */ > @@ -147,49 +132,8 @@ namespace libcamera::ipa::rkisp1 { > * \var IPAActiveState::agc > * \brief State for the Automatic Gain Control algorithm > * > - * 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. > - * > - * \struct IPAActiveState::agc.manual > - * \brief Manual exposure time and analog gain (set through requests) > - * > - * \var IPAActiveState::agc.manual.exposure > - * \brief Manual exposure time expressed as a number of lines as set by the > - * ExposureTime control > - * > - * \var IPAActiveState::agc.manual.gain > - * \brief Manual analogue gain as set by the AnalogueGain control > - * > - * \struct IPAActiveState::agc.automatic > - * \brief Automatic exposure time and analog gain (computed by the algorithm) > - * > - * \var IPAActiveState::agc.automatic.exposure > - * \brief Automatic exposure time expressed as a number of lines > - * > - * \var IPAActiveState::agc.automatic.gain > - * \brief Automatic analogue gain multiplier > - * > - * \var IPAActiveState::agc.autoExposureEnabled > - * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control > - * > - * \var IPAActiveState::agc.autoGainEnabled > - * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control > - * > - * \var IPAActiveState::agc.constraintMode > - * \brief Constraint mode as set by the AeConstraintMode control > - * > - * \var IPAActiveState::agc.exposureMode > - * \brief Exposure mode as set by the AeExposureMode control > - * > * \var IPAActiveState::agc.meteringMode > * \brief Metering mode as set by the AeMeteringMode control > - * > - * \var IPAActiveState::agc.minFrameDuration > - * \brief Minimum frame duration as set by the FrameDurationLimits control > - * > - * \var IPAActiveState::agc.maxFrameDuration > - * \brief Maximum frame duration as set by the FrameDurationLimits control > */ > > /** > @@ -314,53 +258,11 @@ namespace libcamera::ipa::rkisp1 { > * the vertical blanking period is determined to maintain a consistent frame > * rate matched to the FrameDurationLimits as set by the user. > * > - * \var IPAFrameContext::agc.exposure > - * \brief Exposure time expressed as a number of lines computed by the algorithm > - * > - * \var IPAFrameContext::agc.gain > - * \brief Analogue gain multiplier computed by the algorithm > - * > - * The gain should be adapted to the sensor specific gain code before applying. > - * > - * \var IPAFrameContext::agc.vblank > - * \brief Vertical blanking parameter computed by the algorithm > - * > - * \var IPAFrameContext::agc.autoExposureEnabled > - * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control > - * > - * \var IPAFrameContext::agc.autoGainEnabled > - * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control > - * > - * \var IPAFrameContext::agc.constraintMode > - * \brief Constraint mode as set by the AeConstraintMode control > - * > - * \var IPAFrameContext::agc.exposureMode > - * \brief Exposure mode as set by the AeExposureMode control > - * > * \var IPAFrameContext::agc.meteringMode > * \brief Metering mode as set by the AeMeteringMode control > * > - * \var IPAFrameContext::agc.minFrameDuration > - * \brief Minimum frame duration as set by the FrameDurationLimits control > - * > - * \var IPAFrameContext::agc.maxFrameDuration > - * \brief Maximum frame duration as set by the FrameDurationLimits control > - * > - * \var IPAFrameContext::agc.frameDuration > - * \brief The actual FrameDuration used by the algorithm for the frame > - * > * \var IPAFrameContext::agc.updateMetering > * \brief Indicate if new ISP AGC metering parameters need to be applied > - * > - * \var IPAFrameContext::agc.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 IPAFrameContext::agc.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. > */ > > /** > diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h > index cd213dd991..cc07bb9462 100644 > --- a/src/ipa/rkisp1/ipa_context.h > +++ b/src/ipa/rkisp1/ipa_context.h > @@ -24,7 +24,7 @@ > #include "libcamera/internal/matrix.h" > #include "libcamera/internal/vector.h" > > -#include "libipa/agc_mean_luminance.h" > +#include "libipa/agc.h" > #include "libipa/awb.h" > #include "libipa/camera_sensor_helper.h" > #include "libipa/ccm.h" > @@ -57,7 +57,7 @@ struct RKISP1AwbSession { > }; > > struct IPASessionConfiguration { > - struct { > + struct Agc : agc::Session { > struct rkisp1_cif_isp_window measureWindow; > } agc; > > @@ -68,12 +68,6 @@ struct IPASessionConfiguration { > } compress; > > struct { > - utils::Duration minExposureTime; > - utils::Duration maxExposureTime; > - double minAnalogueGain; > - double maxAnalogueGain; > - > - utils::Duration lineDuration; > Size size; > } sensor; > > @@ -82,26 +76,8 @@ struct IPASessionConfiguration { > }; > > struct IPAActiveState { > - struct { > - 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; > + struct Agc : agc::ActiveState { > controls::AeMeteringModeEnum meteringMode; > - utils::Duration minFrameDuration; > - utils::Duration maxFrameDuration; > } agc; > > ipa::awb::ActiveState awb; > @@ -145,24 +121,9 @@ struct IPAActiveState { > }; > > struct IPAFrameContext : public FrameContext { > - struct { > - uint32_t exposure; > - double gain; > - double exposureValue; > - double quantizationGain; > - uint32_t vblank; > - double yTarget; > - bool autoExposureEnabled; > - bool autoGainEnabled; > - controls::AeConstraintModeEnum constraintMode; > - controls::AeExposureModeEnum exposureMode; > + struct Agc : agc::FrameContext { > controls::AeMeteringModeEnum meteringMode; > - utils::Duration minFrameDuration; > - utils::Duration maxFrameDuration; > - utils::Duration frameDuration; > bool updateMetering; > - bool autoExposureModeChange; > - bool autoGainModeChange; > } agc; > > ipa::awb::FrameContext awb; > diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp > index 98ec5a5748..731a362cee 100644 > --- a/src/ipa/rkisp1/rkisp1.cpp > +++ b/src/ipa/rkisp1/rkisp1.cpp > @@ -6,8 +6,6 @@ > */ > > #include <algorithm> > -#include <array> > -#include <chrono> These changes look unrelated? > #include <stdint.h> > #include <string.h> > > @@ -40,8 +38,6 @@ namespace libcamera { > > LOG_DEFINE_CATEGORY(IPARkISP1) > > -using namespace std::literals::chrono_literals; > - > namespace ipa::rkisp1 { > > /* Maximum number of frame contexts to be held */ > -- > 2.55.0 > This is a massive beast. Thanks for squashing it. I like that it is now possible to compare new code and old code in one patch. I didn't do another detailed review, but that already happened on v4. So Reviewed-by: Stefan Klug <stefan.klug@ideasonboard.com> Tested-by: Stefan Klug <stefan.klug@ideasonboard.com> Best regards, Stefan
2026. 08. 19. 17:31 keltezéssel, Stefan Klug írta: > Hi Barnabás, > > Thank you for the patch. > > Quoting Barnabás Pőcze (2026-08-17 13:43:22) >> Add a class that implements the `Algorithm` interface using `AgcMeanLuminance` >> based on the rkisp1 `Agc` algorithm, with the following main adjustments: >> >> * the parameters for `process()` have been made optional to handle >> the cases where statistics are not available; >> * the "raw" capture check has been replaced with the "autoAllowed" >> session parameter; >> * the controls are only provided after `configure()`. > > Eek I completely missed that in v4. Thanks for pointing it out. > I thought that this was problematic as cam showed only controls that > were available before configure. But testing it reveals that this is not > the case. So either I remember incorrectly or cam got fixed :-) Well, every user calls `configure()` in its `init()` to provide initial controls for the camera (before configuration). > >> >> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> >> --- >> src/ipa/libipa/agc.cpp | 637 +++++++++++++++++++++++++++++- >> src/ipa/libipa/agc.h | 106 +++++ >> src/ipa/rkisp1/algorithms/agc.cpp | 464 +++------------------- >> src/ipa/rkisp1/algorithms/agc.h | 10 +- >> src/ipa/rkisp1/algorithms/lux.cpp | 2 +- >> src/ipa/rkisp1/ipa_context.cpp | 98 ----- >> src/ipa/rkisp1/ipa_context.h | 47 +-- >> src/ipa/rkisp1/rkisp1.cpp | 4 - >> 8 files changed, 805 insertions(+), 563 deletions(-) >> >> diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp >> index 415a6d831f..3c6b12e452 100644 >> --- a/src/ipa/libipa/agc.cpp >> +++ b/src/ipa/libipa/agc.cpp >> @@ -1,16 +1,32 @@ >> /* SPDX-License-Identifier: LGPL-2.1-or-later */ >> /* >> - * Copyright (C) 2026 Ideas On Board >> + * Copyright (C) 2021-2026 Ideas On Board >> * >> * Auto exposure/gain algorithm for implementing the IPA-specific AGC algorithms > > Nit: line break How do you mean? > >> */ >> >> #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) >> + >> namespace agc { >> >> /** >> @@ -40,6 +56,625 @@ namespace agc { >> >> } /* namespace 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 >> + */ >> + > > Moving these variables into agc::FrameContext and agx::ActiveState in a separate > preparatory patch might have reduced the size of this patch by quite a > bit. I don't want to send a new Yak, so I won't dwell on it :-) I thought about it, but now I'm not sure why I didn't do it. Maybe I'll try again. > >> +/** >> + * \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 >> + * >> + * \note The IPA algorithm implementation will most likely need to call >> + * this in its Algorithm::init() implementation in order to provide >> + * the initial controls for the camera. >> + */ >> +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; >> + >> + /* \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] } }, >> + }; >> + config.ctrlMap[&controls::ExposureTimeMode] = ControlInfo{ >> + {{ controls::ExposureTimeModeAuto, controls::ExposureTimeModeManual }}, >> + controls::ExposureTimeModeAuto, >> + }; >> + config.ctrlMap[&controls::AnalogueGainMode] = ControlInfo{ >> + {{ controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual }}, >> + controls::AnalogueGainModeAuto, >> + }; >> + config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); >> + config.ctrlMap.merge(impl_.controls()); >> + >> + 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) >> +{ >> + if (!params) { >> + processFrameDuration(session, frameContext, frameContext.minFrameDuration); >> + fillMetadata(session, frameContext, metadata); >> + return; >> + } >> + >> + ASSERT(session.autoAllowed); >> + >> + const utils::Duration &lineDuration = session.lineDuration; >> + >> + /* >> + * 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; >> + >> + LOG(Agc, Debug) >> + << "Divided up exposure time, analogue gain, quantization gain" >> + << " and digital gain are " << newEv.exposureTime >> + << ", " << 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. >> + */ >> + processFrameDuration(session, frameContext, >> + std::max(frameContext.minFrameDuration, newEv.exposureTime)); >> + >> + fillMetadata(session, frameContext, metadata); >> +} >> + >> +/** >> + * \brief Process frame duration and compute vblank >> + * \param[in] session The session parameters >> + * \param[in] frameContext The current frame context >> + * \param[in] frameDuration The target frame duration >> + * >> + * Compute and populate vblank from the target frame duration. >> + */ >> +void AgcAlgorithm::processFrameDuration(const agc::Session &session, >> + agc::FrameContext &frameContext, >> + utils::Duration frameDuration) >> +{ >> + const utils::Duration &lineDuration = session.lineDuration; >> + >> + 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; >> +} >> + >> +void AgcAlgorithm::fillMetadata(const agc::Session &session, > > Does this one need documentation as it lives in libipa now? I don't know. It's a private function, implementation detail, and fairly straightforward in my opinion. > >> + const agc::FrameContext &frameContext, >> + ControlList &metadata) >> +{ >> + >> + metadata.set(controls::AnalogueGain, frameContext.gain); >> + metadata.set(controls::ExposureTime, >> + utils::Duration(session.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/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h >> index cd213dd991..cc07bb9462 100644 >> --- a/src/ipa/rkisp1/ipa_context.h >> +++ b/src/ipa/rkisp1/ipa_context.h >> @@ -24,7 +24,7 @@ >> #include "libcamera/internal/matrix.h" >> #include "libcamera/internal/vector.h" >> >> -#include "libipa/agc_mean_luminance.h" >> +#include "libipa/agc.h" >> #include "libipa/awb.h" >> #include "libipa/camera_sensor_helper.h" >> #include "libipa/ccm.h" >> @@ -57,7 +57,7 @@ struct RKISP1AwbSession { >> }; >> >> struct IPASessionConfiguration { >> - struct { >> + struct Agc : agc::Session { >> struct rkisp1_cif_isp_window measureWindow; >> } agc; >> >> @@ -68,12 +68,6 @@ struct IPASessionConfiguration { >> } compress; >> >> struct { >> - utils::Duration minExposureTime; >> - utils::Duration maxExposureTime; >> - double minAnalogueGain; >> - double maxAnalogueGain; >> - >> - utils::Duration lineDuration; >> Size size; >> } sensor; >> >> @@ -82,26 +76,8 @@ struct IPASessionConfiguration { >> }; >> >> struct IPAActiveState { >> - struct { >> - 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; >> + struct Agc : agc::ActiveState { >> controls::AeMeteringModeEnum meteringMode; >> - utils::Duration minFrameDuration; >> - utils::Duration maxFrameDuration; >> } agc; >> >> ipa::awb::ActiveState awb; >> @@ -145,24 +121,9 @@ struct IPAActiveState { >> }; >> >> struct IPAFrameContext : public FrameContext { >> - struct { >> - uint32_t exposure; >> - double gain; >> - double exposureValue; >> - double quantizationGain; >> - uint32_t vblank; >> - double yTarget; >> - bool autoExposureEnabled; >> - bool autoGainEnabled; >> - controls::AeConstraintModeEnum constraintMode; >> - controls::AeExposureModeEnum exposureMode; >> + struct Agc : agc::FrameContext { >> controls::AeMeteringModeEnum meteringMode; >> - utils::Duration minFrameDuration; >> - utils::Duration maxFrameDuration; >> - utils::Duration frameDuration; >> bool updateMetering; >> - bool autoExposureModeChange; >> - bool autoGainModeChange; >> } agc; >> >> ipa::awb::FrameContext awb; >> diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp >> index 98ec5a5748..731a362cee 100644 >> --- a/src/ipa/rkisp1/rkisp1.cpp >> +++ b/src/ipa/rkisp1/rkisp1.cpp >> @@ -6,8 +6,6 @@ >> */ >> >> #include <algorithm> >> -#include <array> >> -#include <chrono> > > These changes look unrelated? These are now unused because the code has been moved. > >> #include <stdint.h> >> #include <string.h> >> >> @@ -40,8 +38,6 @@ namespace libcamera { >> >> LOG_DEFINE_CATEGORY(IPARkISP1) >> >> -using namespace std::literals::chrono_literals; >> - >> namespace ipa::rkisp1 { >> >> /* Maximum number of frame contexts to be held */ >> -- >> 2.55.0 >> > > This is a massive beast. Thanks for squashing it. I like that it is now > possible to compare new code and old code in one patch. I didn't do > another detailed review, but that already happened on v4. > > So > > Reviewed-by: Stefan Klug <stefan.klug@ideasonboard.com> > Tested-by: Stefan Klug <stefan.klug@ideasonboard.com> > > Best regards, > Stefan
Hi, Quoting Jacopo Mondi (2026-08-17 17:06:39) > Hi Barnabás > > On Mon, Aug 17, 2026 at 01:43:22PM +0200, Barnabás Pőcze wrote: > > Add a class that implements the `Algorithm` interface using `AgcMeanLuminance` > > based on the rkisp1 `Agc` algorithm, with the following main adjustments: > > > > * the parameters for `process()` have been made optional to handle > > the cases where statistics are not available; > > * the "raw" capture check has been replaced with the "autoAllowed" > > session parameter; > > * the controls are only provided after `configure()`. > > > > Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> > > --- > > src/ipa/libipa/agc.cpp | 637 +++++++++++++++++++++++++++++- > > src/ipa/libipa/agc.h | 106 +++++ > > src/ipa/rkisp1/algorithms/agc.cpp | 464 +++------------------- > > src/ipa/rkisp1/algorithms/agc.h | 10 +- > > src/ipa/rkisp1/algorithms/lux.cpp | 2 +- > > src/ipa/rkisp1/ipa_context.cpp | 98 ----- > > src/ipa/rkisp1/ipa_context.h | 47 +-- > > src/ipa/rkisp1/rkisp1.cpp | 4 - > > 8 files changed, 805 insertions(+), 563 deletions(-) > > > > diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp > > index 415a6d831f..3c6b12e452 100644 > > --- a/src/ipa/libipa/agc.cpp > > +++ b/src/ipa/libipa/agc.cpp > > @@ -1,16 +1,32 @@ > > /* SPDX-License-Identifier: LGPL-2.1-or-later */ > > /* > > - * Copyright (C) 2026 Ideas On Board > > + * Copyright (C) 2021-2026 Ideas On Board > > * > > * Auto exposure/gain algorithm for implementing the IPA-specific AGC algorithms > > */ > > > > #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) > > + > > namespace agc { > > > > /** > > @@ -40,6 +56,625 @@ namespace agc { > > > > } /* namespace 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 > > + * > > + * \note The IPA algorithm implementation will most likely need to call > > + * this in its Algorithm::init() implementation in order to provide > > + * the initial controls for the camera. > > + */ > > +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; > > + > > + /* \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] } }, > > + }; > > + config.ctrlMap[&controls::ExposureTimeMode] = ControlInfo{ > > + {{ controls::ExposureTimeModeAuto, controls::ExposureTimeModeManual }}, > > + controls::ExposureTimeModeAuto, > > + }; > > + config.ctrlMap[&controls::AnalogueGainMode] = ControlInfo{ > > + {{ controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual }}, > > + controls::AnalogueGainModeAuto, > > + }; > > + config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); > > + config.ctrlMap.merge(impl_.controls()); > > + > > + 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) > > +{ > > + if (!params) { > > + processFrameDuration(session, frameContext, frameContext.minFrameDuration); > > + fillMetadata(session, frameContext, metadata); > > + return; > > + } > > + > > + ASSERT(session.autoAllowed); > > + > > + const utils::Duration &lineDuration = session.lineDuration; > > + > > + /* > > + * 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; > > + > > + LOG(Agc, Debug) > > + << "Divided up exposure time, analogue gain, quantization gain" > > + << " and digital gain are " << newEv.exposureTime > > + << ", " << 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. > > + */ > > + processFrameDuration(session, frameContext, > > + std::max(frameContext.minFrameDuration, newEv.exposureTime)); > > + > > + fillMetadata(session, frameContext, metadata); > > +} > > + > > +/** > > + * \brief Process frame duration and compute vblank > > + * \param[in] session The session parameters > > + * \param[in] frameContext The current frame context > > + * \param[in] frameDuration The target frame duration > > + * > > + * Compute and populate vblank from the target frame duration. > > + */ > > +void AgcAlgorithm::processFrameDuration(const agc::Session &session, > > + agc::FrameContext &frameContext, > > + utils::Duration frameDuration) > > +{ > > + const utils::Duration &lineDuration = session.lineDuration; > > + > > + 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; > > +} > > + > > +void AgcAlgorithm::fillMetadata(const agc::Session &session, > > + const agc::FrameContext &frameContext, > > + ControlList &metadata) > > +{ > > + > > + metadata.set(controls::AnalogueGain, frameContext.gain); > > + metadata.set(controls::ExposureTime, > > + utils::Duration(session.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 4789c06ef8..66aa0eacb0 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 { > > > > @@ -21,6 +27,61 @@ namespace ipa { > > > > namespace agc { > > > > +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; > > +}; > > + > > [[nodiscard]] > > inline std::pair<uint32_t, double> > > extractControls(const ControlList &controls, const CameraSensorHelper *sensor) > > @@ -47,6 +108,51 @@ prepareControls(ControlList &controls, const CameraSensorHelper *sensor, > > > > } /* namespace agc */ > > > > +class AgcAlgorithm > > +{ > > +public: > > + struct ConfigurationParams { > > + const CameraSensorHelper *sensor; > > + const IPACameraSensorInfo &sensorInfo; > > + const ControlInfoMap &sensorControls; > > + ControlInfoMap::Map &ctrlMap; > > + bool autoAllowed = true; > > + }; > > + > > + struct ProcessParams { > > + const AgcMeanLuminance::Traits &traits; > > + const Histogram &yHist; > > + uint32_t exposure; > > + double gain; > > + std::vector<AgcMeanLuminance::AgcConstraint> &&additionalConstraints = {}; > > + double lux = 0; > > + }; > > + > > + 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); > > + > > + void process(const agc::Session &session, agc::ActiveState &state, > > + agc::FrameContext &frameContext, std::optional<ProcessParams> &¶ms, > > + ControlList &metadata); > > + > > +private: > > + void processFrameDuration(const agc::Session &session, > > + agc::FrameContext &frameContext, > > + utils::Duration frameDuration); > > + void fillMetadata(const agc::Session &session, > > + const agc::FrameContext &frameContext, > > + ControlList &metadata); > > + > > + AgcMeanLuminance impl_; > > +}; > > + > > } /* namespace ipa */ > > > > } /* namespace libcamera */ > > diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp > > index fc228452c3..4c2a066e86 100644 > > --- a/src/ipa/rkisp1/algorithms/agc.cpp > > +++ b/src/ipa/rkisp1/algorithms/agc.cpp > > @@ -8,9 +8,7 @@ > > #include "agc.h" > > > > #include <algorithm> > > -#include <chrono> > > #include <cmath> > > -#include <tuple> > > #include <vector> > > > > #include <libcamera/base/log.h> > > @@ -35,89 +33,6 @@ namespace ipa::rkisp1::algorithms { > > > > LOG_DEFINE_CATEGORY(RkISP1Agc) > > > > -namespace { > > - > > -void reconfigure(IPAContext &context) > > -{ > > - context.configuration.sensor.lineDuration = > > - context.sensorInfo.minLineLength * 1.0s / context.sensorInfo.pixelRate; > > - > > - double lineDurationUs = context.configuration.sensor.lineDuration.get<std::micro>(); > > - > > - /* > > - * Compute exposure time limits from the V4L2_CID_EXPOSURE control > > - * limits and the line duration. > > - */ > > - > > - const ControlInfo &v4l2Exposure = context.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>(); > > - context.ctrlMap[&controls::ExposureTime] = ControlInfo{ > > - static_cast<int32_t>(minExposure * lineDurationUs), > > - static_cast<int32_t>(maxExposure * lineDurationUs), > > - static_cast<int32_t>(defExposure * lineDurationUs), > > - }; > > - > > - /* Compute the analogue gain limits. */ > > - const ControlInfo &v4l2Gain = context.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; > > - float minGain = context.camHelper->gain(v4l2Gain.min().get<int32_t>()); > > - float maxGain = context.camHelper->gain(v4l2Gain.max().get<int32_t>()); > > - float defGain = context.camHelper->gain(v4l2Gain.def().get<int32_t>()); > > - context.ctrlMap[&controls::AnalogueGain] = ControlInfo{ > > - minGain, > > - maxGain, > > - defGain, > > - }; > > - > > - LOG(RkISP1Agc, 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 = context.sensorControls.find(V4L2_CID_HBLANK)->second; > > - uint32_t hblank = v4l2HBlank.def().get<int32_t>(); > > - uint32_t lineLength = context.sensorInfo.outputSize.width + hblank; > > - > > - const ControlInfo &v4l2VBlank = context.sensorControls.find(V4L2_CID_VBLANK)->second; > > - std::array<uint32_t, 3> frameHeights{ > > - v4l2VBlank.min().get<int32_t>() + context.sensorInfo.outputSize.height, > > - v4l2VBlank.max().get<int32_t>() + context.sensorInfo.outputSize.height, > > - v4l2VBlank.def().get<int32_t>() + context.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 / (context.sensorInfo.pixelRate / 1000000U); > > - } > > - > > - context.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ > > - frameDurations[0], > > - frameDurations[1], > > - Span<const int64_t, 2>{ { frameDurations[2], frameDurations[2] } }, > > - }; > > - > > - /* > > - * 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 > > - */ > > - context.configuration.sensor.minExposureTime = minExposure * context.configuration.sensor.lineDuration; > > - context.configuration.sensor.maxExposureTime = maxExposure * context.configuration.sensor.lineDuration; > > - context.configuration.sensor.minAnalogueGain = minGain; > > - context.configuration.sensor.maxAnalogueGain = maxGain; > > -} > > - > > -} /* namespace */ > > - > > /** > > * \class Agc > > * \brief A mean-based auto-exposure algorithm > > @@ -221,7 +136,16 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) > > { > > int ret; > > > > - ret = agc_.parseTuningData(tuningData); > > + ret = agc_.init(tuningData); > > + if (ret) > > + return ret; > > + > > + ret = agc_.configure(context.configuration.agc, context.activeState.agc, { > > + .sensor = context.camHelper.get(), > > + .sensorInfo = context.sensorInfo, > > + .sensorControls = context.sensorControls, > > + .ctrlMap = context.ctrlMap, > > + }); > > The only comment, which might eventually be addressed as an on-top > change, is about the requirement to call AgcAlgorithm::init() and > configure() in the IPA init function. > > What if the paramters required for configure() are passed to > AgcAlgorithm::init() and this function calls AgcAlgorithm::configure() > internally ? Now I stumbled over that part in my review also. I think I like that idea. Having the outer algorithm call the equally named function only (init() calls init() and configure() calls configure()) seems to be a good idea. Best regards, Stefan > > Apart from this: > Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > > Thanks > j > > > if (ret) > > return ret; > > > > @@ -230,21 +154,6 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) > > if (ret) > > return ret; > > > > - context.ctrlMap[&controls::ExposureTimeMode] = > > - ControlInfo({ { ControlValue(controls::ExposureTimeModeAuto), > > - ControlValue(controls::ExposureTimeModeManual) } }, > > - ControlValue(controls::ExposureTimeModeAuto)); > > - context.ctrlMap[&controls::AnalogueGainMode] = > > - ControlInfo({ { ControlValue(controls::AnalogueGainModeAuto), > > - ControlValue(controls::AnalogueGainModeManual) } }, > > - ControlValue(controls::AnalogueGainModeAuto)); > > - /* \todo Move this to the Camera class */ > > - context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true, true); > > - context.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); > > - context.ctrlMap.merge(agc_.controls()); > > - > > - reconfigure(context); > > - > > return 0; > > } > > > > @@ -257,47 +166,24 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) > > */ > > int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) > > { > > - reconfigure(context); > > - > > - /* Configure the default exposure and gain. */ > > - context.activeState.agc.automatic.gain = context.configuration.sensor.minAnalogueGain; > > - context.activeState.agc.automatic.exposure = > > - 10ms / context.configuration.sensor.lineDuration; > > - context.activeState.agc.automatic.quantizationGain = 1.0; > > - context.activeState.agc.manual.gain = context.activeState.agc.automatic.gain; > > - context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure; > > - context.activeState.agc.autoExposureEnabled = !context.configuration.raw; > > - context.activeState.agc.autoGainEnabled = !context.configuration.raw; > > - context.activeState.agc.exposureValue = 0.0; > > - > > - context.activeState.agc.constraintMode = > > - static_cast<controls::AeConstraintModeEnum>(agc_.constraintModes().begin()->first); > > - context.activeState.agc.exposureMode = > > - static_cast<controls::AeExposureModeEnum>(agc_.exposureModeHelpers().begin()->first); > > + int ret = agc_.configure(context.configuration.agc, context.activeState.agc, { > > + .sensor = context.camHelper.get(), > > + .sensorInfo = context.sensorInfo, > > + .sensorControls = context.sensorControls, > > + .ctrlMap = context.ctrlMap, > > + .autoAllowed = !context.configuration.raw, > > + }); > > + if (ret) > > + return ret; > > + > > context.activeState.agc.meteringMode = > > static_cast<controls::AeMeteringModeEnum>(meteringModes_.begin()->first); > > > > - /* Limit the frame duration to match current initialisation */ > > - ControlInfo &frameDurationLimits = context.ctrlMap[&controls::FrameDurationLimits]; > > - context.activeState.agc.minFrameDuration = std::chrono::microseconds(frameDurationLimits.min().get<int64_t>()); > > - context.activeState.agc.maxFrameDuration = std::chrono::microseconds(frameDurationLimits.max().get<int64_t>()); > > - > > context.configuration.agc.measureWindow.h_offs = 0; > > context.configuration.agc.measureWindow.v_offs = 0; > > context.configuration.agc.measureWindow.h_size = configInfo.outputSize.width; > > context.configuration.agc.measureWindow.v_size = configInfo.outputSize.height; > > > > - agc_.configure(context.configuration.sensor.lineDuration, context.camHelper.get()); > > - > > - agc_.setLimits(context.configuration.sensor.minExposureTime, > > - context.configuration.sensor.maxExposureTime, > > - context.configuration.sensor.minAnalogueGain, > > - context.configuration.sensor.maxAnalogueGain, {}); > > - > > - context.activeState.agc.automatic.yTarget = agc_.effectiveYTarget(0, 1); > > - > > - agc_.resetFrameCount(); > > - > > return 0; > > } > > > > @@ -311,73 +197,7 @@ void Agc::queueRequest(IPAContext &context, > > { > > auto &agc = context.activeState.agc; > > > > - if (!context.configuration.raw) { > > - const auto &aeEnable = controls.get(controls::ExposureTimeMode); > > - if (aeEnable && > > - (*aeEnable == controls::ExposureTimeModeAuto) != agc.autoExposureEnabled) { > > - agc.autoExposureEnabled = (*aeEnable == controls::ExposureTimeModeAuto); > > - > > - LOG(RkISP1Agc, Debug) > > - << (agc.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 (!agc.autoExposureEnabled && !controls.get(controls::ExposureTime)) > > - frameContext.agc.autoExposureModeChange = true; > > - } > > - > > - const auto &agEnable = controls.get(controls::AnalogueGainMode); > > - if (agEnable && > > - (*agEnable == controls::AnalogueGainModeAuto) != agc.autoGainEnabled) { > > - agc.autoGainEnabled = (*agEnable == controls::AnalogueGainModeAuto); > > - > > - LOG(RkISP1Agc, Debug) > > - << (agc.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 (!agc.autoGainEnabled && !controls.get(controls::AnalogueGain)) > > - frameContext.agc.autoGainModeChange = true; > > - } > > - } > > - > > - const auto &exposure = controls.get(controls::ExposureTime); > > - if (exposure && !agc.autoExposureEnabled) { > > - agc.manual.exposure = *exposure * 1.0us > > - / context.configuration.sensor.lineDuration; > > - > > - LOG(RkISP1Agc, Debug) > > - << "Set exposure to " << agc.manual.exposure; > > - } > > - > > - const auto &gain = controls.get(controls::AnalogueGain); > > - if (gain && !agc.autoGainEnabled) { > > - agc.manual.gain = *gain; > > - > > - LOG(RkISP1Agc, Debug) << "Set gain to " << agc.manual.gain; > > - } > > - > > - frameContext.agc.autoExposureEnabled = agc.autoExposureEnabled; > > - frameContext.agc.autoGainEnabled = agc.autoGainEnabled; > > - > > - if (!frameContext.agc.autoExposureEnabled) > > - frameContext.agc.exposure = agc.manual.exposure; > > - if (!frameContext.agc.autoGainEnabled) > > - frameContext.agc.gain = agc.manual.gain; > > - > > - if (!frameContext.agc.autoExposureEnabled && > > - !frameContext.agc.autoGainEnabled) > > - frameContext.agc.quantizationGain = 1.0; > > + agc_.queueRequest(context.configuration.agc, agc, frameContext.agc, controls); > > > > const auto &meteringMode = controls.get(controls::AeMeteringMode); > > if (meteringMode) { > > @@ -386,42 +206,6 @@ void Agc::queueRequest(IPAContext &context, > > static_cast<controls::AeMeteringModeEnum>(*meteringMode); > > } > > frameContext.agc.meteringMode = agc.meteringMode; > > - > > - const auto &exposureMode = controls.get(controls::AeExposureMode); > > - if (exposureMode) > > - agc.exposureMode = > > - static_cast<controls::AeExposureModeEnum>(*exposureMode); > > - frameContext.agc.exposureMode = agc.exposureMode; > > - > > - const auto &constraintMode = controls.get(controls::AeConstraintMode); > > - if (constraintMode) > > - agc.constraintMode = > > - static_cast<controls::AeConstraintModeEnum>(*constraintMode); > > - frameContext.agc.constraintMode = agc.constraintMode; > > - > > - const auto &exposureValue = controls.get(controls::ExposureValue); > > - if (exposureValue) > > - agc.exposureValue = *exposureValue; > > - frameContext.agc.exposureValue = agc.exposureValue; > > - > > - const auto &frameDurationLimits = controls.get(controls::FrameDurationLimits); > > - if (frameDurationLimits) { > > - /* Limit the control value to the limits in ControlInfo */ > > - ControlInfo &limits = context.ctrlMap[&controls::FrameDurationLimits]; > > - int64_t minFrameDuration = > > - std::clamp((*frameDurationLimits).front(), > > - limits.min().get<int64_t>(), > > - limits.max().get<int64_t>()); > > - int64_t maxFrameDuration = > > - std::clamp((*frameDurationLimits).back(), > > - limits.min().get<int64_t>(), > > - limits.max().get<int64_t>()); > > - > > - agc.minFrameDuration = std::chrono::microseconds(minFrameDuration); > > - agc.maxFrameDuration = std::chrono::microseconds(maxFrameDuration); > > - } > > - frameContext.agc.minFrameDuration = agc.minFrameDuration; > > - frameContext.agc.maxFrameDuration = agc.maxFrameDuration; > > } > > > > /** > > @@ -430,41 +214,13 @@ void Agc::queueRequest(IPAContext &context, > > void Agc::prepare(IPAContext &context, const uint32_t frame, > > IPAFrameContext &frameContext, RkISP1Params *params) > > { > > - uint32_t activeAutoExposure = context.activeState.agc.automatic.exposure; > > - double activeAutoGain = context.activeState.agc.automatic.gain; > > - double activeAutoQGain = context.activeState.agc.automatic.quantizationGain; > > - > > - /* Populate exposure and gain in auto mode */ > > - if (frameContext.agc.autoExposureEnabled) { > > - frameContext.agc.exposure = activeAutoExposure; > > - frameContext.agc.quantizationGain = activeAutoQGain; > > - } > > - if (frameContext.agc.autoGainEnabled) { > > - frameContext.agc.gain = activeAutoGain; > > - frameContext.agc.quantizationGain = activeAutoQGain; > > - } > > - > > - /* > > - * Populate manual exposure and gain from the active auto values when > > - * transitioning from auto to manual > > - */ > > - if (!frameContext.agc.autoExposureEnabled && frameContext.agc.autoExposureModeChange) { > > - context.activeState.agc.manual.exposure = activeAutoExposure; > > - frameContext.agc.exposure = activeAutoExposure; > > - } > > - if (!frameContext.agc.autoGainEnabled && frameContext.agc.autoGainModeChange) { > > - context.activeState.agc.manual.gain = activeAutoGain; > > - frameContext.agc.gain = activeAutoGain; > > - frameContext.agc.quantizationGain = activeAutoQGain; > > - } > > + agc_.prepare(context.activeState.agc, frameContext.agc); > > > > if (context.configuration.compress.supported) { > > frameContext.compress.enable = true; > > frameContext.compress.gain = frameContext.agc.quantizationGain; > > } > > > > - frameContext.agc.yTarget = context.activeState.agc.automatic.yTarget; > > - > > if (frame > 0 && !frameContext.agc.updateMetering) > > return; > > > > @@ -520,50 +276,6 @@ void Agc::prepare(IPAContext &context, const uint32_t frame, > > static_cast<rkisp1_cif_isp_histogram_mode>(hstConfig->mode)); > > } > > > > -void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext, > > - ControlList &metadata) > > -{ > > - utils::Duration exposureTime = context.configuration.sensor.lineDuration > > - * frameContext.sensor.exposure; > > - metadata.set(controls::AnalogueGain, frameContext.sensor.gain); > > - metadata.set(controls::ExposureTime, exposureTime.get<std::micro>()); > > - metadata.set(controls::FrameDuration, frameContext.agc.frameDuration.get<std::micro>()); > > - metadata.set(controls::ExposureTimeMode, > > - frameContext.agc.autoExposureEnabled > > - ? controls::ExposureTimeModeAuto > > - : controls::ExposureTimeModeManual); > > - metadata.set(controls::AnalogueGainMode, > > - frameContext.agc.autoGainEnabled > > - ? controls::AnalogueGainModeAuto > > - : controls::AnalogueGainModeManual); > > - > > - metadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode); > > - metadata.set(controls::AeExposureMode, frameContext.agc.exposureMode); > > - metadata.set(controls::AeConstraintMode, frameContext.agc.constraintMode); > > - metadata.set(controls::ExposureValue, frameContext.agc.exposureValue); > > -} > > - > > -/** > > - * \brief Process frame duration and compute vblank > > - * \param[in] context The shared IPA context > > - * \param[in] frameContext The current frame context > > - * \param[in] frameDuration The target frame duration > > - * > > - * Compute and populate vblank from the target frame duration. > > - */ > > -void Agc::processFrameDuration(IPAContext &context, > > - IPAFrameContext &frameContext, > > - utils::Duration frameDuration) > > -{ > > - IPACameraSensorInfo &sensorInfo = context.sensorInfo; > > - utils::Duration lineDuration = context.configuration.sensor.lineDuration; > > - > > - frameContext.agc.vblank = (frameDuration / lineDuration) - sensorInfo.outputSize.height; > > - > > - /* Update frame duration accounting for line length quantization. */ > > - frameContext.agc.frameDuration = (sensorInfo.outputSize.height + frameContext.agc.vblank) * lineDuration; > > -} > > - > > namespace { > > > > class AgcTraits final : public AgcMeanLuminance::Traits > > @@ -637,21 +349,6 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, > > IPAFrameContext &frameContext, const rkisp1_stat_buffer *stats, > > ControlList &metadata) > > { > > - if (!stats) { > > - processFrameDuration(context, frameContext, > > - frameContext.agc.minFrameDuration); > > - fillMetadata(context, frameContext, metadata); > > - return; > > - } > > - > > - if (!(stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP)) { > > - fillMetadata(context, frameContext, metadata); > > - LOG(RkISP1Agc, Error) << "AUTOEXP data is missing in statistics"; > > - return; > > - } > > - > > - const utils::Duration &lineDuration = context.configuration.sensor.lineDuration; > > - > > /* > > * \todo Verify that the exposure and gain applied by the sensor for > > * this frame match what has been requested. This isn't a hard > > @@ -660,95 +357,46 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, > > * we receive), but is important in manual mode. > > */ > > > > - const rkisp1_cif_isp_stat *params = &stats->params; > > + const rkisp1_cif_isp_stat *params = nullptr; > > > > - /* > > - * 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.agc.autoExposureEnabled) { > > - minExposureTime = context.configuration.sensor.minExposureTime; > > - maxExposureTime = std::clamp(frameContext.agc.maxFrameDuration, > > - context.configuration.sensor.minExposureTime, > > - context.configuration.sensor.maxExposureTime); > > - } else { > > - minExposureTime = context.configuration.sensor.lineDuration > > - * frameContext.agc.exposure; > > - maxExposureTime = minExposureTime; > > + if (stats) { > > + if (stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP) > > + params = &stats->params; > > + else > > + LOG(RkISP1Agc, Error) << "AUTOEXP data is missing in statistics"; > > } > > > > - if (frameContext.agc.autoGainEnabled) { > > - minAnalogueGain = context.configuration.sensor.minAnalogueGain; > > - maxAnalogueGain = context.configuration.sensor.maxAnalogueGain; > > + if (params) { > > + std::vector<AgcMeanLuminance::AgcConstraint> additionalConstraints; > > + if (context.activeState.wdr.mode != controls::WdrOff) > > + additionalConstraints.push_back(context.activeState.wdr.constraint); > > + > > + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {{ > > + .traits = AgcTraits{ > > + { params->ae.exp_mean, context.hw.numAeCells }, > > + meteringModes_.at(frameContext.agc.meteringMode), > > + }, > > + .yHist = { > > + /* The lower 4 bits are fractional and meant to be discarded. */ > > + { params->hist.hist_bins, context.hw.numHistogramBins }, > > + [](uint32_t x) { return x >> 4; }, > > + }, > > + .exposure = frameContext.sensor.exposure, > > + /* > > + * Include the quantization gain if it was applied. Do not use > > + * compress.gain because it will include gains that shall not be > > + * reported to the user when HDR is implemented. > > + */ > > + .gain = frameContext.sensor.gain > > + * (frameContext.compress.enable ? frameContext.agc.quantizationGain : 1), > > + .additionalConstraints = std::move(additionalConstraints), > > + .lux = frameContext.lux.lux, > > + }}, metadata); > > } else { > > - minAnalogueGain = frameContext.agc.gain; > > - maxAnalogueGain = frameContext.agc.gain; > > + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {}, metadata); > > } > > > > - std::vector<AgcMeanLuminance::AgcConstraint> additionalConstraints; > > - if (context.activeState.wdr.mode != controls::WdrOff) > > - additionalConstraints.push_back(context.activeState.wdr.constraint); > > - > > - agc_.setLimits(minExposureTime, maxExposureTime, minAnalogueGain, maxAnalogueGain, > > - std::move(additionalConstraints)); > > - > > - /* > > - * The Agc algorithm needs to know the effective exposure value that was > > - * applied to the sensor when the statistics were collected. > > - */ > > - utils::Duration exposureTime = lineDuration * frameContext.sensor.exposure; > > - double analogueGain = frameContext.sensor.gain; > > - utils::Duration effectiveExposureValue = exposureTime * analogueGain; > > - > > - /* > > - * Include the quantization gain if it was applied. Do not use > > - * compress.gain because it will include gains that shall not be > > - * reported to the user when HDR is implemented. > > - */ > > - if (frameContext.compress.enable) > > - effectiveExposureValue *= frameContext.agc.quantizationGain; > > - > > - /* The lower 4 bits are fractional and meant to be discarded. */ > > - Histogram hist({ params->hist.hist_bins, context.hw.numHistogramBins }, > > - [](uint32_t x) { return x >> 4; }); > > - > > - const auto &newEv = agc_.calculateNewEv({ > > - .traits = AgcTraits{ > > - { params->ae.exp_mean, context.hw.numAeCells }, > > - meteringModes_.at(frameContext.agc.meteringMode), > > - }, > > - .yHist = hist, > > - .effectiveExposureValue = effectiveExposureValue, > > - .constraintModeIndex = frameContext.agc.constraintMode, > > - .exposureModeIndex = frameContext.agc.exposureMode, > > - .lux = frameContext.lux.lux, > > - .exposureCompensation = pow(2.0, frameContext.agc.exposureValue), > > - }); > > - > > - LOG(RkISP1Agc, Debug) > > - << "Divided up exposure time, analogue gain, quantization gain" > > - << " and digital gain are " << newEv.exposureTime << ", " << newEv.analogueGain > > - << ", " << newEv.quantizationGain << " and " << newEv.digitalGain; > > - > > - IPAActiveState &activeState = context.activeState; > > - /* Update the estimated exposure and gain. */ > > - activeState.agc.automatic.exposure = newEv.exposureTime / lineDuration; > > - activeState.agc.automatic.gain = newEv.analogueGain; > > - activeState.agc.automatic.quantizationGain = newEv.quantizationGain; > > - activeState.agc.automatic.yTarget = newEv.yTarget; > > - /* > > - * 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, newEv.exposureTime)); > > - > > - fillMetadata(context, frameContext, metadata); > > + metadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode); > > } > > > > REGISTER_IPA_ALGORITHM(Agc, "Agc") > > diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h > > index 0527ca0d5f..3a4d7bc546 100644 > > --- a/src/ipa/rkisp1/algorithms/agc.h > > +++ b/src/ipa/rkisp1/algorithms/agc.h > > @@ -14,7 +14,7 @@ > > > > #include <libcamera/geometry.h> > > > > -#include "libipa/agc_mean_luminance.h" > > +#include "libipa/agc.h" > > > > #include "algorithm.h" > > > > @@ -47,14 +47,8 @@ private: > > uint8_t computeHistogramPredivider(const Size &size, > > enum rkisp1_cif_isp_histogram_mode mode); > > > > - void fillMetadata(IPAContext &context, IPAFrameContext &frameContext, > > - ControlList &metadata); > > - void processFrameDuration(IPAContext &context, > > - IPAFrameContext &frameContext, > > - utils::Duration frameDuration); > > - > > std::map<int32_t, std::vector<uint8_t>> meteringModes_; > > - AgcMeanLuminance agc_; > > + AgcAlgorithm agc_; > > }; > > > > } /* namespace ipa::rkisp1::algorithms */ > > diff --git a/src/ipa/rkisp1/algorithms/lux.cpp b/src/ipa/rkisp1/algorithms/lux.cpp > > index 86e46c492f..ce6928a55d 100644 > > --- a/src/ipa/rkisp1/algorithms/lux.cpp > > +++ b/src/ipa/rkisp1/algorithms/lux.cpp > > @@ -74,7 +74,7 @@ void Lux::process(IPAContext &context, > > if (!stats) > > return; > > > > - utils::Duration exposureTime = context.configuration.sensor.lineDuration * > > + utils::Duration exposureTime = context.configuration.agc.lineDuration * > > frameContext.sensor.exposure; > > double gain = frameContext.sensor.gain; > > > > diff --git a/src/ipa/rkisp1/ipa_context.cpp b/src/ipa/rkisp1/ipa_context.cpp > > index 1f94afda6b..47691674ad 100644 > > --- a/src/ipa/rkisp1/ipa_context.cpp > > +++ b/src/ipa/rkisp1/ipa_context.cpp > > @@ -86,21 +86,6 @@ namespace libcamera::ipa::rkisp1 { > > * \var IPASessionConfiguration::sensor > > * \brief Sensor-specific configuration of the IPA > > * > > - * \var IPASessionConfiguration::sensor.minExposureTime > > - * \brief Minimum exposure time supported with the sensor > > - * > > - * \var IPASessionConfiguration::sensor.maxExposureTime > > - * \brief Maximum exposure time supported with the sensor > > - * > > - * \var IPASessionConfiguration::sensor.minAnalogueGain > > - * \brief Minimum analogue gain supported with the sensor > > - * > > - * \var IPASessionConfiguration::sensor.maxAnalogueGain > > - * \brief Maximum analogue gain supported with the sensor > > - * > > - * \var IPASessionConfiguration::sensor.lineDuration > > - * \brief Line duration in microseconds > > - * > > * \var IPASessionConfiguration::sensor.size > > * \brief Sensor output resolution > > */ > > @@ -147,49 +132,8 @@ namespace libcamera::ipa::rkisp1 { > > * \var IPAActiveState::agc > > * \brief State for the Automatic Gain Control algorithm > > * > > - * 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. > > - * > > - * \struct IPAActiveState::agc.manual > > - * \brief Manual exposure time and analog gain (set through requests) > > - * > > - * \var IPAActiveState::agc.manual.exposure > > - * \brief Manual exposure time expressed as a number of lines as set by the > > - * ExposureTime control > > - * > > - * \var IPAActiveState::agc.manual.gain > > - * \brief Manual analogue gain as set by the AnalogueGain control > > - * > > - * \struct IPAActiveState::agc.automatic > > - * \brief Automatic exposure time and analog gain (computed by the algorithm) > > - * > > - * \var IPAActiveState::agc.automatic.exposure > > - * \brief Automatic exposure time expressed as a number of lines > > - * > > - * \var IPAActiveState::agc.automatic.gain > > - * \brief Automatic analogue gain multiplier > > - * > > - * \var IPAActiveState::agc.autoExposureEnabled > > - * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control > > - * > > - * \var IPAActiveState::agc.autoGainEnabled > > - * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control > > - * > > - * \var IPAActiveState::agc.constraintMode > > - * \brief Constraint mode as set by the AeConstraintMode control > > - * > > - * \var IPAActiveState::agc.exposureMode > > - * \brief Exposure mode as set by the AeExposureMode control > > - * > > * \var IPAActiveState::agc.meteringMode > > * \brief Metering mode as set by the AeMeteringMode control > > - * > > - * \var IPAActiveState::agc.minFrameDuration > > - * \brief Minimum frame duration as set by the FrameDurationLimits control > > - * > > - * \var IPAActiveState::agc.maxFrameDuration > > - * \brief Maximum frame duration as set by the FrameDurationLimits control > > */ > > > > /** > > @@ -314,53 +258,11 @@ namespace libcamera::ipa::rkisp1 { > > * the vertical blanking period is determined to maintain a consistent frame > > * rate matched to the FrameDurationLimits as set by the user. > > * > > - * \var IPAFrameContext::agc.exposure > > - * \brief Exposure time expressed as a number of lines computed by the algorithm > > - * > > - * \var IPAFrameContext::agc.gain > > - * \brief Analogue gain multiplier computed by the algorithm > > - * > > - * The gain should be adapted to the sensor specific gain code before applying. > > - * > > - * \var IPAFrameContext::agc.vblank > > - * \brief Vertical blanking parameter computed by the algorithm > > - * > > - * \var IPAFrameContext::agc.autoExposureEnabled > > - * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control > > - * > > - * \var IPAFrameContext::agc.autoGainEnabled > > - * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control > > - * > > - * \var IPAFrameContext::agc.constraintMode > > - * \brief Constraint mode as set by the AeConstraintMode control > > - * > > - * \var IPAFrameContext::agc.exposureMode > > - * \brief Exposure mode as set by the AeExposureMode control > > - * > > * \var IPAFrameContext::agc.meteringMode > > * \brief Metering mode as set by the AeMeteringMode control > > * > > - * \var IPAFrameContext::agc.minFrameDuration > > - * \brief Minimum frame duration as set by the FrameDurationLimits control > > - * > > - * \var IPAFrameContext::agc.maxFrameDuration > > - * \brief Maximum frame duration as set by the FrameDurationLimits control > > - * > > - * \var IPAFrameContext::agc.frameDuration > > - * \brief The actual FrameDuration used by the algorithm for the frame > > - * > > * \var IPAFrameContext::agc.updateMetering > > * \brief Indicate if new ISP AGC metering parameters need to be applied > > - * > > - * \var IPAFrameContext::agc.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 IPAFrameContext::agc.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. > > */ > > > > /** > > diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h > > index cd213dd991..cc07bb9462 100644 > > --- a/src/ipa/rkisp1/ipa_context.h > > +++ b/src/ipa/rkisp1/ipa_context.h > > @@ -24,7 +24,7 @@ > > #include "libcamera/internal/matrix.h" > > #include "libcamera/internal/vector.h" > > > > -#include "libipa/agc_mean_luminance.h" > > +#include "libipa/agc.h" > > #include "libipa/awb.h" > > #include "libipa/camera_sensor_helper.h" > > #include "libipa/ccm.h" > > @@ -57,7 +57,7 @@ struct RKISP1AwbSession { > > }; > > > > struct IPASessionConfiguration { > > - struct { > > + struct Agc : agc::Session { > > struct rkisp1_cif_isp_window measureWindow; > > } agc; > > > > @@ -68,12 +68,6 @@ struct IPASessionConfiguration { > > } compress; > > > > struct { > > - utils::Duration minExposureTime; > > - utils::Duration maxExposureTime; > > - double minAnalogueGain; > > - double maxAnalogueGain; > > - > > - utils::Duration lineDuration; > > Size size; > > } sensor; > > > > @@ -82,26 +76,8 @@ struct IPASessionConfiguration { > > }; > > > > struct IPAActiveState { > > - struct { > > - 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; > > + struct Agc : agc::ActiveState { > > controls::AeMeteringModeEnum meteringMode; > > - utils::Duration minFrameDuration; > > - utils::Duration maxFrameDuration; > > } agc; > > > > ipa::awb::ActiveState awb; > > @@ -145,24 +121,9 @@ struct IPAActiveState { > > }; > > > > struct IPAFrameContext : public FrameContext { > > - struct { > > - uint32_t exposure; > > - double gain; > > - double exposureValue; > > - double quantizationGain; > > - uint32_t vblank; > > - double yTarget; > > - bool autoExposureEnabled; > > - bool autoGainEnabled; > > - controls::AeConstraintModeEnum constraintMode; > > - controls::AeExposureModeEnum exposureMode; > > + struct Agc : agc::FrameContext { > > controls::AeMeteringModeEnum meteringMode; > > - utils::Duration minFrameDuration; > > - utils::Duration maxFrameDuration; > > - utils::Duration frameDuration; > > bool updateMetering; > > - bool autoExposureModeChange; > > - bool autoGainModeChange; > > } agc; > > > > ipa::awb::FrameContext awb; > > diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp > > index 98ec5a5748..731a362cee 100644 > > --- a/src/ipa/rkisp1/rkisp1.cpp > > +++ b/src/ipa/rkisp1/rkisp1.cpp > > @@ -6,8 +6,6 @@ > > */ > > > > #include <algorithm> > > -#include <array> > > -#include <chrono> > > #include <stdint.h> > > #include <string.h> > > > > @@ -40,8 +38,6 @@ namespace libcamera { > > > > LOG_DEFINE_CATEGORY(IPARkISP1) > > > > -using namespace std::literals::chrono_literals; > > - > > namespace ipa::rkisp1 { > > > > /* Maximum number of frame contexts to be held */ > > -- > > 2.55.0 > >
Hi Barnabás, Quoting Barnabás Pőcze (2026-08-20 11:08:00) > 2026. 08. 19. 17:31 keltezéssel, Stefan Klug írta: > > Hi Barnabás, > > > > Thank you for the patch. > > > > Quoting Barnabás Pőcze (2026-08-17 13:43:22) > >> Add a class that implements the `Algorithm` interface using `AgcMeanLuminance` > >> based on the rkisp1 `Agc` algorithm, with the following main adjustments: > >> > >> * the parameters for `process()` have been made optional to handle > >> the cases where statistics are not available; > >> * the "raw" capture check has been replaced with the "autoAllowed" > >> session parameter; > >> * the controls are only provided after `configure()`. > > > > Eek I completely missed that in v4. Thanks for pointing it out. > > I thought that this was problematic as cam showed only controls that > > were available before configure. But testing it reveals that this is not > > the case. So either I remember incorrectly or cam got fixed :-) > > Well, every user calls `configure()` in its `init()` to provide initial > controls for the camera (before configuration). Oh I missed that, Tanks for clarification. Maybe the proposal from Jacopo in his reply is a good one? > > > > > >> > >> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> > >> --- > >> src/ipa/libipa/agc.cpp | 637 +++++++++++++++++++++++++++++- > >> src/ipa/libipa/agc.h | 106 +++++ > >> src/ipa/rkisp1/algorithms/agc.cpp | 464 +++------------------- > >> src/ipa/rkisp1/algorithms/agc.h | 10 +- > >> src/ipa/rkisp1/algorithms/lux.cpp | 2 +- > >> src/ipa/rkisp1/ipa_context.cpp | 98 ----- > >> src/ipa/rkisp1/ipa_context.h | 47 +-- > >> src/ipa/rkisp1/rkisp1.cpp | 4 - > >> 8 files changed, 805 insertions(+), 563 deletions(-) > >> > >> diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp > >> index 415a6d831f..3c6b12e452 100644 > >> --- a/src/ipa/libipa/agc.cpp > >> +++ b/src/ipa/libipa/agc.cpp > >> @@ -1,16 +1,32 @@ > >> /* SPDX-License-Identifier: LGPL-2.1-or-later */ > >> /* > >> - * Copyright (C) 2026 Ideas On Board > >> + * Copyright (C) 2021-2026 Ideas On Board > >> * > >> * Auto exposure/gain algorithm for implementing the IPA-specific AGC algorithms > > > > Nit: line break > > How do you mean? > Sorry, I miscounted in the email. The line looked so long but is actually exactly 80 chars. > > > > >> */ > >> > >> #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) > >> + > >> namespace agc { > >> > >> /** > >> @@ -40,6 +56,625 @@ namespace agc { > >> > >> } /* namespace 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 > >> + */ > >> + > > > > Moving these variables into agc::FrameContext and agx::ActiveState in a separate > > preparatory patch might have reduced the size of this patch by quite a > > bit. I don't want to send a new Yak, so I won't dwell on it :-) > > I thought about it, but now I'm not sure why I didn't do it. Maybe I'll try again. > > > > > >> +/** > >> + * \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 > >> + * > >> + * \note The IPA algorithm implementation will most likely need to call > >> + * this in its Algorithm::init() implementation in order to provide > >> + * the initial controls for the camera. > >> + */ > >> +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; > >> + > >> + /* \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] } }, > >> + }; > >> + config.ctrlMap[&controls::ExposureTimeMode] = ControlInfo{ > >> + {{ controls::ExposureTimeModeAuto, controls::ExposureTimeModeManual }}, > >> + controls::ExposureTimeModeAuto, > >> + }; > >> + config.ctrlMap[&controls::AnalogueGainMode] = ControlInfo{ > >> + {{ controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual }}, > >> + controls::AnalogueGainModeAuto, > >> + }; > >> + config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); > >> + config.ctrlMap.merge(impl_.controls()); > >> + > >> + 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) > >> +{ > >> + if (!params) { > >> + processFrameDuration(session, frameContext, frameContext.minFrameDuration); > >> + fillMetadata(session, frameContext, metadata); > >> + return; > >> + } > >> + > >> + ASSERT(session.autoAllowed); > >> + > >> + const utils::Duration &lineDuration = session.lineDuration; > >> + > >> + /* > >> + * 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; > >> + > >> + LOG(Agc, Debug) > >> + << "Divided up exposure time, analogue gain, quantization gain" > >> + << " and digital gain are " << newEv.exposureTime > >> + << ", " << 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. > >> + */ > >> + processFrameDuration(session, frameContext, > >> + std::max(frameContext.minFrameDuration, newEv.exposureTime)); > >> + > >> + fillMetadata(session, frameContext, metadata); > >> +} > >> + > >> +/** > >> + * \brief Process frame duration and compute vblank > >> + * \param[in] session The session parameters > >> + * \param[in] frameContext The current frame context > >> + * \param[in] frameDuration The target frame duration > >> + * > >> + * Compute and populate vblank from the target frame duration. > >> + */ > >> +void AgcAlgorithm::processFrameDuration(const agc::Session &session, > >> + agc::FrameContext &frameContext, > >> + utils::Duration frameDuration) > >> +{ > >> + const utils::Duration &lineDuration = session.lineDuration; > >> + > >> + 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; > >> +} > >> + > >> +void AgcAlgorithm::fillMetadata(const agc::Session &session, > > > > Does this one need documentation as it lives in libipa now? > > I don't know. It's a private function, implementation detail, > and fairly straightforward in my opinion. Oh I expected the doxygen to nag it. But as it's private it won't. Then it is fine with me. > > > > > >> + const agc::FrameContext &frameContext, > >> + ControlList &metadata) > >> +{ > >> + > >> + metadata.set(controls::AnalogueGain, frameContext.gain); > >> + metadata.set(controls::ExposureTime, > >> + utils::Duration(session.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/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h > >> index cd213dd991..cc07bb9462 100644 > >> --- a/src/ipa/rkisp1/ipa_context.h > >> +++ b/src/ipa/rkisp1/ipa_context.h > >> @@ -24,7 +24,7 @@ > >> #include "libcamera/internal/matrix.h" > >> #include "libcamera/internal/vector.h" > >> > >> -#include "libipa/agc_mean_luminance.h" > >> +#include "libipa/agc.h" > >> #include "libipa/awb.h" > >> #include "libipa/camera_sensor_helper.h" > >> #include "libipa/ccm.h" > >> @@ -57,7 +57,7 @@ struct RKISP1AwbSession { > >> }; > >> > >> struct IPASessionConfiguration { > >> - struct { > >> + struct Agc : agc::Session { > >> struct rkisp1_cif_isp_window measureWindow; > >> } agc; > >> > >> @@ -68,12 +68,6 @@ struct IPASessionConfiguration { > >> } compress; > >> > >> struct { > >> - utils::Duration minExposureTime; > >> - utils::Duration maxExposureTime; > >> - double minAnalogueGain; > >> - double maxAnalogueGain; > >> - > >> - utils::Duration lineDuration; > >> Size size; > >> } sensor; > >> > >> @@ -82,26 +76,8 @@ struct IPASessionConfiguration { > >> }; > >> > >> struct IPAActiveState { > >> - struct { > >> - 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; > >> + struct Agc : agc::ActiveState { > >> controls::AeMeteringModeEnum meteringMode; > >> - utils::Duration minFrameDuration; > >> - utils::Duration maxFrameDuration; > >> } agc; > >> > >> ipa::awb::ActiveState awb; > >> @@ -145,24 +121,9 @@ struct IPAActiveState { > >> }; > >> > >> struct IPAFrameContext : public FrameContext { > >> - struct { > >> - uint32_t exposure; > >> - double gain; > >> - double exposureValue; > >> - double quantizationGain; > >> - uint32_t vblank; > >> - double yTarget; > >> - bool autoExposureEnabled; > >> - bool autoGainEnabled; > >> - controls::AeConstraintModeEnum constraintMode; > >> - controls::AeExposureModeEnum exposureMode; > >> + struct Agc : agc::FrameContext { > >> controls::AeMeteringModeEnum meteringMode; > >> - utils::Duration minFrameDuration; > >> - utils::Duration maxFrameDuration; > >> - utils::Duration frameDuration; > >> bool updateMetering; > >> - bool autoExposureModeChange; > >> - bool autoGainModeChange; > >> } agc; > >> > >> ipa::awb::FrameContext awb; > >> diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp > >> index 98ec5a5748..731a362cee 100644 > >> --- a/src/ipa/rkisp1/rkisp1.cpp > >> +++ b/src/ipa/rkisp1/rkisp1.cpp > >> @@ -6,8 +6,6 @@ > >> */ > >> > >> #include <algorithm> > >> -#include <array> > >> -#include <chrono> > > > > These changes look unrelated? > > These are now unused because the code has been moved. > But then these were wrong already before this patch as there were no other changes in this file? But no worries, cleanup is always good. Best regards, Stefan > > > > >> #include <stdint.h> > >> #include <string.h> > >> > >> @@ -40,8 +38,6 @@ namespace libcamera { > >> > >> LOG_DEFINE_CATEGORY(IPARkISP1) > >> > >> -using namespace std::literals::chrono_literals; > >> - > >> namespace ipa::rkisp1 { > >> > >> /* Maximum number of frame contexts to be held */ > >> -- > >> 2.55.0 > >> > > > > This is a massive beast. Thanks for squashing it. I like that it is now > > possible to compare new code and old code in one patch. I didn't do > > another detailed review, but that already happened on v4. > > > > So > > > > Reviewed-by: Stefan Klug <stefan.klug@ideasonboard.com> > > Tested-by: Stefan Klug <stefan.klug@ideasonboard.com> > > > > Best regards, > > Stefan >
2026. 08. 20. 12:10 keltezéssel, Stefan Klug írta: > Hi, > > Quoting Jacopo Mondi (2026-08-17 17:06:39) >> Hi Barnabás >> >> On Mon, Aug 17, 2026 at 01:43:22PM +0200, Barnabás Pőcze wrote: >>> Add a class that implements the `Algorithm` interface using `AgcMeanLuminance` >>> based on the rkisp1 `Agc` algorithm, with the following main adjustments: >>> >>> * the parameters for `process()` have been made optional to handle >>> the cases where statistics are not available; >>> * the "raw" capture check has been replaced with the "autoAllowed" >>> session parameter; >>> * the controls are only provided after `configure()`. >>> >>> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> >>> --- >>> src/ipa/libipa/agc.cpp | 637 +++++++++++++++++++++++++++++- >>> src/ipa/libipa/agc.h | 106 +++++ >>> src/ipa/rkisp1/algorithms/agc.cpp | 464 +++------------------- >>> src/ipa/rkisp1/algorithms/agc.h | 10 +- >>> src/ipa/rkisp1/algorithms/lux.cpp | 2 +- >>> src/ipa/rkisp1/ipa_context.cpp | 98 ----- >>> src/ipa/rkisp1/ipa_context.h | 47 +-- >>> src/ipa/rkisp1/rkisp1.cpp | 4 - >>> 8 files changed, 805 insertions(+), 563 deletions(-) >>> >>> diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp >>> index 415a6d831f..3c6b12e452 100644 >>> --- a/src/ipa/libipa/agc.cpp >>> +++ b/src/ipa/libipa/agc.cpp >>> @@ -1,16 +1,32 @@ >>> /* SPDX-License-Identifier: LGPL-2.1-or-later */ >>> /* >>> - * Copyright (C) 2026 Ideas On Board >>> + * Copyright (C) 2021-2026 Ideas On Board >>> * >>> * Auto exposure/gain algorithm for implementing the IPA-specific AGC algorithms >>> */ >>> >>> #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) >>> + >>> namespace agc { >>> >>> /** >>> @@ -40,6 +56,625 @@ namespace agc { >>> >>> } /* namespace 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 >>> + * >>> + * \note The IPA algorithm implementation will most likely need to call >>> + * this in its Algorithm::init() implementation in order to provide >>> + * the initial controls for the camera. >>> + */ >>> +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; >>> + >>> + /* \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] } }, >>> + }; >>> + config.ctrlMap[&controls::ExposureTimeMode] = ControlInfo{ >>> + {{ controls::ExposureTimeModeAuto, controls::ExposureTimeModeManual }}, >>> + controls::ExposureTimeModeAuto, >>> + }; >>> + config.ctrlMap[&controls::AnalogueGainMode] = ControlInfo{ >>> + {{ controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual }}, >>> + controls::AnalogueGainModeAuto, >>> + }; >>> + config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); >>> + config.ctrlMap.merge(impl_.controls()); >>> + >>> + 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) >>> +{ >>> + if (!params) { >>> + processFrameDuration(session, frameContext, frameContext.minFrameDuration); >>> + fillMetadata(session, frameContext, metadata); >>> + return; >>> + } >>> + >>> + ASSERT(session.autoAllowed); >>> + >>> + const utils::Duration &lineDuration = session.lineDuration; >>> + >>> + /* >>> + * 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; >>> + >>> + LOG(Agc, Debug) >>> + << "Divided up exposure time, analogue gain, quantization gain" >>> + << " and digital gain are " << newEv.exposureTime >>> + << ", " << 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. >>> + */ >>> + processFrameDuration(session, frameContext, >>> + std::max(frameContext.minFrameDuration, newEv.exposureTime)); >>> + >>> + fillMetadata(session, frameContext, metadata); >>> +} >>> + >>> +/** >>> + * \brief Process frame duration and compute vblank >>> + * \param[in] session The session parameters >>> + * \param[in] frameContext The current frame context >>> + * \param[in] frameDuration The target frame duration >>> + * >>> + * Compute and populate vblank from the target frame duration. >>> + */ >>> +void AgcAlgorithm::processFrameDuration(const agc::Session &session, >>> + agc::FrameContext &frameContext, >>> + utils::Duration frameDuration) >>> +{ >>> + const utils::Duration &lineDuration = session.lineDuration; >>> + >>> + 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; >>> +} >>> + >>> +void AgcAlgorithm::fillMetadata(const agc::Session &session, >>> + const agc::FrameContext &frameContext, >>> + ControlList &metadata) >>> +{ >>> + >>> + metadata.set(controls::AnalogueGain, frameContext.gain); >>> + metadata.set(controls::ExposureTime, >>> + utils::Duration(session.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 4789c06ef8..66aa0eacb0 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 { >>> >>> @@ -21,6 +27,61 @@ namespace ipa { >>> >>> namespace agc { >>> >>> +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; >>> +}; >>> + >>> [[nodiscard]] >>> inline std::pair<uint32_t, double> >>> extractControls(const ControlList &controls, const CameraSensorHelper *sensor) >>> @@ -47,6 +108,51 @@ prepareControls(ControlList &controls, const CameraSensorHelper *sensor, >>> >>> } /* namespace agc */ >>> >>> +class AgcAlgorithm >>> +{ >>> +public: >>> + struct ConfigurationParams { >>> + const CameraSensorHelper *sensor; >>> + const IPACameraSensorInfo &sensorInfo; >>> + const ControlInfoMap &sensorControls; >>> + ControlInfoMap::Map &ctrlMap; >>> + bool autoAllowed = true; >>> + }; >>> + >>> + struct ProcessParams { >>> + const AgcMeanLuminance::Traits &traits; >>> + const Histogram &yHist; >>> + uint32_t exposure; >>> + double gain; >>> + std::vector<AgcMeanLuminance::AgcConstraint> &&additionalConstraints = {}; >>> + double lux = 0; >>> + }; >>> + >>> + 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); >>> + >>> + void process(const agc::Session &session, agc::ActiveState &state, >>> + agc::FrameContext &frameContext, std::optional<ProcessParams> &¶ms, >>> + ControlList &metadata); >>> + >>> +private: >>> + void processFrameDuration(const agc::Session &session, >>> + agc::FrameContext &frameContext, >>> + utils::Duration frameDuration); >>> + void fillMetadata(const agc::Session &session, >>> + const agc::FrameContext &frameContext, >>> + ControlList &metadata); >>> + >>> + AgcMeanLuminance impl_; >>> +}; >>> + >>> } /* namespace ipa */ >>> >>> } /* namespace libcamera */ >>> diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp >>> index fc228452c3..4c2a066e86 100644 >>> --- a/src/ipa/rkisp1/algorithms/agc.cpp >>> +++ b/src/ipa/rkisp1/algorithms/agc.cpp >>> @@ -8,9 +8,7 @@ >>> #include "agc.h" >>> >>> #include <algorithm> >>> -#include <chrono> >>> #include <cmath> >>> -#include <tuple> >>> #include <vector> >>> >>> #include <libcamera/base/log.h> >>> @@ -35,89 +33,6 @@ namespace ipa::rkisp1::algorithms { >>> >>> LOG_DEFINE_CATEGORY(RkISP1Agc) >>> >>> -namespace { >>> - >>> -void reconfigure(IPAContext &context) >>> -{ >>> - context.configuration.sensor.lineDuration = >>> - context.sensorInfo.minLineLength * 1.0s / context.sensorInfo.pixelRate; >>> - >>> - double lineDurationUs = context.configuration.sensor.lineDuration.get<std::micro>(); >>> - >>> - /* >>> - * Compute exposure time limits from the V4L2_CID_EXPOSURE control >>> - * limits and the line duration. >>> - */ >>> - >>> - const ControlInfo &v4l2Exposure = context.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>(); >>> - context.ctrlMap[&controls::ExposureTime] = ControlInfo{ >>> - static_cast<int32_t>(minExposure * lineDurationUs), >>> - static_cast<int32_t>(maxExposure * lineDurationUs), >>> - static_cast<int32_t>(defExposure * lineDurationUs), >>> - }; >>> - >>> - /* Compute the analogue gain limits. */ >>> - const ControlInfo &v4l2Gain = context.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; >>> - float minGain = context.camHelper->gain(v4l2Gain.min().get<int32_t>()); >>> - float maxGain = context.camHelper->gain(v4l2Gain.max().get<int32_t>()); >>> - float defGain = context.camHelper->gain(v4l2Gain.def().get<int32_t>()); >>> - context.ctrlMap[&controls::AnalogueGain] = ControlInfo{ >>> - minGain, >>> - maxGain, >>> - defGain, >>> - }; >>> - >>> - LOG(RkISP1Agc, 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 = context.sensorControls.find(V4L2_CID_HBLANK)->second; >>> - uint32_t hblank = v4l2HBlank.def().get<int32_t>(); >>> - uint32_t lineLength = context.sensorInfo.outputSize.width + hblank; >>> - >>> - const ControlInfo &v4l2VBlank = context.sensorControls.find(V4L2_CID_VBLANK)->second; >>> - std::array<uint32_t, 3> frameHeights{ >>> - v4l2VBlank.min().get<int32_t>() + context.sensorInfo.outputSize.height, >>> - v4l2VBlank.max().get<int32_t>() + context.sensorInfo.outputSize.height, >>> - v4l2VBlank.def().get<int32_t>() + context.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 / (context.sensorInfo.pixelRate / 1000000U); >>> - } >>> - >>> - context.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ >>> - frameDurations[0], >>> - frameDurations[1], >>> - Span<const int64_t, 2>{ { frameDurations[2], frameDurations[2] } }, >>> - }; >>> - >>> - /* >>> - * 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 >>> - */ >>> - context.configuration.sensor.minExposureTime = minExposure * context.configuration.sensor.lineDuration; >>> - context.configuration.sensor.maxExposureTime = maxExposure * context.configuration.sensor.lineDuration; >>> - context.configuration.sensor.minAnalogueGain = minGain; >>> - context.configuration.sensor.maxAnalogueGain = maxGain; >>> -} >>> - >>> -} /* namespace */ >>> - >>> /** >>> * \class Agc >>> * \brief A mean-based auto-exposure algorithm >>> @@ -221,7 +136,16 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) >>> { >>> int ret; >>> >>> - ret = agc_.parseTuningData(tuningData); >>> + ret = agc_.init(tuningData); >>> + if (ret) >>> + return ret; >>> + >>> + ret = agc_.configure(context.configuration.agc, context.activeState.agc, { >>> + .sensor = context.camHelper.get(), >>> + .sensorInfo = context.sensorInfo, >>> + .sensorControls = context.sensorControls, >>> + .ctrlMap = context.ctrlMap, >>> + }); >> >> The only comment, which might eventually be addressed as an on-top >> change, is about the requirement to call AgcAlgorithm::init() and >> configure() in the IPA init function. >> >> What if the paramters required for configure() are passed to >> AgcAlgorithm::init() and this function calls AgcAlgorithm::configure() >> internally ? > > Now I stumbled over that part in my review also. I think I like that > idea. Having the outer algorithm call the equally named function only > (init() calls init() and configure() calls configure()) seems to be a > good idea. Okay, let's do that. Although I would rather modify each algorithm to manage the controls exclusively in the `configure()` phase. > > Best regards, > Stefan > >> >> Apart from this: >> Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> >> >> Thanks >> j >> >>> if (ret) >>> return ret; >>> >>> @@ -230,21 +154,6 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) >>> if (ret) >>> return ret; >>> >>> - context.ctrlMap[&controls::ExposureTimeMode] = >>> - ControlInfo({ { ControlValue(controls::ExposureTimeModeAuto), >>> - ControlValue(controls::ExposureTimeModeManual) } }, >>> - ControlValue(controls::ExposureTimeModeAuto)); >>> - context.ctrlMap[&controls::AnalogueGainMode] = >>> - ControlInfo({ { ControlValue(controls::AnalogueGainModeAuto), >>> - ControlValue(controls::AnalogueGainModeManual) } }, >>> - ControlValue(controls::AnalogueGainModeAuto)); >>> - /* \todo Move this to the Camera class */ >>> - context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true, true); >>> - context.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); >>> - context.ctrlMap.merge(agc_.controls()); >>> - >>> - reconfigure(context); >>> - >>> return 0; >>> } >>> >>> @@ -257,47 +166,24 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) >>> */ >>> int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) >>> { >>> - reconfigure(context); >>> - >>> - /* Configure the default exposure and gain. */ >>> - context.activeState.agc.automatic.gain = context.configuration.sensor.minAnalogueGain; >>> - context.activeState.agc.automatic.exposure = >>> - 10ms / context.configuration.sensor.lineDuration; >>> - context.activeState.agc.automatic.quantizationGain = 1.0; >>> - context.activeState.agc.manual.gain = context.activeState.agc.automatic.gain; >>> - context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure; >>> - context.activeState.agc.autoExposureEnabled = !context.configuration.raw; >>> - context.activeState.agc.autoGainEnabled = !context.configuration.raw; >>> - context.activeState.agc.exposureValue = 0.0; >>> - >>> - context.activeState.agc.constraintMode = >>> - static_cast<controls::AeConstraintModeEnum>(agc_.constraintModes().begin()->first); >>> - context.activeState.agc.exposureMode = >>> - static_cast<controls::AeExposureModeEnum>(agc_.exposureModeHelpers().begin()->first); >>> + int ret = agc_.configure(context.configuration.agc, context.activeState.agc, { >>> + .sensor = context.camHelper.get(), >>> + .sensorInfo = context.sensorInfo, >>> + .sensorControls = context.sensorControls, >>> + .ctrlMap = context.ctrlMap, >>> + .autoAllowed = !context.configuration.raw, >>> + }); >>> + if (ret) >>> + return ret; >>> + >>> context.activeState.agc.meteringMode = >>> static_cast<controls::AeMeteringModeEnum>(meteringModes_.begin()->first); >>> >>> - /* Limit the frame duration to match current initialisation */ >>> - ControlInfo &frameDurationLimits = context.ctrlMap[&controls::FrameDurationLimits]; >>> - context.activeState.agc.minFrameDuration = std::chrono::microseconds(frameDurationLimits.min().get<int64_t>()); >>> - context.activeState.agc.maxFrameDuration = std::chrono::microseconds(frameDurationLimits.max().get<int64_t>()); >>> - >>> context.configuration.agc.measureWindow.h_offs = 0; >>> context.configuration.agc.measureWindow.v_offs = 0; >>> context.configuration.agc.measureWindow.h_size = configInfo.outputSize.width; >>> context.configuration.agc.measureWindow.v_size = configInfo.outputSize.height; >>> >>> - agc_.configure(context.configuration.sensor.lineDuration, context.camHelper.get()); >>> - >>> - agc_.setLimits(context.configuration.sensor.minExposureTime, >>> - context.configuration.sensor.maxExposureTime, >>> - context.configuration.sensor.minAnalogueGain, >>> - context.configuration.sensor.maxAnalogueGain, {}); >>> - >>> - context.activeState.agc.automatic.yTarget = agc_.effectiveYTarget(0, 1); >>> - >>> - agc_.resetFrameCount(); >>> - >>> return 0; >>> } >>> >>> @@ -311,73 +197,7 @@ void Agc::queueRequest(IPAContext &context, >>> { >>> auto &agc = context.activeState.agc; >>> >>> - if (!context.configuration.raw) { >>> - const auto &aeEnable = controls.get(controls::ExposureTimeMode); >>> - if (aeEnable && >>> - (*aeEnable == controls::ExposureTimeModeAuto) != agc.autoExposureEnabled) { >>> - agc.autoExposureEnabled = (*aeEnable == controls::ExposureTimeModeAuto); >>> - >>> - LOG(RkISP1Agc, Debug) >>> - << (agc.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 (!agc.autoExposureEnabled && !controls.get(controls::ExposureTime)) >>> - frameContext.agc.autoExposureModeChange = true; >>> - } >>> - >>> - const auto &agEnable = controls.get(controls::AnalogueGainMode); >>> - if (agEnable && >>> - (*agEnable == controls::AnalogueGainModeAuto) != agc.autoGainEnabled) { >>> - agc.autoGainEnabled = (*agEnable == controls::AnalogueGainModeAuto); >>> - >>> - LOG(RkISP1Agc, Debug) >>> - << (agc.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 (!agc.autoGainEnabled && !controls.get(controls::AnalogueGain)) >>> - frameContext.agc.autoGainModeChange = true; >>> - } >>> - } >>> - >>> - const auto &exposure = controls.get(controls::ExposureTime); >>> - if (exposure && !agc.autoExposureEnabled) { >>> - agc.manual.exposure = *exposure * 1.0us >>> - / context.configuration.sensor.lineDuration; >>> - >>> - LOG(RkISP1Agc, Debug) >>> - << "Set exposure to " << agc.manual.exposure; >>> - } >>> - >>> - const auto &gain = controls.get(controls::AnalogueGain); >>> - if (gain && !agc.autoGainEnabled) { >>> - agc.manual.gain = *gain; >>> - >>> - LOG(RkISP1Agc, Debug) << "Set gain to " << agc.manual.gain; >>> - } >>> - >>> - frameContext.agc.autoExposureEnabled = agc.autoExposureEnabled; >>> - frameContext.agc.autoGainEnabled = agc.autoGainEnabled; >>> - >>> - if (!frameContext.agc.autoExposureEnabled) >>> - frameContext.agc.exposure = agc.manual.exposure; >>> - if (!frameContext.agc.autoGainEnabled) >>> - frameContext.agc.gain = agc.manual.gain; >>> - >>> - if (!frameContext.agc.autoExposureEnabled && >>> - !frameContext.agc.autoGainEnabled) >>> - frameContext.agc.quantizationGain = 1.0; >>> + agc_.queueRequest(context.configuration.agc, agc, frameContext.agc, controls); >>> >>> const auto &meteringMode = controls.get(controls::AeMeteringMode); >>> if (meteringMode) { >>> @@ -386,42 +206,6 @@ void Agc::queueRequest(IPAContext &context, >>> static_cast<controls::AeMeteringModeEnum>(*meteringMode); >>> } >>> frameContext.agc.meteringMode = agc.meteringMode; >>> - >>> - const auto &exposureMode = controls.get(controls::AeExposureMode); >>> - if (exposureMode) >>> - agc.exposureMode = >>> - static_cast<controls::AeExposureModeEnum>(*exposureMode); >>> - frameContext.agc.exposureMode = agc.exposureMode; >>> - >>> - const auto &constraintMode = controls.get(controls::AeConstraintMode); >>> - if (constraintMode) >>> - agc.constraintMode = >>> - static_cast<controls::AeConstraintModeEnum>(*constraintMode); >>> - frameContext.agc.constraintMode = agc.constraintMode; >>> - >>> - const auto &exposureValue = controls.get(controls::ExposureValue); >>> - if (exposureValue) >>> - agc.exposureValue = *exposureValue; >>> - frameContext.agc.exposureValue = agc.exposureValue; >>> - >>> - const auto &frameDurationLimits = controls.get(controls::FrameDurationLimits); >>> - if (frameDurationLimits) { >>> - /* Limit the control value to the limits in ControlInfo */ >>> - ControlInfo &limits = context.ctrlMap[&controls::FrameDurationLimits]; >>> - int64_t minFrameDuration = >>> - std::clamp((*frameDurationLimits).front(), >>> - limits.min().get<int64_t>(), >>> - limits.max().get<int64_t>()); >>> - int64_t maxFrameDuration = >>> - std::clamp((*frameDurationLimits).back(), >>> - limits.min().get<int64_t>(), >>> - limits.max().get<int64_t>()); >>> - >>> - agc.minFrameDuration = std::chrono::microseconds(minFrameDuration); >>> - agc.maxFrameDuration = std::chrono::microseconds(maxFrameDuration); >>> - } >>> - frameContext.agc.minFrameDuration = agc.minFrameDuration; >>> - frameContext.agc.maxFrameDuration = agc.maxFrameDuration; >>> } >>> >>> /** >>> @@ -430,41 +214,13 @@ void Agc::queueRequest(IPAContext &context, >>> void Agc::prepare(IPAContext &context, const uint32_t frame, >>> IPAFrameContext &frameContext, RkISP1Params *params) >>> { >>> - uint32_t activeAutoExposure = context.activeState.agc.automatic.exposure; >>> - double activeAutoGain = context.activeState.agc.automatic.gain; >>> - double activeAutoQGain = context.activeState.agc.automatic.quantizationGain; >>> - >>> - /* Populate exposure and gain in auto mode */ >>> - if (frameContext.agc.autoExposureEnabled) { >>> - frameContext.agc.exposure = activeAutoExposure; >>> - frameContext.agc.quantizationGain = activeAutoQGain; >>> - } >>> - if (frameContext.agc.autoGainEnabled) { >>> - frameContext.agc.gain = activeAutoGain; >>> - frameContext.agc.quantizationGain = activeAutoQGain; >>> - } >>> - >>> - /* >>> - * Populate manual exposure and gain from the active auto values when >>> - * transitioning from auto to manual >>> - */ >>> - if (!frameContext.agc.autoExposureEnabled && frameContext.agc.autoExposureModeChange) { >>> - context.activeState.agc.manual.exposure = activeAutoExposure; >>> - frameContext.agc.exposure = activeAutoExposure; >>> - } >>> - if (!frameContext.agc.autoGainEnabled && frameContext.agc.autoGainModeChange) { >>> - context.activeState.agc.manual.gain = activeAutoGain; >>> - frameContext.agc.gain = activeAutoGain; >>> - frameContext.agc.quantizationGain = activeAutoQGain; >>> - } >>> + agc_.prepare(context.activeState.agc, frameContext.agc); >>> >>> if (context.configuration.compress.supported) { >>> frameContext.compress.enable = true; >>> frameContext.compress.gain = frameContext.agc.quantizationGain; >>> } >>> >>> - frameContext.agc.yTarget = context.activeState.agc.automatic.yTarget; >>> - >>> if (frame > 0 && !frameContext.agc.updateMetering) >>> return; >>> >>> @@ -520,50 +276,6 @@ void Agc::prepare(IPAContext &context, const uint32_t frame, >>> static_cast<rkisp1_cif_isp_histogram_mode>(hstConfig->mode)); >>> } >>> >>> -void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext, >>> - ControlList &metadata) >>> -{ >>> - utils::Duration exposureTime = context.configuration.sensor.lineDuration >>> - * frameContext.sensor.exposure; >>> - metadata.set(controls::AnalogueGain, frameContext.sensor.gain); >>> - metadata.set(controls::ExposureTime, exposureTime.get<std::micro>()); >>> - metadata.set(controls::FrameDuration, frameContext.agc.frameDuration.get<std::micro>()); >>> - metadata.set(controls::ExposureTimeMode, >>> - frameContext.agc.autoExposureEnabled >>> - ? controls::ExposureTimeModeAuto >>> - : controls::ExposureTimeModeManual); >>> - metadata.set(controls::AnalogueGainMode, >>> - frameContext.agc.autoGainEnabled >>> - ? controls::AnalogueGainModeAuto >>> - : controls::AnalogueGainModeManual); >>> - >>> - metadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode); >>> - metadata.set(controls::AeExposureMode, frameContext.agc.exposureMode); >>> - metadata.set(controls::AeConstraintMode, frameContext.agc.constraintMode); >>> - metadata.set(controls::ExposureValue, frameContext.agc.exposureValue); >>> -} >>> - >>> -/** >>> - * \brief Process frame duration and compute vblank >>> - * \param[in] context The shared IPA context >>> - * \param[in] frameContext The current frame context >>> - * \param[in] frameDuration The target frame duration >>> - * >>> - * Compute and populate vblank from the target frame duration. >>> - */ >>> -void Agc::processFrameDuration(IPAContext &context, >>> - IPAFrameContext &frameContext, >>> - utils::Duration frameDuration) >>> -{ >>> - IPACameraSensorInfo &sensorInfo = context.sensorInfo; >>> - utils::Duration lineDuration = context.configuration.sensor.lineDuration; >>> - >>> - frameContext.agc.vblank = (frameDuration / lineDuration) - sensorInfo.outputSize.height; >>> - >>> - /* Update frame duration accounting for line length quantization. */ >>> - frameContext.agc.frameDuration = (sensorInfo.outputSize.height + frameContext.agc.vblank) * lineDuration; >>> -} >>> - >>> namespace { >>> >>> class AgcTraits final : public AgcMeanLuminance::Traits >>> @@ -637,21 +349,6 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, >>> IPAFrameContext &frameContext, const rkisp1_stat_buffer *stats, >>> ControlList &metadata) >>> { >>> - if (!stats) { >>> - processFrameDuration(context, frameContext, >>> - frameContext.agc.minFrameDuration); >>> - fillMetadata(context, frameContext, metadata); >>> - return; >>> - } >>> - >>> - if (!(stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP)) { >>> - fillMetadata(context, frameContext, metadata); >>> - LOG(RkISP1Agc, Error) << "AUTOEXP data is missing in statistics"; >>> - return; >>> - } >>> - >>> - const utils::Duration &lineDuration = context.configuration.sensor.lineDuration; >>> - >>> /* >>> * \todo Verify that the exposure and gain applied by the sensor for >>> * this frame match what has been requested. This isn't a hard >>> @@ -660,95 +357,46 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, >>> * we receive), but is important in manual mode. >>> */ >>> >>> - const rkisp1_cif_isp_stat *params = &stats->params; >>> + const rkisp1_cif_isp_stat *params = nullptr; >>> >>> - /* >>> - * 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.agc.autoExposureEnabled) { >>> - minExposureTime = context.configuration.sensor.minExposureTime; >>> - maxExposureTime = std::clamp(frameContext.agc.maxFrameDuration, >>> - context.configuration.sensor.minExposureTime, >>> - context.configuration.sensor.maxExposureTime); >>> - } else { >>> - minExposureTime = context.configuration.sensor.lineDuration >>> - * frameContext.agc.exposure; >>> - maxExposureTime = minExposureTime; >>> + if (stats) { >>> + if (stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP) >>> + params = &stats->params; >>> + else >>> + LOG(RkISP1Agc, Error) << "AUTOEXP data is missing in statistics"; >>> } >>> >>> - if (frameContext.agc.autoGainEnabled) { >>> - minAnalogueGain = context.configuration.sensor.minAnalogueGain; >>> - maxAnalogueGain = context.configuration.sensor.maxAnalogueGain; >>> + if (params) { >>> + std::vector<AgcMeanLuminance::AgcConstraint> additionalConstraints; >>> + if (context.activeState.wdr.mode != controls::WdrOff) >>> + additionalConstraints.push_back(context.activeState.wdr.constraint); >>> + >>> + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {{ >>> + .traits = AgcTraits{ >>> + { params->ae.exp_mean, context.hw.numAeCells }, >>> + meteringModes_.at(frameContext.agc.meteringMode), >>> + }, >>> + .yHist = { >>> + /* The lower 4 bits are fractional and meant to be discarded. */ >>> + { params->hist.hist_bins, context.hw.numHistogramBins }, >>> + [](uint32_t x) { return x >> 4; }, >>> + }, >>> + .exposure = frameContext.sensor.exposure, >>> + /* >>> + * Include the quantization gain if it was applied. Do not use >>> + * compress.gain because it will include gains that shall not be >>> + * reported to the user when HDR is implemented. >>> + */ >>> + .gain = frameContext.sensor.gain >>> + * (frameContext.compress.enable ? frameContext.agc.quantizationGain : 1), >>> + .additionalConstraints = std::move(additionalConstraints), >>> + .lux = frameContext.lux.lux, >>> + }}, metadata); >>> } else { >>> - minAnalogueGain = frameContext.agc.gain; >>> - maxAnalogueGain = frameContext.agc.gain; >>> + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {}, metadata); >>> } >>> >>> - std::vector<AgcMeanLuminance::AgcConstraint> additionalConstraints; >>> - if (context.activeState.wdr.mode != controls::WdrOff) >>> - additionalConstraints.push_back(context.activeState.wdr.constraint); >>> - >>> - agc_.setLimits(minExposureTime, maxExposureTime, minAnalogueGain, maxAnalogueGain, >>> - std::move(additionalConstraints)); >>> - >>> - /* >>> - * The Agc algorithm needs to know the effective exposure value that was >>> - * applied to the sensor when the statistics were collected. >>> - */ >>> - utils::Duration exposureTime = lineDuration * frameContext.sensor.exposure; >>> - double analogueGain = frameContext.sensor.gain; >>> - utils::Duration effectiveExposureValue = exposureTime * analogueGain; >>> - >>> - /* >>> - * Include the quantization gain if it was applied. Do not use >>> - * compress.gain because it will include gains that shall not be >>> - * reported to the user when HDR is implemented. >>> - */ >>> - if (frameContext.compress.enable) >>> - effectiveExposureValue *= frameContext.agc.quantizationGain; >>> - >>> - /* The lower 4 bits are fractional and meant to be discarded. */ >>> - Histogram hist({ params->hist.hist_bins, context.hw.numHistogramBins }, >>> - [](uint32_t x) { return x >> 4; }); >>> - >>> - const auto &newEv = agc_.calculateNewEv({ >>> - .traits = AgcTraits{ >>> - { params->ae.exp_mean, context.hw.numAeCells }, >>> - meteringModes_.at(frameContext.agc.meteringMode), >>> - }, >>> - .yHist = hist, >>> - .effectiveExposureValue = effectiveExposureValue, >>> - .constraintModeIndex = frameContext.agc.constraintMode, >>> - .exposureModeIndex = frameContext.agc.exposureMode, >>> - .lux = frameContext.lux.lux, >>> - .exposureCompensation = pow(2.0, frameContext.agc.exposureValue), >>> - }); >>> - >>> - LOG(RkISP1Agc, Debug) >>> - << "Divided up exposure time, analogue gain, quantization gain" >>> - << " and digital gain are " << newEv.exposureTime << ", " << newEv.analogueGain >>> - << ", " << newEv.quantizationGain << " and " << newEv.digitalGain; >>> - >>> - IPAActiveState &activeState = context.activeState; >>> - /* Update the estimated exposure and gain. */ >>> - activeState.agc.automatic.exposure = newEv.exposureTime / lineDuration; >>> - activeState.agc.automatic.gain = newEv.analogueGain; >>> - activeState.agc.automatic.quantizationGain = newEv.quantizationGain; >>> - activeState.agc.automatic.yTarget = newEv.yTarget; >>> - /* >>> - * 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, newEv.exposureTime)); >>> - >>> - fillMetadata(context, frameContext, metadata); >>> + metadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode); >>> } >>> >>> REGISTER_IPA_ALGORITHM(Agc, "Agc") >>> diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h >>> index 0527ca0d5f..3a4d7bc546 100644 >>> --- a/src/ipa/rkisp1/algorithms/agc.h >>> +++ b/src/ipa/rkisp1/algorithms/agc.h >>> @@ -14,7 +14,7 @@ >>> >>> #include <libcamera/geometry.h> >>> >>> -#include "libipa/agc_mean_luminance.h" >>> +#include "libipa/agc.h" >>> >>> #include "algorithm.h" >>> >>> @@ -47,14 +47,8 @@ private: >>> uint8_t computeHistogramPredivider(const Size &size, >>> enum rkisp1_cif_isp_histogram_mode mode); >>> >>> - void fillMetadata(IPAContext &context, IPAFrameContext &frameContext, >>> - ControlList &metadata); >>> - void processFrameDuration(IPAContext &context, >>> - IPAFrameContext &frameContext, >>> - utils::Duration frameDuration); >>> - >>> std::map<int32_t, std::vector<uint8_t>> meteringModes_; >>> - AgcMeanLuminance agc_; >>> + AgcAlgorithm agc_; >>> }; >>> >>> } /* namespace ipa::rkisp1::algorithms */ >>> diff --git a/src/ipa/rkisp1/algorithms/lux.cpp b/src/ipa/rkisp1/algorithms/lux.cpp >>> index 86e46c492f..ce6928a55d 100644 >>> --- a/src/ipa/rkisp1/algorithms/lux.cpp >>> +++ b/src/ipa/rkisp1/algorithms/lux.cpp >>> @@ -74,7 +74,7 @@ void Lux::process(IPAContext &context, >>> if (!stats) >>> return; >>> >>> - utils::Duration exposureTime = context.configuration.sensor.lineDuration * >>> + utils::Duration exposureTime = context.configuration.agc.lineDuration * >>> frameContext.sensor.exposure; >>> double gain = frameContext.sensor.gain; >>> >>> diff --git a/src/ipa/rkisp1/ipa_context.cpp b/src/ipa/rkisp1/ipa_context.cpp >>> index 1f94afda6b..47691674ad 100644 >>> --- a/src/ipa/rkisp1/ipa_context.cpp >>> +++ b/src/ipa/rkisp1/ipa_context.cpp >>> @@ -86,21 +86,6 @@ namespace libcamera::ipa::rkisp1 { >>> * \var IPASessionConfiguration::sensor >>> * \brief Sensor-specific configuration of the IPA >>> * >>> - * \var IPASessionConfiguration::sensor.minExposureTime >>> - * \brief Minimum exposure time supported with the sensor >>> - * >>> - * \var IPASessionConfiguration::sensor.maxExposureTime >>> - * \brief Maximum exposure time supported with the sensor >>> - * >>> - * \var IPASessionConfiguration::sensor.minAnalogueGain >>> - * \brief Minimum analogue gain supported with the sensor >>> - * >>> - * \var IPASessionConfiguration::sensor.maxAnalogueGain >>> - * \brief Maximum analogue gain supported with the sensor >>> - * >>> - * \var IPASessionConfiguration::sensor.lineDuration >>> - * \brief Line duration in microseconds >>> - * >>> * \var IPASessionConfiguration::sensor.size >>> * \brief Sensor output resolution >>> */ >>> @@ -147,49 +132,8 @@ namespace libcamera::ipa::rkisp1 { >>> * \var IPAActiveState::agc >>> * \brief State for the Automatic Gain Control algorithm >>> * >>> - * 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. >>> - * >>> - * \struct IPAActiveState::agc.manual >>> - * \brief Manual exposure time and analog gain (set through requests) >>> - * >>> - * \var IPAActiveState::agc.manual.exposure >>> - * \brief Manual exposure time expressed as a number of lines as set by the >>> - * ExposureTime control >>> - * >>> - * \var IPAActiveState::agc.manual.gain >>> - * \brief Manual analogue gain as set by the AnalogueGain control >>> - * >>> - * \struct IPAActiveState::agc.automatic >>> - * \brief Automatic exposure time and analog gain (computed by the algorithm) >>> - * >>> - * \var IPAActiveState::agc.automatic.exposure >>> - * \brief Automatic exposure time expressed as a number of lines >>> - * >>> - * \var IPAActiveState::agc.automatic.gain >>> - * \brief Automatic analogue gain multiplier >>> - * >>> - * \var IPAActiveState::agc.autoExposureEnabled >>> - * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control >>> - * >>> - * \var IPAActiveState::agc.autoGainEnabled >>> - * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control >>> - * >>> - * \var IPAActiveState::agc.constraintMode >>> - * \brief Constraint mode as set by the AeConstraintMode control >>> - * >>> - * \var IPAActiveState::agc.exposureMode >>> - * \brief Exposure mode as set by the AeExposureMode control >>> - * >>> * \var IPAActiveState::agc.meteringMode >>> * \brief Metering mode as set by the AeMeteringMode control >>> - * >>> - * \var IPAActiveState::agc.minFrameDuration >>> - * \brief Minimum frame duration as set by the FrameDurationLimits control >>> - * >>> - * \var IPAActiveState::agc.maxFrameDuration >>> - * \brief Maximum frame duration as set by the FrameDurationLimits control >>> */ >>> >>> /** >>> @@ -314,53 +258,11 @@ namespace libcamera::ipa::rkisp1 { >>> * the vertical blanking period is determined to maintain a consistent frame >>> * rate matched to the FrameDurationLimits as set by the user. >>> * >>> - * \var IPAFrameContext::agc.exposure >>> - * \brief Exposure time expressed as a number of lines computed by the algorithm >>> - * >>> - * \var IPAFrameContext::agc.gain >>> - * \brief Analogue gain multiplier computed by the algorithm >>> - * >>> - * The gain should be adapted to the sensor specific gain code before applying. >>> - * >>> - * \var IPAFrameContext::agc.vblank >>> - * \brief Vertical blanking parameter computed by the algorithm >>> - * >>> - * \var IPAFrameContext::agc.autoExposureEnabled >>> - * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control >>> - * >>> - * \var IPAFrameContext::agc.autoGainEnabled >>> - * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control >>> - * >>> - * \var IPAFrameContext::agc.constraintMode >>> - * \brief Constraint mode as set by the AeConstraintMode control >>> - * >>> - * \var IPAFrameContext::agc.exposureMode >>> - * \brief Exposure mode as set by the AeExposureMode control >>> - * >>> * \var IPAFrameContext::agc.meteringMode >>> * \brief Metering mode as set by the AeMeteringMode control >>> * >>> - * \var IPAFrameContext::agc.minFrameDuration >>> - * \brief Minimum frame duration as set by the FrameDurationLimits control >>> - * >>> - * \var IPAFrameContext::agc.maxFrameDuration >>> - * \brief Maximum frame duration as set by the FrameDurationLimits control >>> - * >>> - * \var IPAFrameContext::agc.frameDuration >>> - * \brief The actual FrameDuration used by the algorithm for the frame >>> - * >>> * \var IPAFrameContext::agc.updateMetering >>> * \brief Indicate if new ISP AGC metering parameters need to be applied >>> - * >>> - * \var IPAFrameContext::agc.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 IPAFrameContext::agc.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. >>> */ >>> >>> /** >>> diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h >>> index cd213dd991..cc07bb9462 100644 >>> --- a/src/ipa/rkisp1/ipa_context.h >>> +++ b/src/ipa/rkisp1/ipa_context.h >>> @@ -24,7 +24,7 @@ >>> #include "libcamera/internal/matrix.h" >>> #include "libcamera/internal/vector.h" >>> >>> -#include "libipa/agc_mean_luminance.h" >>> +#include "libipa/agc.h" >>> #include "libipa/awb.h" >>> #include "libipa/camera_sensor_helper.h" >>> #include "libipa/ccm.h" >>> @@ -57,7 +57,7 @@ struct RKISP1AwbSession { >>> }; >>> >>> struct IPASessionConfiguration { >>> - struct { >>> + struct Agc : agc::Session { >>> struct rkisp1_cif_isp_window measureWindow; >>> } agc; >>> >>> @@ -68,12 +68,6 @@ struct IPASessionConfiguration { >>> } compress; >>> >>> struct { >>> - utils::Duration minExposureTime; >>> - utils::Duration maxExposureTime; >>> - double minAnalogueGain; >>> - double maxAnalogueGain; >>> - >>> - utils::Duration lineDuration; >>> Size size; >>> } sensor; >>> >>> @@ -82,26 +76,8 @@ struct IPASessionConfiguration { >>> }; >>> >>> struct IPAActiveState { >>> - struct { >>> - 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; >>> + struct Agc : agc::ActiveState { >>> controls::AeMeteringModeEnum meteringMode; >>> - utils::Duration minFrameDuration; >>> - utils::Duration maxFrameDuration; >>> } agc; >>> >>> ipa::awb::ActiveState awb; >>> @@ -145,24 +121,9 @@ struct IPAActiveState { >>> }; >>> >>> struct IPAFrameContext : public FrameContext { >>> - struct { >>> - uint32_t exposure; >>> - double gain; >>> - double exposureValue; >>> - double quantizationGain; >>> - uint32_t vblank; >>> - double yTarget; >>> - bool autoExposureEnabled; >>> - bool autoGainEnabled; >>> - controls::AeConstraintModeEnum constraintMode; >>> - controls::AeExposureModeEnum exposureMode; >>> + struct Agc : agc::FrameContext { >>> controls::AeMeteringModeEnum meteringMode; >>> - utils::Duration minFrameDuration; >>> - utils::Duration maxFrameDuration; >>> - utils::Duration frameDuration; >>> bool updateMetering; >>> - bool autoExposureModeChange; >>> - bool autoGainModeChange; >>> } agc; >>> >>> ipa::awb::FrameContext awb; >>> diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp >>> index 98ec5a5748..731a362cee 100644 >>> --- a/src/ipa/rkisp1/rkisp1.cpp >>> +++ b/src/ipa/rkisp1/rkisp1.cpp >>> @@ -6,8 +6,6 @@ >>> */ >>> >>> #include <algorithm> >>> -#include <array> >>> -#include <chrono> >>> #include <stdint.h> >>> #include <string.h> >>> >>> @@ -40,8 +38,6 @@ namespace libcamera { >>> >>> LOG_DEFINE_CATEGORY(IPARkISP1) >>> >>> -using namespace std::literals::chrono_literals; >>> - >>> namespace ipa::rkisp1 { >>> >>> /* Maximum number of frame contexts to be held */ >>> -- >>> 2.55.0 >>>
2026. 08. 20. 12:19 keltezéssel, Stefan Klug írta: > Hi Barnabás, > > Quoting Barnabás Pőcze (2026-08-20 11:08:00) >> 2026. 08. 19. 17:31 keltezéssel, Stefan Klug írta: >>> Hi Barnabás, >>> >>> Thank you for the patch. >>> >>> Quoting Barnabás Pőcze (2026-08-17 13:43:22) >>>> Add a class that implements the `Algorithm` interface using `AgcMeanLuminance` >>>> based on the rkisp1 `Agc` algorithm, with the following main adjustments: >>>> >>>> * the parameters for `process()` have been made optional to handle >>>> the cases where statistics are not available; >>>> * the "raw" capture check has been replaced with the "autoAllowed" >>>> session parameter; >>>> * the controls are only provided after `configure()`. >>> >>> Eek I completely missed that in v4. Thanks for pointing it out. >>> I thought that this was problematic as cam showed only controls that >>> were available before configure. But testing it reveals that this is not >>> the case. So either I remember incorrectly or cam got fixed :-) >> >> Well, every user calls `configure()` in its `init()` to provide initial >> controls for the camera (before configuration). > > Oh I missed that, Tanks for clarification. Maybe the proposal from > Jacopo in his reply is a good one? > >> >> >>> >>>> >>>> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> >>>> --- >>>> src/ipa/libipa/agc.cpp | 637 +++++++++++++++++++++++++++++- >>>> src/ipa/libipa/agc.h | 106 +++++ >>>> src/ipa/rkisp1/algorithms/agc.cpp | 464 +++------------------- >>>> src/ipa/rkisp1/algorithms/agc.h | 10 +- >>>> src/ipa/rkisp1/algorithms/lux.cpp | 2 +- >>>> src/ipa/rkisp1/ipa_context.cpp | 98 ----- >>>> src/ipa/rkisp1/ipa_context.h | 47 +-- >>>> src/ipa/rkisp1/rkisp1.cpp | 4 - >>>> 8 files changed, 805 insertions(+), 563 deletions(-) >>>> >>>> diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp >>>> index 415a6d831f..3c6b12e452 100644 >>>> --- a/src/ipa/libipa/agc.cpp >>>> +++ b/src/ipa/libipa/agc.cpp >>>> @@ -1,16 +1,32 @@ >>>> /* SPDX-License-Identifier: LGPL-2.1-or-later */ >>>> /* >>>> - * Copyright (C) 2026 Ideas On Board >>>> + * Copyright (C) 2021-2026 Ideas On Board >>>> * >>>> * Auto exposure/gain algorithm for implementing the IPA-specific AGC algorithms >>> >>> Nit: line break >> >> How do you mean? >> > > Sorry, I miscounted in the email. The line looked so long but is > actually exactly 80 chars. > >> >>> >>>> */ >>>> >>>> #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) >>>> + >>>> namespace agc { >>>> >>>> /** >>>> @@ -40,6 +56,625 @@ namespace agc { >>>> >>>> } /* namespace 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 >>>> + */ >>>> + >>> >>> Moving these variables into agc::FrameContext and agx::ActiveState in a separate >>> preparatory patch might have reduced the size of this patch by quite a >>> bit. I don't want to send a new Yak, so I won't dwell on it :-) >> >> I thought about it, but now I'm not sure why I didn't do it. Maybe I'll try again. >> >> >>> >>>> +/** >>>> + * \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 >>>> + * >>>> + * \note The IPA algorithm implementation will most likely need to call >>>> + * this in its Algorithm::init() implementation in order to provide >>>> + * the initial controls for the camera. >>>> + */ >>>> +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; >>>> + >>>> + /* \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] } }, >>>> + }; >>>> + config.ctrlMap[&controls::ExposureTimeMode] = ControlInfo{ >>>> + {{ controls::ExposureTimeModeAuto, controls::ExposureTimeModeManual }}, >>>> + controls::ExposureTimeModeAuto, >>>> + }; >>>> + config.ctrlMap[&controls::AnalogueGainMode] = ControlInfo{ >>>> + {{ controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual }}, >>>> + controls::AnalogueGainModeAuto, >>>> + }; >>>> + config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); >>>> + config.ctrlMap.merge(impl_.controls()); >>>> + >>>> + 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) >>>> +{ >>>> + if (!params) { >>>> + processFrameDuration(session, frameContext, frameContext.minFrameDuration); >>>> + fillMetadata(session, frameContext, metadata); >>>> + return; >>>> + } >>>> + >>>> + ASSERT(session.autoAllowed); >>>> + >>>> + const utils::Duration &lineDuration = session.lineDuration; >>>> + >>>> + /* >>>> + * 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; >>>> + >>>> + LOG(Agc, Debug) >>>> + << "Divided up exposure time, analogue gain, quantization gain" >>>> + << " and digital gain are " << newEv.exposureTime >>>> + << ", " << 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. >>>> + */ >>>> + processFrameDuration(session, frameContext, >>>> + std::max(frameContext.minFrameDuration, newEv.exposureTime)); >>>> + >>>> + fillMetadata(session, frameContext, metadata); >>>> +} >>>> + >>>> +/** >>>> + * \brief Process frame duration and compute vblank >>>> + * \param[in] session The session parameters >>>> + * \param[in] frameContext The current frame context >>>> + * \param[in] frameDuration The target frame duration >>>> + * >>>> + * Compute and populate vblank from the target frame duration. >>>> + */ >>>> +void AgcAlgorithm::processFrameDuration(const agc::Session &session, >>>> + agc::FrameContext &frameContext, >>>> + utils::Duration frameDuration) >>>> +{ >>>> + const utils::Duration &lineDuration = session.lineDuration; >>>> + >>>> + 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; >>>> +} >>>> + >>>> +void AgcAlgorithm::fillMetadata(const agc::Session &session, >>> >>> Does this one need documentation as it lives in libipa now? >> >> I don't know. It's a private function, implementation detail, >> and fairly straightforward in my opinion. > > Oh I expected the doxygen to nag it. But as it's private it won't. Then > it is fine with me. > >> >> >>> >>>> + const agc::FrameContext &frameContext, >>>> + ControlList &metadata) >>>> +{ >>>> + >>>> + metadata.set(controls::AnalogueGain, frameContext.gain); >>>> + metadata.set(controls::ExposureTime, >>>> + utils::Duration(session.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/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h >>>> index cd213dd991..cc07bb9462 100644 >>>> --- a/src/ipa/rkisp1/ipa_context.h >>>> +++ b/src/ipa/rkisp1/ipa_context.h >>>> @@ -24,7 +24,7 @@ >>>> #include "libcamera/internal/matrix.h" >>>> #include "libcamera/internal/vector.h" >>>> >>>> -#include "libipa/agc_mean_luminance.h" >>>> +#include "libipa/agc.h" >>>> #include "libipa/awb.h" >>>> #include "libipa/camera_sensor_helper.h" >>>> #include "libipa/ccm.h" >>>> @@ -57,7 +57,7 @@ struct RKISP1AwbSession { >>>> }; >>>> >>>> struct IPASessionConfiguration { >>>> - struct { >>>> + struct Agc : agc::Session { >>>> struct rkisp1_cif_isp_window measureWindow; >>>> } agc; >>>> >>>> @@ -68,12 +68,6 @@ struct IPASessionConfiguration { >>>> } compress; >>>> >>>> struct { >>>> - utils::Duration minExposureTime; >>>> - utils::Duration maxExposureTime; >>>> - double minAnalogueGain; >>>> - double maxAnalogueGain; >>>> - >>>> - utils::Duration lineDuration; >>>> Size size; >>>> } sensor; >>>> >>>> @@ -82,26 +76,8 @@ struct IPASessionConfiguration { >>>> }; >>>> >>>> struct IPAActiveState { >>>> - struct { >>>> - 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; >>>> + struct Agc : agc::ActiveState { >>>> controls::AeMeteringModeEnum meteringMode; >>>> - utils::Duration minFrameDuration; >>>> - utils::Duration maxFrameDuration; >>>> } agc; >>>> >>>> ipa::awb::ActiveState awb; >>>> @@ -145,24 +121,9 @@ struct IPAActiveState { >>>> }; >>>> >>>> struct IPAFrameContext : public FrameContext { >>>> - struct { >>>> - uint32_t exposure; >>>> - double gain; >>>> - double exposureValue; >>>> - double quantizationGain; >>>> - uint32_t vblank; >>>> - double yTarget; >>>> - bool autoExposureEnabled; >>>> - bool autoGainEnabled; >>>> - controls::AeConstraintModeEnum constraintMode; >>>> - controls::AeExposureModeEnum exposureMode; >>>> + struct Agc : agc::FrameContext { >>>> controls::AeMeteringModeEnum meteringMode; >>>> - utils::Duration minFrameDuration; >>>> - utils::Duration maxFrameDuration; >>>> - utils::Duration frameDuration; >>>> bool updateMetering; >>>> - bool autoExposureModeChange; >>>> - bool autoGainModeChange; >>>> } agc; >>>> >>>> ipa::awb::FrameContext awb; >>>> diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp >>>> index 98ec5a5748..731a362cee 100644 >>>> --- a/src/ipa/rkisp1/rkisp1.cpp >>>> +++ b/src/ipa/rkisp1/rkisp1.cpp >>>> @@ -6,8 +6,6 @@ >>>> */ >>>> >>>> #include <algorithm> >>>> -#include <array> >>>> -#include <chrono> >>> >>> These changes look unrelated? >> >> These are now unused because the code has been moved. >> > > But then these were wrong already before this patch as there were no > other changes in this file? > > But no worries, cleanup is always good. Ahh, you're right, this change is indeed in the wrong commit, I have now fixed that. > > Best regards, > Stefan > >> >>> >>>> #include <stdint.h> >>>> #include <string.h> >>>> >>>> @@ -40,8 +38,6 @@ namespace libcamera { >>>> >>>> LOG_DEFINE_CATEGORY(IPARkISP1) >>>> >>>> -using namespace std::literals::chrono_literals; >>>> - >>>> namespace ipa::rkisp1 { >>>> >>>> /* Maximum number of frame contexts to be held */ >>>> -- >>>> 2.55.0 >>>> >>> >>> This is a massive beast. Thanks for squashing it. I like that it is now >>> possible to compare new code and old code in one patch. I didn't do >>> another detailed review, but that already happened on v4. >>> >>> So >>> >>> Reviewed-by: Stefan Klug <stefan.klug@ideasonboard.com> >>> Tested-by: Stefan Klug <stefan.klug@ideasonboard.com> >>> >>> Best regards, >>> Stefan >>
diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 415a6d831f..3c6b12e452 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -1,16 +1,32 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ /* - * Copyright (C) 2026 Ideas On Board + * Copyright (C) 2021-2026 Ideas On Board * * Auto exposure/gain algorithm for implementing the IPA-specific AGC algorithms */ #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) + namespace agc { /** @@ -40,6 +56,625 @@ namespace agc { } /* namespace 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 + * + * \note The IPA algorithm implementation will most likely need to call + * this in its Algorithm::init() implementation in order to provide + * the initial controls for the camera. + */ +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; + + /* \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] } }, + }; + config.ctrlMap[&controls::ExposureTimeMode] = ControlInfo{ + {{ controls::ExposureTimeModeAuto, controls::ExposureTimeModeManual }}, + controls::ExposureTimeModeAuto, + }; + config.ctrlMap[&controls::AnalogueGainMode] = ControlInfo{ + {{ controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual }}, + controls::AnalogueGainModeAuto, + }; + config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); + config.ctrlMap.merge(impl_.controls()); + + 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) +{ + if (!params) { + processFrameDuration(session, frameContext, frameContext.minFrameDuration); + fillMetadata(session, frameContext, metadata); + return; + } + + ASSERT(session.autoAllowed); + + const utils::Duration &lineDuration = session.lineDuration; + + /* + * 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; + + LOG(Agc, Debug) + << "Divided up exposure time, analogue gain, quantization gain" + << " and digital gain are " << newEv.exposureTime + << ", " << 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. + */ + processFrameDuration(session, frameContext, + std::max(frameContext.minFrameDuration, newEv.exposureTime)); + + fillMetadata(session, frameContext, metadata); +} + +/** + * \brief Process frame duration and compute vblank + * \param[in] session The session parameters + * \param[in] frameContext The current frame context + * \param[in] frameDuration The target frame duration + * + * Compute and populate vblank from the target frame duration. + */ +void AgcAlgorithm::processFrameDuration(const agc::Session &session, + agc::FrameContext &frameContext, + utils::Duration frameDuration) +{ + const utils::Duration &lineDuration = session.lineDuration; + + 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; +} + +void AgcAlgorithm::fillMetadata(const agc::Session &session, + const agc::FrameContext &frameContext, + ControlList &metadata) +{ + + metadata.set(controls::AnalogueGain, frameContext.gain); + metadata.set(controls::ExposureTime, + utils::Duration(session.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 4789c06ef8..66aa0eacb0 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 { @@ -21,6 +27,61 @@ namespace ipa { namespace agc { +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; +}; + [[nodiscard]] inline std::pair<uint32_t, double> extractControls(const ControlList &controls, const CameraSensorHelper *sensor) @@ -47,6 +108,51 @@ prepareControls(ControlList &controls, const CameraSensorHelper *sensor, } /* namespace agc */ +class AgcAlgorithm +{ +public: + struct ConfigurationParams { + const CameraSensorHelper *sensor; + const IPACameraSensorInfo &sensorInfo; + const ControlInfoMap &sensorControls; + ControlInfoMap::Map &ctrlMap; + bool autoAllowed = true; + }; + + struct ProcessParams { + const AgcMeanLuminance::Traits &traits; + const Histogram &yHist; + uint32_t exposure; + double gain; + std::vector<AgcMeanLuminance::AgcConstraint> &&additionalConstraints = {}; + double lux = 0; + }; + + 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); + + void process(const agc::Session &session, agc::ActiveState &state, + agc::FrameContext &frameContext, std::optional<ProcessParams> &¶ms, + ControlList &metadata); + +private: + void processFrameDuration(const agc::Session &session, + agc::FrameContext &frameContext, + utils::Duration frameDuration); + void fillMetadata(const agc::Session &session, + const agc::FrameContext &frameContext, + ControlList &metadata); + + AgcMeanLuminance impl_; +}; + } /* namespace ipa */ } /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index fc228452c3..4c2a066e86 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -8,9 +8,7 @@ #include "agc.h" #include <algorithm> -#include <chrono> #include <cmath> -#include <tuple> #include <vector> #include <libcamera/base/log.h> @@ -35,89 +33,6 @@ namespace ipa::rkisp1::algorithms { LOG_DEFINE_CATEGORY(RkISP1Agc) -namespace { - -void reconfigure(IPAContext &context) -{ - context.configuration.sensor.lineDuration = - context.sensorInfo.minLineLength * 1.0s / context.sensorInfo.pixelRate; - - double lineDurationUs = context.configuration.sensor.lineDuration.get<std::micro>(); - - /* - * Compute exposure time limits from the V4L2_CID_EXPOSURE control - * limits and the line duration. - */ - - const ControlInfo &v4l2Exposure = context.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>(); - context.ctrlMap[&controls::ExposureTime] = ControlInfo{ - static_cast<int32_t>(minExposure * lineDurationUs), - static_cast<int32_t>(maxExposure * lineDurationUs), - static_cast<int32_t>(defExposure * lineDurationUs), - }; - - /* Compute the analogue gain limits. */ - const ControlInfo &v4l2Gain = context.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; - float minGain = context.camHelper->gain(v4l2Gain.min().get<int32_t>()); - float maxGain = context.camHelper->gain(v4l2Gain.max().get<int32_t>()); - float defGain = context.camHelper->gain(v4l2Gain.def().get<int32_t>()); - context.ctrlMap[&controls::AnalogueGain] = ControlInfo{ - minGain, - maxGain, - defGain, - }; - - LOG(RkISP1Agc, 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 = context.sensorControls.find(V4L2_CID_HBLANK)->second; - uint32_t hblank = v4l2HBlank.def().get<int32_t>(); - uint32_t lineLength = context.sensorInfo.outputSize.width + hblank; - - const ControlInfo &v4l2VBlank = context.sensorControls.find(V4L2_CID_VBLANK)->second; - std::array<uint32_t, 3> frameHeights{ - v4l2VBlank.min().get<int32_t>() + context.sensorInfo.outputSize.height, - v4l2VBlank.max().get<int32_t>() + context.sensorInfo.outputSize.height, - v4l2VBlank.def().get<int32_t>() + context.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 / (context.sensorInfo.pixelRate / 1000000U); - } - - context.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ - frameDurations[0], - frameDurations[1], - Span<const int64_t, 2>{ { frameDurations[2], frameDurations[2] } }, - }; - - /* - * 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 - */ - context.configuration.sensor.minExposureTime = minExposure * context.configuration.sensor.lineDuration; - context.configuration.sensor.maxExposureTime = maxExposure * context.configuration.sensor.lineDuration; - context.configuration.sensor.minAnalogueGain = minGain; - context.configuration.sensor.maxAnalogueGain = maxGain; -} - -} /* namespace */ - /** * \class Agc * \brief A mean-based auto-exposure algorithm @@ -221,7 +136,16 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) { int ret; - ret = agc_.parseTuningData(tuningData); + ret = agc_.init(tuningData); + if (ret) + return ret; + + ret = agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensor = context.camHelper.get(), + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + }); if (ret) return ret; @@ -230,21 +154,6 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) if (ret) return ret; - context.ctrlMap[&controls::ExposureTimeMode] = - ControlInfo({ { ControlValue(controls::ExposureTimeModeAuto), - ControlValue(controls::ExposureTimeModeManual) } }, - ControlValue(controls::ExposureTimeModeAuto)); - context.ctrlMap[&controls::AnalogueGainMode] = - ControlInfo({ { ControlValue(controls::AnalogueGainModeAuto), - ControlValue(controls::AnalogueGainModeManual) } }, - ControlValue(controls::AnalogueGainModeAuto)); - /* \todo Move this to the Camera class */ - context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true, true); - context.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); - context.ctrlMap.merge(agc_.controls()); - - reconfigure(context); - return 0; } @@ -257,47 +166,24 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) */ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) { - reconfigure(context); - - /* Configure the default exposure and gain. */ - context.activeState.agc.automatic.gain = context.configuration.sensor.minAnalogueGain; - context.activeState.agc.automatic.exposure = - 10ms / context.configuration.sensor.lineDuration; - context.activeState.agc.automatic.quantizationGain = 1.0; - context.activeState.agc.manual.gain = context.activeState.agc.automatic.gain; - context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure; - context.activeState.agc.autoExposureEnabled = !context.configuration.raw; - context.activeState.agc.autoGainEnabled = !context.configuration.raw; - context.activeState.agc.exposureValue = 0.0; - - context.activeState.agc.constraintMode = - static_cast<controls::AeConstraintModeEnum>(agc_.constraintModes().begin()->first); - context.activeState.agc.exposureMode = - static_cast<controls::AeExposureModeEnum>(agc_.exposureModeHelpers().begin()->first); + int ret = agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensor = context.camHelper.get(), + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + .autoAllowed = !context.configuration.raw, + }); + if (ret) + return ret; + context.activeState.agc.meteringMode = static_cast<controls::AeMeteringModeEnum>(meteringModes_.begin()->first); - /* Limit the frame duration to match current initialisation */ - ControlInfo &frameDurationLimits = context.ctrlMap[&controls::FrameDurationLimits]; - context.activeState.agc.minFrameDuration = std::chrono::microseconds(frameDurationLimits.min().get<int64_t>()); - context.activeState.agc.maxFrameDuration = std::chrono::microseconds(frameDurationLimits.max().get<int64_t>()); - context.configuration.agc.measureWindow.h_offs = 0; context.configuration.agc.measureWindow.v_offs = 0; context.configuration.agc.measureWindow.h_size = configInfo.outputSize.width; context.configuration.agc.measureWindow.v_size = configInfo.outputSize.height; - agc_.configure(context.configuration.sensor.lineDuration, context.camHelper.get()); - - agc_.setLimits(context.configuration.sensor.minExposureTime, - context.configuration.sensor.maxExposureTime, - context.configuration.sensor.minAnalogueGain, - context.configuration.sensor.maxAnalogueGain, {}); - - context.activeState.agc.automatic.yTarget = agc_.effectiveYTarget(0, 1); - - agc_.resetFrameCount(); - return 0; } @@ -311,73 +197,7 @@ void Agc::queueRequest(IPAContext &context, { auto &agc = context.activeState.agc; - if (!context.configuration.raw) { - const auto &aeEnable = controls.get(controls::ExposureTimeMode); - if (aeEnable && - (*aeEnable == controls::ExposureTimeModeAuto) != agc.autoExposureEnabled) { - agc.autoExposureEnabled = (*aeEnable == controls::ExposureTimeModeAuto); - - LOG(RkISP1Agc, Debug) - << (agc.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 (!agc.autoExposureEnabled && !controls.get(controls::ExposureTime)) - frameContext.agc.autoExposureModeChange = true; - } - - const auto &agEnable = controls.get(controls::AnalogueGainMode); - if (agEnable && - (*agEnable == controls::AnalogueGainModeAuto) != agc.autoGainEnabled) { - agc.autoGainEnabled = (*agEnable == controls::AnalogueGainModeAuto); - - LOG(RkISP1Agc, Debug) - << (agc.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 (!agc.autoGainEnabled && !controls.get(controls::AnalogueGain)) - frameContext.agc.autoGainModeChange = true; - } - } - - const auto &exposure = controls.get(controls::ExposureTime); - if (exposure && !agc.autoExposureEnabled) { - agc.manual.exposure = *exposure * 1.0us - / context.configuration.sensor.lineDuration; - - LOG(RkISP1Agc, Debug) - << "Set exposure to " << agc.manual.exposure; - } - - const auto &gain = controls.get(controls::AnalogueGain); - if (gain && !agc.autoGainEnabled) { - agc.manual.gain = *gain; - - LOG(RkISP1Agc, Debug) << "Set gain to " << agc.manual.gain; - } - - frameContext.agc.autoExposureEnabled = agc.autoExposureEnabled; - frameContext.agc.autoGainEnabled = agc.autoGainEnabled; - - if (!frameContext.agc.autoExposureEnabled) - frameContext.agc.exposure = agc.manual.exposure; - if (!frameContext.agc.autoGainEnabled) - frameContext.agc.gain = agc.manual.gain; - - if (!frameContext.agc.autoExposureEnabled && - !frameContext.agc.autoGainEnabled) - frameContext.agc.quantizationGain = 1.0; + agc_.queueRequest(context.configuration.agc, agc, frameContext.agc, controls); const auto &meteringMode = controls.get(controls::AeMeteringMode); if (meteringMode) { @@ -386,42 +206,6 @@ void Agc::queueRequest(IPAContext &context, static_cast<controls::AeMeteringModeEnum>(*meteringMode); } frameContext.agc.meteringMode = agc.meteringMode; - - const auto &exposureMode = controls.get(controls::AeExposureMode); - if (exposureMode) - agc.exposureMode = - static_cast<controls::AeExposureModeEnum>(*exposureMode); - frameContext.agc.exposureMode = agc.exposureMode; - - const auto &constraintMode = controls.get(controls::AeConstraintMode); - if (constraintMode) - agc.constraintMode = - static_cast<controls::AeConstraintModeEnum>(*constraintMode); - frameContext.agc.constraintMode = agc.constraintMode; - - const auto &exposureValue = controls.get(controls::ExposureValue); - if (exposureValue) - agc.exposureValue = *exposureValue; - frameContext.agc.exposureValue = agc.exposureValue; - - const auto &frameDurationLimits = controls.get(controls::FrameDurationLimits); - if (frameDurationLimits) { - /* Limit the control value to the limits in ControlInfo */ - ControlInfo &limits = context.ctrlMap[&controls::FrameDurationLimits]; - int64_t minFrameDuration = - std::clamp((*frameDurationLimits).front(), - limits.min().get<int64_t>(), - limits.max().get<int64_t>()); - int64_t maxFrameDuration = - std::clamp((*frameDurationLimits).back(), - limits.min().get<int64_t>(), - limits.max().get<int64_t>()); - - agc.minFrameDuration = std::chrono::microseconds(minFrameDuration); - agc.maxFrameDuration = std::chrono::microseconds(maxFrameDuration); - } - frameContext.agc.minFrameDuration = agc.minFrameDuration; - frameContext.agc.maxFrameDuration = agc.maxFrameDuration; } /** @@ -430,41 +214,13 @@ void Agc::queueRequest(IPAContext &context, void Agc::prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, RkISP1Params *params) { - uint32_t activeAutoExposure = context.activeState.agc.automatic.exposure; - double activeAutoGain = context.activeState.agc.automatic.gain; - double activeAutoQGain = context.activeState.agc.automatic.quantizationGain; - - /* Populate exposure and gain in auto mode */ - if (frameContext.agc.autoExposureEnabled) { - frameContext.agc.exposure = activeAutoExposure; - frameContext.agc.quantizationGain = activeAutoQGain; - } - if (frameContext.agc.autoGainEnabled) { - frameContext.agc.gain = activeAutoGain; - frameContext.agc.quantizationGain = activeAutoQGain; - } - - /* - * Populate manual exposure and gain from the active auto values when - * transitioning from auto to manual - */ - if (!frameContext.agc.autoExposureEnabled && frameContext.agc.autoExposureModeChange) { - context.activeState.agc.manual.exposure = activeAutoExposure; - frameContext.agc.exposure = activeAutoExposure; - } - if (!frameContext.agc.autoGainEnabled && frameContext.agc.autoGainModeChange) { - context.activeState.agc.manual.gain = activeAutoGain; - frameContext.agc.gain = activeAutoGain; - frameContext.agc.quantizationGain = activeAutoQGain; - } + agc_.prepare(context.activeState.agc, frameContext.agc); if (context.configuration.compress.supported) { frameContext.compress.enable = true; frameContext.compress.gain = frameContext.agc.quantizationGain; } - frameContext.agc.yTarget = context.activeState.agc.automatic.yTarget; - if (frame > 0 && !frameContext.agc.updateMetering) return; @@ -520,50 +276,6 @@ void Agc::prepare(IPAContext &context, const uint32_t frame, static_cast<rkisp1_cif_isp_histogram_mode>(hstConfig->mode)); } -void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext, - ControlList &metadata) -{ - utils::Duration exposureTime = context.configuration.sensor.lineDuration - * frameContext.sensor.exposure; - metadata.set(controls::AnalogueGain, frameContext.sensor.gain); - metadata.set(controls::ExposureTime, exposureTime.get<std::micro>()); - metadata.set(controls::FrameDuration, frameContext.agc.frameDuration.get<std::micro>()); - metadata.set(controls::ExposureTimeMode, - frameContext.agc.autoExposureEnabled - ? controls::ExposureTimeModeAuto - : controls::ExposureTimeModeManual); - metadata.set(controls::AnalogueGainMode, - frameContext.agc.autoGainEnabled - ? controls::AnalogueGainModeAuto - : controls::AnalogueGainModeManual); - - metadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode); - metadata.set(controls::AeExposureMode, frameContext.agc.exposureMode); - metadata.set(controls::AeConstraintMode, frameContext.agc.constraintMode); - metadata.set(controls::ExposureValue, frameContext.agc.exposureValue); -} - -/** - * \brief Process frame duration and compute vblank - * \param[in] context The shared IPA context - * \param[in] frameContext The current frame context - * \param[in] frameDuration The target frame duration - * - * Compute and populate vblank from the target frame duration. - */ -void Agc::processFrameDuration(IPAContext &context, - IPAFrameContext &frameContext, - utils::Duration frameDuration) -{ - IPACameraSensorInfo &sensorInfo = context.sensorInfo; - utils::Duration lineDuration = context.configuration.sensor.lineDuration; - - frameContext.agc.vblank = (frameDuration / lineDuration) - sensorInfo.outputSize.height; - - /* Update frame duration accounting for line length quantization. */ - frameContext.agc.frameDuration = (sensorInfo.outputSize.height + frameContext.agc.vblank) * lineDuration; -} - namespace { class AgcTraits final : public AgcMeanLuminance::Traits @@ -637,21 +349,6 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, const rkisp1_stat_buffer *stats, ControlList &metadata) { - if (!stats) { - processFrameDuration(context, frameContext, - frameContext.agc.minFrameDuration); - fillMetadata(context, frameContext, metadata); - return; - } - - if (!(stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP)) { - fillMetadata(context, frameContext, metadata); - LOG(RkISP1Agc, Error) << "AUTOEXP data is missing in statistics"; - return; - } - - const utils::Duration &lineDuration = context.configuration.sensor.lineDuration; - /* * \todo Verify that the exposure and gain applied by the sensor for * this frame match what has been requested. This isn't a hard @@ -660,95 +357,46 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, * we receive), but is important in manual mode. */ - const rkisp1_cif_isp_stat *params = &stats->params; + const rkisp1_cif_isp_stat *params = nullptr; - /* - * 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.agc.autoExposureEnabled) { - minExposureTime = context.configuration.sensor.minExposureTime; - maxExposureTime = std::clamp(frameContext.agc.maxFrameDuration, - context.configuration.sensor.minExposureTime, - context.configuration.sensor.maxExposureTime); - } else { - minExposureTime = context.configuration.sensor.lineDuration - * frameContext.agc.exposure; - maxExposureTime = minExposureTime; + if (stats) { + if (stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP) + params = &stats->params; + else + LOG(RkISP1Agc, Error) << "AUTOEXP data is missing in statistics"; } - if (frameContext.agc.autoGainEnabled) { - minAnalogueGain = context.configuration.sensor.minAnalogueGain; - maxAnalogueGain = context.configuration.sensor.maxAnalogueGain; + if (params) { + std::vector<AgcMeanLuminance::AgcConstraint> additionalConstraints; + if (context.activeState.wdr.mode != controls::WdrOff) + additionalConstraints.push_back(context.activeState.wdr.constraint); + + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {{ + .traits = AgcTraits{ + { params->ae.exp_mean, context.hw.numAeCells }, + meteringModes_.at(frameContext.agc.meteringMode), + }, + .yHist = { + /* The lower 4 bits are fractional and meant to be discarded. */ + { params->hist.hist_bins, context.hw.numHistogramBins }, + [](uint32_t x) { return x >> 4; }, + }, + .exposure = frameContext.sensor.exposure, + /* + * Include the quantization gain if it was applied. Do not use + * compress.gain because it will include gains that shall not be + * reported to the user when HDR is implemented. + */ + .gain = frameContext.sensor.gain + * (frameContext.compress.enable ? frameContext.agc.quantizationGain : 1), + .additionalConstraints = std::move(additionalConstraints), + .lux = frameContext.lux.lux, + }}, metadata); } else { - minAnalogueGain = frameContext.agc.gain; - maxAnalogueGain = frameContext.agc.gain; + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {}, metadata); } - std::vector<AgcMeanLuminance::AgcConstraint> additionalConstraints; - if (context.activeState.wdr.mode != controls::WdrOff) - additionalConstraints.push_back(context.activeState.wdr.constraint); - - agc_.setLimits(minExposureTime, maxExposureTime, minAnalogueGain, maxAnalogueGain, - std::move(additionalConstraints)); - - /* - * The Agc algorithm needs to know the effective exposure value that was - * applied to the sensor when the statistics were collected. - */ - utils::Duration exposureTime = lineDuration * frameContext.sensor.exposure; - double analogueGain = frameContext.sensor.gain; - utils::Duration effectiveExposureValue = exposureTime * analogueGain; - - /* - * Include the quantization gain if it was applied. Do not use - * compress.gain because it will include gains that shall not be - * reported to the user when HDR is implemented. - */ - if (frameContext.compress.enable) - effectiveExposureValue *= frameContext.agc.quantizationGain; - - /* The lower 4 bits are fractional and meant to be discarded. */ - Histogram hist({ params->hist.hist_bins, context.hw.numHistogramBins }, - [](uint32_t x) { return x >> 4; }); - - const auto &newEv = agc_.calculateNewEv({ - .traits = AgcTraits{ - { params->ae.exp_mean, context.hw.numAeCells }, - meteringModes_.at(frameContext.agc.meteringMode), - }, - .yHist = hist, - .effectiveExposureValue = effectiveExposureValue, - .constraintModeIndex = frameContext.agc.constraintMode, - .exposureModeIndex = frameContext.agc.exposureMode, - .lux = frameContext.lux.lux, - .exposureCompensation = pow(2.0, frameContext.agc.exposureValue), - }); - - LOG(RkISP1Agc, Debug) - << "Divided up exposure time, analogue gain, quantization gain" - << " and digital gain are " << newEv.exposureTime << ", " << newEv.analogueGain - << ", " << newEv.quantizationGain << " and " << newEv.digitalGain; - - IPAActiveState &activeState = context.activeState; - /* Update the estimated exposure and gain. */ - activeState.agc.automatic.exposure = newEv.exposureTime / lineDuration; - activeState.agc.automatic.gain = newEv.analogueGain; - activeState.agc.automatic.quantizationGain = newEv.quantizationGain; - activeState.agc.automatic.yTarget = newEv.yTarget; - /* - * 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, newEv.exposureTime)); - - fillMetadata(context, frameContext, metadata); + metadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode); } REGISTER_IPA_ALGORITHM(Agc, "Agc") diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h index 0527ca0d5f..3a4d7bc546 100644 --- a/src/ipa/rkisp1/algorithms/agc.h +++ b/src/ipa/rkisp1/algorithms/agc.h @@ -14,7 +14,7 @@ #include <libcamera/geometry.h> -#include "libipa/agc_mean_luminance.h" +#include "libipa/agc.h" #include "algorithm.h" @@ -47,14 +47,8 @@ private: uint8_t computeHistogramPredivider(const Size &size, enum rkisp1_cif_isp_histogram_mode mode); - void fillMetadata(IPAContext &context, IPAFrameContext &frameContext, - ControlList &metadata); - void processFrameDuration(IPAContext &context, - IPAFrameContext &frameContext, - utils::Duration frameDuration); - std::map<int32_t, std::vector<uint8_t>> meteringModes_; - AgcMeanLuminance agc_; + AgcAlgorithm agc_; }; } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/algorithms/lux.cpp b/src/ipa/rkisp1/algorithms/lux.cpp index 86e46c492f..ce6928a55d 100644 --- a/src/ipa/rkisp1/algorithms/lux.cpp +++ b/src/ipa/rkisp1/algorithms/lux.cpp @@ -74,7 +74,7 @@ void Lux::process(IPAContext &context, if (!stats) return; - utils::Duration exposureTime = context.configuration.sensor.lineDuration * + utils::Duration exposureTime = context.configuration.agc.lineDuration * frameContext.sensor.exposure; double gain = frameContext.sensor.gain; diff --git a/src/ipa/rkisp1/ipa_context.cpp b/src/ipa/rkisp1/ipa_context.cpp index 1f94afda6b..47691674ad 100644 --- a/src/ipa/rkisp1/ipa_context.cpp +++ b/src/ipa/rkisp1/ipa_context.cpp @@ -86,21 +86,6 @@ namespace libcamera::ipa::rkisp1 { * \var IPASessionConfiguration::sensor * \brief Sensor-specific configuration of the IPA * - * \var IPASessionConfiguration::sensor.minExposureTime - * \brief Minimum exposure time supported with the sensor - * - * \var IPASessionConfiguration::sensor.maxExposureTime - * \brief Maximum exposure time supported with the sensor - * - * \var IPASessionConfiguration::sensor.minAnalogueGain - * \brief Minimum analogue gain supported with the sensor - * - * \var IPASessionConfiguration::sensor.maxAnalogueGain - * \brief Maximum analogue gain supported with the sensor - * - * \var IPASessionConfiguration::sensor.lineDuration - * \brief Line duration in microseconds - * * \var IPASessionConfiguration::sensor.size * \brief Sensor output resolution */ @@ -147,49 +132,8 @@ namespace libcamera::ipa::rkisp1 { * \var IPAActiveState::agc * \brief State for the Automatic Gain Control algorithm * - * 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. - * - * \struct IPAActiveState::agc.manual - * \brief Manual exposure time and analog gain (set through requests) - * - * \var IPAActiveState::agc.manual.exposure - * \brief Manual exposure time expressed as a number of lines as set by the - * ExposureTime control - * - * \var IPAActiveState::agc.manual.gain - * \brief Manual analogue gain as set by the AnalogueGain control - * - * \struct IPAActiveState::agc.automatic - * \brief Automatic exposure time and analog gain (computed by the algorithm) - * - * \var IPAActiveState::agc.automatic.exposure - * \brief Automatic exposure time expressed as a number of lines - * - * \var IPAActiveState::agc.automatic.gain - * \brief Automatic analogue gain multiplier - * - * \var IPAActiveState::agc.autoExposureEnabled - * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control - * - * \var IPAActiveState::agc.autoGainEnabled - * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control - * - * \var IPAActiveState::agc.constraintMode - * \brief Constraint mode as set by the AeConstraintMode control - * - * \var IPAActiveState::agc.exposureMode - * \brief Exposure mode as set by the AeExposureMode control - * * \var IPAActiveState::agc.meteringMode * \brief Metering mode as set by the AeMeteringMode control - * - * \var IPAActiveState::agc.minFrameDuration - * \brief Minimum frame duration as set by the FrameDurationLimits control - * - * \var IPAActiveState::agc.maxFrameDuration - * \brief Maximum frame duration as set by the FrameDurationLimits control */ /** @@ -314,53 +258,11 @@ namespace libcamera::ipa::rkisp1 { * the vertical blanking period is determined to maintain a consistent frame * rate matched to the FrameDurationLimits as set by the user. * - * \var IPAFrameContext::agc.exposure - * \brief Exposure time expressed as a number of lines computed by the algorithm - * - * \var IPAFrameContext::agc.gain - * \brief Analogue gain multiplier computed by the algorithm - * - * The gain should be adapted to the sensor specific gain code before applying. - * - * \var IPAFrameContext::agc.vblank - * \brief Vertical blanking parameter computed by the algorithm - * - * \var IPAFrameContext::agc.autoExposureEnabled - * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control - * - * \var IPAFrameContext::agc.autoGainEnabled - * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control - * - * \var IPAFrameContext::agc.constraintMode - * \brief Constraint mode as set by the AeConstraintMode control - * - * \var IPAFrameContext::agc.exposureMode - * \brief Exposure mode as set by the AeExposureMode control - * * \var IPAFrameContext::agc.meteringMode * \brief Metering mode as set by the AeMeteringMode control * - * \var IPAFrameContext::agc.minFrameDuration - * \brief Minimum frame duration as set by the FrameDurationLimits control - * - * \var IPAFrameContext::agc.maxFrameDuration - * \brief Maximum frame duration as set by the FrameDurationLimits control - * - * \var IPAFrameContext::agc.frameDuration - * \brief The actual FrameDuration used by the algorithm for the frame - * * \var IPAFrameContext::agc.updateMetering * \brief Indicate if new ISP AGC metering parameters need to be applied - * - * \var IPAFrameContext::agc.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 IPAFrameContext::agc.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. */ /** diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index cd213dd991..cc07bb9462 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -24,7 +24,7 @@ #include "libcamera/internal/matrix.h" #include "libcamera/internal/vector.h" -#include "libipa/agc_mean_luminance.h" +#include "libipa/agc.h" #include "libipa/awb.h" #include "libipa/camera_sensor_helper.h" #include "libipa/ccm.h" @@ -57,7 +57,7 @@ struct RKISP1AwbSession { }; struct IPASessionConfiguration { - struct { + struct Agc : agc::Session { struct rkisp1_cif_isp_window measureWindow; } agc; @@ -68,12 +68,6 @@ struct IPASessionConfiguration { } compress; struct { - utils::Duration minExposureTime; - utils::Duration maxExposureTime; - double minAnalogueGain; - double maxAnalogueGain; - - utils::Duration lineDuration; Size size; } sensor; @@ -82,26 +76,8 @@ struct IPASessionConfiguration { }; struct IPAActiveState { - struct { - 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; + struct Agc : agc::ActiveState { controls::AeMeteringModeEnum meteringMode; - utils::Duration minFrameDuration; - utils::Duration maxFrameDuration; } agc; ipa::awb::ActiveState awb; @@ -145,24 +121,9 @@ struct IPAActiveState { }; struct IPAFrameContext : public FrameContext { - struct { - uint32_t exposure; - double gain; - double exposureValue; - double quantizationGain; - uint32_t vblank; - double yTarget; - bool autoExposureEnabled; - bool autoGainEnabled; - controls::AeConstraintModeEnum constraintMode; - controls::AeExposureModeEnum exposureMode; + struct Agc : agc::FrameContext { controls::AeMeteringModeEnum meteringMode; - utils::Duration minFrameDuration; - utils::Duration maxFrameDuration; - utils::Duration frameDuration; bool updateMetering; - bool autoExposureModeChange; - bool autoGainModeChange; } agc; ipa::awb::FrameContext awb; diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 98ec5a5748..731a362cee 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -6,8 +6,6 @@ */ #include <algorithm> -#include <array> -#include <chrono> #include <stdint.h> #include <string.h> @@ -40,8 +38,6 @@ namespace libcamera { LOG_DEFINE_CATEGORY(IPARkISP1) -using namespace std::literals::chrono_literals; - namespace ipa::rkisp1 { /* Maximum number of frame contexts to be held */
Add a class that implements the `Algorithm` interface using `AgcMeanLuminance` based on the rkisp1 `Agc` algorithm, with the following main adjustments: * the parameters for `process()` have been made optional to handle the cases where statistics are not available; * the "raw" capture check has been replaced with the "autoAllowed" session parameter; * the controls are only provided after `configure()`. Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> --- src/ipa/libipa/agc.cpp | 637 +++++++++++++++++++++++++++++- src/ipa/libipa/agc.h | 106 +++++ src/ipa/rkisp1/algorithms/agc.cpp | 464 +++------------------- src/ipa/rkisp1/algorithms/agc.h | 10 +- src/ipa/rkisp1/algorithms/lux.cpp | 2 +- src/ipa/rkisp1/ipa_context.cpp | 98 ----- src/ipa/rkisp1/ipa_context.h | 47 +-- src/ipa/rkisp1/rkisp1.cpp | 4 - 8 files changed, 805 insertions(+), 563 deletions(-)