
目录一、概述二、头文件 exposure_mode_helper.h三、实现文件 exposure_mode_helper.cpp 逐段解析四、算法完整流程文字版五、代码实现一、概述ExposureModeHelper属于 libcamera IPA 模块是给 AEGC自动曝光自动增益控制使用的曝光拆分工具类。核心职责给定目标总曝光量将其拆分为硬件曝光时间、硬件模拟增益、量化补偿增益、ISP 数字增益四部分输出。普通 AE 策略通常先把曝光时间拉到最大再增加增益。 该类支持多阶段 (stage) 策略每个阶段配置一对(阶段最大曝光时间阶段总增益上限)。 业务价值可以主动限制曝光时长避免运动场景拖影、保障帧率提前启用增益当传入空 stages自动回退传统「优先曝光时间后增益」策略。重要定义stage 中的 gain 是逻辑总增益上限模拟增益 数字增益合计不是直接的模拟增益会被硬件模拟增益上限maxGain_钳位超出部分落到数字增益。quantizationGain硬件寄存器量化带来的补偿系数不是硬件可配置参数是软件算法补偿因子和数字增益相互独立要同时应用两者才能精准复现目标曝光。lineLength入参类型 DurationlineDuration_成员sensor 一行的时间行周期曝光时间必须是该值整数倍不是像素计数。二、头文件 exposure_mode_helper.h#pragma once #include tuple #include utility #include vector #include libcamera/base/span.h #include libcamera/base/utils.h #include camera_sensor_helper.h namespace libcamera { namespace ipa { class ExposureModeHelper { public: /// \param stages 阶段数组每一项(阶段最大曝光时间阶段总增益上限) ExposureModeHelper(const Spanstd::pairutils::Duration, double stages); ~ExposureModeHelper() default; /// 配置sensor行周期与sensor辅助对象用于硬件量化 /// \param lineLength sensor行周期时间维度 void configure(utils::Duration lineLength, const CameraSensorHelper *sensorHelper); /// 设置硬件运行时约束**每次硬件限制变化必须调用splitExposure调用前必须执行** void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime, double minGain, double maxGain); /// 核心接口输入目标总曝光量返回四元组 /// 返回(实际下发sensor曝光时间模拟增益量化补偿增益数字增益) std::tupleutils::Duration, double, double, double splitExposure(utils::Duration exposure) const; // 获取已经配置的硬件限制 utils::Duration minExposureTime() const { return minExposureTime_; } utils::Duration maxExposureTime() const { return maxExposureTime_; } double minGain() const { return minGain_; } double maxGain() const { return maxGain_; } private: /// 私有工具曝光时间钳位 对齐行周期量化 utils::Duration clampExposureTime(utils::Duration exposureTime, double *quantizationGain nullptr) const; /// 私有工具增益钳位 sensor增益档位量化 double clampGain(double gain, double *quantizationGain nullptr) const; std::vectorutils::Duration exposureTimes_; /// 各阶段的最大曝光时间 std::vectordouble gains_; /// 各阶段逻辑总增益上限 utils::Duration lineDuration_; /// sensor行周期来自configure utils::Duration minExposureTime_; /// 硬件最小曝光 utils::Duration maxExposureTime_; /// 硬件最大曝光 double minGain_; /// 硬件最小模拟增益 double maxGain_; /// 硬件最大模拟增益 const CameraSensorHelper *sensorHelper_; /// sensor辅助对象外部保证生命周期有效 }; } /* namespace ipa */ } /* namespace libcamera */三、实现文件 exposure_mode_helper.cpp 逐段解析构造函数ExposureModeHelper::ExposureModeHelper(const Spanstd::pairutils::Duration, double stages) : lineDuration_(1us), minExposureTime_(0us), maxExposureTime_(0us), minGain_(0), maxGain_(0), sensorHelper_(nullptr) { for (const auto [s, g] : stages) { exposureTimes_.push_back(s); gains_.push_back(g); } }将传入的 stage 数组拆分为两个并行 vector 保存。注意此时不做硬件钳位钳位、量化发生在splitExposure运行时。configure()void ExposureModeHelper::configure(utils::Duration lineDuration, const CameraSensorHelper *sensorHelper) { lineDuration_ lineDuration; sensorHelper_ sensorHelper; }设置 sensor 行周期、sensorHelper 裸指针。若不调用默认曝光单位为微秒增益不做硬件量化。⚠️只保存指针不管理对象生命周期调用方必须保证 sensorHelper 在使用期间有效。setLimits()void ExposureModeHelper::setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime, double minGain, double maxGain) { minExposureTime_ minExposureTime; maxExposureTime_ maxExposureTime; minGain_ minGain; maxGain_ maxGain; }设置硬件运行边界。固定曝光minExposureTime maxExposureTime固定模拟增益minGain maxGain未调用该函数时maxExposureTime_/maxGain_为 0调用splitExposure会触发ASSERT崩溃。clampExposureTime 曝光时间钳位与量化utils::Duration ExposureModeHelper::clampExposureTime(utils::Duration exposureTime, double *quantizationGain) const { utils::Duration clamped; utils::Duration exp; // 第一步钳位到硬件最大最小曝光 clamped std::clamp(exposureTime, minExposureTime_, maxExposureTime_); // 第二步对齐行周期时长除以行周期取long向零截断正数等价向下取整 exp static_castlong(clamped / lineDuration_) * lineDuration_; // 量化补偿系数 理想值 / 硬件实际可设置值exp ≤ clamped → quantGain ≥ 1 if (quantizationGain) *quantizationGain clamped / exp; return exp; }示例理想曝光 105us行周期 10us → exp100usquantizationGain 105/100 1.05。clampGain 模拟增益钳位与量化double ExposureModeHelper::clampGain(double gain, double *quantizationGain) const { // 钳位到硬件模拟增益上下限 double clamped std::clamp(gain, minGain_, maxGain_); if (sensorHelper_) // sensorHelper完成离散增益档位量化输出量化补偿 return sensorHelper_-quantizeGain(clamped, quantizationGain); if (quantizationGain) *quantizationGain 1.0; // 无量化损失补偿系数为1 return clamped; }stage 传入的总增益上限会经过本函数被硬件模拟增益上限截断超出部分无法由模拟增益实现最后落到 digitalGain。核心函数 splitExposure输入exposure目标总曝光量 返回 tuple(exposureTime, analogue_gain, quantization_gain, digital_gain)严格正确总曝光等式exposureTime_hw下发 sensor 的曝光时间对齐行周期gain_ana_hw下发 sensor 的模拟增益量化后档位quantGain曝光 模拟增益带来的总量化补偿gain_digitalISP 配置的数字增益std::tupleutils::Duration, double, double, double ExposureModeHelper::splitExposure(utils::Duration exposure) const { ASSERT(maxExposureTime_); ASSERT(maxGain_); utils::Duration exposureTime; double gain; double quantGain; double quantGain2; bool gainFixed minGain_ maxGain_; bool exposureTimeFixed minExposureTime_ maxExposureTime_; // 分支1硬件层面曝光时间和增益全部锁死不可调节 if (exposureTimeFixed gainFixed) { exposureTime clampExposureTime(minExposureTime_, quantGain); gain clampGain(minGain_, quantGain2); quantGain * quantGain2; return { exposureTime, gain, quantGain, exposure / (exposureTime * gain * quantGain) }; } double stageGain clampGain(1.0); double lastStageGain stageGain; // 上一阶段经过钳位后的总增益上限初始为1.0 // 分支2遍历所有stage做分阶段曝光拆分 for (unsigned int stage 0; stage gains_.size(); stage) { utils::Duration stageExposureTime clampExposureTime(exposureTimes_[stage], quantGain); stageGain clampGain(gains_[stage]); // a维持上一阶段增益上限lastStageGain不提升曝光最多拉到本阶段stageExposureTime // 条件成立仅靠【不提升增益 曝光最高到本阶段上限】就可以满足目标曝光 if (stageExposureTime * lastStageGain exposure) { exposureTime clampExposureTime(exposure / lastStageGain, quantGain); gain clampGain(exposure / exposureTime, quantGain2); quantGain * quantGain2; return { exposureTime, gain, quantGain, exposure / (exposureTime * gain * quantGain) }; } // ba条件不成立允许增益提升到本阶段stageGain曝光钉死本阶段最大曝光 // 条件成立本阶段最大曝光 本阶段最大增益可满足目标曝光 if (stageExposureTime * stageGain exposure) { exposureTime stageExposureTime; gain clampGain(exposure / exposureTime, quantGain2); quantGain * quantGain2; return { exposureTime, gain, quantGain, exposure / (exposureTime * gain * quantGain) }; } // ca、b都不成立本stage即使曝光、增益全部拉满依旧达不到目标曝光 // 更新上阶段增益进入下一轮stage lastStageGain stageGain; } // 分支3全部stage遍历完毕进入兜底逻辑 // stageGain为最后一个stage经过钳位后的增益上限stages为空时for不执行stageGain1.0 exposureTime clampExposureTime(exposure / stageGain, quantGain); gain clampGain(exposure / exposureTime, quantGain2); quantGain * quantGain2; return { exposureTime, gain, quantGain, exposure / (exposureTime * gain * quantGain) }; }四、算法完整流程文字版前置断言maxExposureTime_、maxGain_必须非 0如果硬件曝光、模拟增益同时被锁死直接计算硬件可设置值缺口全部由数字增益补齐返回初始化lastStageGain clampGain(1.0)遍历每一个 stage将当前 stage 配置的最大曝光、最大总增益做硬件钳位、量化得到stageExposureTime、stageGain判断stageExposureTime * lastStageGain exposure✅成立不提升增益维持上阶段增益上限反求需要的曝光时间硬件钳位量化微调增益直接返回结果。关键点最终曝光不需要等于 stageExposureTime反求得到更小的曝光时间。上一步不成立判断stageExposureTime * stageGain exposure✅成立曝光固定为本阶段最大曝光stageExposureTime允许增益提升到本阶段上限反求增益返回结果。两个条件均不成立本 stage 能力不足以满足目标曝光更新lastStageGain stageGain进入下一 stage所有 stage 执行完毕执行兜底使用最后 stage 的增益上限stageGain做基准计算曝光时间与增益特殊情况stages 为空for 循环不执行stageGain1.0此时行为等价传统 AE优先最大化曝光时间之后增加模拟增益剩余缺口交给数字增益。返回四元组。五、代码实现exposure_mode_helper.h实现/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2024, Paul Elder paul.elderideasonboard.com * * Helper class that performs computations relating to exposure */ #pragma once #include tuple #include utility #include vector #include libcamera/base/span.h #include libcamera/base/utils.h #include camera_sensor_helper.h namespace libcamera { namespace ipa { class ExposureModeHelper { public: ExposureModeHelper(const Spanstd::pairutils::Duration, double stages); ~ExposureModeHelper() default; void configure(utils::Duration lineLength, const CameraSensorHelper *sensorHelper); void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime, double minGain, double maxGain); std::tupleutils::Duration, double, double, double splitExposure(utils::Duration exposure) const; utils::Duration minExposureTime() const { return minExposureTime_; } utils::Duration maxExposureTime() const { return maxExposureTime_; } double minGain() const { return minGain_; } double maxGain() const { return maxGain_; } private: utils::Duration clampExposureTime(utils::Duration exposureTime, double *quantizationGain nullptr) const; double clampGain(double gain, double *quantizationGain nullptr) const; std::vectorutils::Duration exposureTimes_; std::vectordouble gains_; utils::Duration lineDuration_; utils::Duration minExposureTime_; utils::Duration maxExposureTime_; double minGain_; double maxGain_; const CameraSensorHelper *sensorHelper_; }; } /* namespace ipa */ } /* namespace libcamera */exposure_mode_helper.c实现/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2024, Paul Elder paul.elderideasonboard.com * * Helper class that performs computations relating to exposure */ #include exposure_mode_helper.h #include algorithm #include libcamera/base/log.h /** * \file exposure_mode_helper.h * \brief Helper class that performs computations relating to exposure * * AEGC algorithms have a need to split exposure between exposure time, analogue * and digital gain. Multiple implementations do so based on paired stages of * exposure time and gain limits; provide a helper to avoid duplicating the code. */ namespace libcamera { using namespace std::literals::chrono_literals; LOG_DEFINE_CATEGORY(ExposureModeHelper) namespace ipa { /** * \class ExposureModeHelper * \brief Class for splitting exposure into exposure time and total gain * * The ExposureModeHelper class provides a standard interface through which an * AEGC algorithm can divide exposure between exposure time and gain. It is * configured with a set of exposure time and gain pairs and works by initially * fixing gain at 1.0 and increasing exposure time up to the exposure time value * from the first pair in the set in an attempt to meet the required exposure * value. * * If the required exposure is not achievable by the first exposure time value * alone it ramps gain up to the value from the first pair in the set. If the * required exposure is still not met it then allows exposure time to ramp up to * the exposure time value from the second pair in the set, and continues in this * vein until either the required exposure time is met, or else the hardwares * exposure time or gain limits are reached. * * This method allows users to strike a balance between a well-exposed image and * an acceptable frame-rate, as opposed to simply maximising exposure time * followed by gain. The same helpers can be used to perform the latter * operation if needed by passing an empty set of pairs to the initialisation * function. * * The gain values may exceed a camera sensors analogue gain limits if either * it or the IPA is also capable of digital gain. The configure() function must * be called with the hardwares limits to inform the helper of those * constraints. Any gain that is needed will be applied as analogue gain first * until the hardwares limit is reached, following which digital gain will be * used. */ /** * \brief Construct an ExposureModeHelper instance * \param[in] stages The vector of paired exposure time and gain limits * * The input stages are exposure time and _total_ gain pairs; the gain * encompasses both analogue and digital gain. * * The vector of stages may be empty. In that case, the helper will simply use * the runtime limits set through setLimits() instead. */ ExposureModeHelper::ExposureModeHelper(const Spanstd::pairutils::Duration, double stages) : lineDuration_(1us), minExposureTime_(0us), maxExposureTime_(0us), minGain_(0), maxGain_(0), sensorHelper_(nullptr) { for (const auto [s, g] : stages) { exposureTimes_.push_back(s); gains_.push_back(g); } } /** * \brief Configure sensor details * \param[in] lineDuration The current line length of the sensor * \param[in] sensorHelper The sensor helper * * This function sets the line length and sensor helper. These are used in * splitExposure() to take the quantization of the exposure and gain into * account. * * When this has not been called, it is assumed that exposure is in micro second * granularity and gain has no quantization at all. * * ExposureModeHelper keeps a pointer to the CameraSensorHelper, so the caller * has to ensure that sensorHelper is valid until the next call to configure(). */ void ExposureModeHelper::configure(utils::Duration lineDuration, const CameraSensorHelper *sensorHelper) { lineDuration_ lineDuration; sensorHelper_ sensorHelper; } /** * \brief Set the exposure time and gain limits * \param[in] minExposureTime The minimum exposure time supported * \param[in] maxExposureTime The maximum exposure time supported * \param[in] minGain The minimum analogue gain supported * \param[in] maxGain The maximum analogue gain supported * * This function configures the exposure time and analogue gain limits that need * to be adhered to as the helper divides up exposure. Note that this function * *must* be called whenever those limits change and before splitExposure() is * used. * * If the algorithm using the helpers needs to indicate that either exposure time * or analogue gain or both should be fixed it can do so by setting both the * minima and maxima to the same value. */ void ExposureModeHelper::setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime, double minGain, double maxGain) { minExposureTime_ minExposureTime; maxExposureTime_ maxExposureTime; minGain_ minGain; maxGain_ maxGain; } utils::Duration ExposureModeHelper::clampExposureTime(utils::Duration exposureTime, double *quantizationGain) const { utils::Duration clamped; utils::Duration exp; clamped std::clamp(exposureTime, minExposureTime_, maxExposureTime_); exp static_castlong(clamped / lineDuration_) * lineDuration_; if (quantizationGain) *quantizationGain clamped / exp; return exp; } double ExposureModeHelper::clampGain(double gain, double *quantizationGain) const { double clamped std::clamp(gain, minGain_, maxGain_); if (sensorHelper_) return sensorHelper_-quantizeGain(clamped, quantizationGain); if (quantizationGain) *quantizationGain 1.0; return clamped; } /** * \brief Split exposure into exposure time and gain * \param[in] exposure Exposure value * * This function divides a given exposure into exposure time, analogue and * digital gain by iterating through stages of exposure time and gain limits. * At each stage the current stages exposure time limit is multiplied by the * previous stages gain limit (or 1.0 initially) to see if the combination of * the two can meet the required exposure. If they cannot then the current * stages exposure time limit is multiplied by the same stages gain limit to * see if that combination can meet the required exposure time. If they cannot * then the function moves to consider the next stage. * * When a combination of exposure time and gain _stage_ limits are found that * are sufficient to meet the required exposure, the function attempts to reduce * exposure time as much as possible whilst fixing gain and still meeting the * exposure. If a _runtime_ limit prevents exposure time from being lowered * enough to meet the exposure with gain fixed at the stage limit, gain is also * lowered to compensate. * * Once the exposure time and gain values are ascertained, gain is assigned as * analogue gain as much as possible, with digital gain only in use if the * maximum analogue gain runtime limit is unable to accommodate the exposure * value. * * If no combination of exposure time and gain limits is found that meets the * required exposure, the helper falls-back to simply maximising the exposure * time first, followed by analogue gain, followed by digital gain. * * During the calculations the gain missed due to quantization is recorded and * returned as quantization gain. The quantization gain is not included in the * digital gain. So to exactly apply the given exposure, both quantization gain * and digital gain must be applied. * * \return Tuple of exposure time, analogue gain, quantization gain and digital * gain */ std::tupleutils::Duration, double, double, double ExposureModeHelper::splitExposure(utils::Duration exposure) const { ASSERT(maxExposureTime_); ASSERT(maxGain_); utils::Duration exposureTime; double gain; double quantGain; double quantGain2; bool gainFixed minGain_ maxGain_; bool exposureTimeFixed minExposureTime_ maxExposureTime_; /* * Theres no point entering the loop if we cannot change either gain * nor exposure time anyway. */ if (exposureTimeFixed gainFixed) { exposureTime clampExposureTime(minExposureTime_, quantGain); gain clampGain(minGain_, quantGain2); quantGain * quantGain2; return { exposureTime, gain, quantGain, exposure / (exposureTime * gain * quantGain) }; } double stageGain clampGain(1.0); double lastStageGain stageGain; for (unsigned int stage 0; stage gains_.size(); stage) { utils::Duration stageExposureTime clampExposureTime(exposureTimes_[stage], quantGain); stageGain clampGain(gains_[stage]); /* * We perform the clamping on both exposure time and gain in * case the helper has had limits set that prevent those values * being lowered beyond a certain minimum...this can happen at * runtime for various reasons and so would not be known when * the stage limits are initialised. */ /* Clamp the gain to lastStageGain and regulate exposureTime. */ if (stageExposureTime * lastStageGain exposure) { exposureTime clampExposureTime(exposure / lastStageGain, quantGain); gain clampGain(exposure / exposureTime, quantGain2); quantGain * quantGain2; return { exposureTime, gain, quantGain, exposure / (exposureTime * gain * quantGain) }; } /* Clamp the exposureTime to stageExposureTime and regulate gain. */ if (stageExposureTime * stageGain exposure) { exposureTime stageExposureTime; gain clampGain(exposure / exposureTime, quantGain2); quantGain * quantGain2; return { exposureTime, gain, quantGain, exposure / (exposureTime * gain * quantGain) }; } lastStageGain stageGain; } /* * From here on all we can do is max out the exposure time, followed by * the analogue gain. If we still havent achieved the target we send * the rest of the exposure time to digital gain. If we were given no * stages to use then the default stageGain of 1.0 is used so that * exposure time is maxed before gain is touched at all. */ exposureTime clampExposureTime(exposure / stageGain, quantGain); gain clampGain(exposure / exposureTime, quantGain2); quantGain * quantGain2; return { exposureTime, gain, quantGain, exposure / (exposureTime * gain * quantGain) }; } /** * \fn ExposureModeHelper::minExposureTime() * \brief Retrieve the configured minimum exposure time limit set through * setLimits() * \return The minExposureTime_ value */ /** * \fn ExposureModeHelper::maxExposureTime() * \brief Retrieve the configured maximum exposure time set through setLimits() * \return The maxExposureTime_ value */ /** * \fn ExposureModeHelper::minGain() * \brief Retrieve the configured minimum gain set through setLimits() * \return The minGain_ value */ /** * \fn ExposureModeHelper::maxGain() * \brief Retrieve the configured maximum gain set through setLimits() * \return The maxGain_ value */ } /* namespace ipa */ } /* namespace libcamera */