summaryrefslogtreecommitdiff
path: root/src/ipa/rpi/controller/rpi/agc.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ipa/rpi/controller/rpi/agc.cpp')
-rw-r--r--src/ipa/rpi/controller/rpi/agc.cpp912
1 files changed, 114 insertions, 798 deletions
diff --git a/src/ipa/rpi/controller/rpi/agc.cpp b/src/ipa/rpi/controller/rpi/agc.cpp
index 7b02972a..598fc890 100644
--- a/src/ipa/rpi/controller/rpi/agc.cpp
+++ b/src/ipa/rpi/controller/rpi/agc.cpp
@@ -5,20 +5,12 @@
* agc.cpp - AGC/AEC control algorithm
*/
-#include <algorithm>
-#include <map>
-#include <tuple>
+#include "agc.h"
#include <libcamera/base/log.h>
-#include "../awb_status.h"
-#include "../device_status.h"
-#include "../histogram.h"
-#include "../lux_status.h"
#include "../metadata.h"
-#include "agc.h"
-
using namespace RPiController;
using namespace libcamera;
using libcamera::utils::Duration;
@@ -28,881 +20,205 @@ LOG_DEFINE_CATEGORY(RPiAgc)
#define NAME "rpi.agc"
-int AgcMeteringMode::read(const libcamera::YamlObject &params)
+Agc::Agc(Controller *controller)
+ : AgcAlgorithm(controller),
+ activeChannels_({ 0 })
{
- const YamlObject &yamlWeights = params["weights"];
-
- for (const auto &p : yamlWeights.asList()) {
- auto value = p.get<double>();
- if (!value)
- return -EINVAL;
- weights.push_back(*value);
- }
-
- return 0;
}
-static std::tuple<int, std::string>
-readMeteringModes(std::map<std::string, AgcMeteringMode> &metering_modes,
- const libcamera::YamlObject &params)
+char const *Agc::name() const
{
- std::string first;
- int ret;
-
- for (const auto &[key, value] : params.asDict()) {
- AgcMeteringMode meteringMode;
- ret = meteringMode.read(value);
- if (ret)
- return { ret, {} };
-
- metering_modes[key] = std::move(meteringMode);
- if (first.empty())
- first = key;
- }
-
- return { 0, first };
+ return NAME;
}
-int AgcExposureMode::read(const libcamera::YamlObject &params)
+int Agc::read(const libcamera::YamlObject &params)
{
- auto value = params["shutter"].getList<double>();
- if (!value)
- return -EINVAL;
- std::transform(value->begin(), value->end(), std::back_inserter(shutter),
- [](double v) { return v * 1us; });
-
- value = params["gain"].getList<double>();
- if (!value)
- return -EINVAL;
- gain = std::move(*value);
-
- if (shutter.size() < 2 || gain.size() < 2) {
- LOG(RPiAgc, Error)
- << "AgcExposureMode: must have at least two entries in exposure profile";
- return -EINVAL;
- }
-
- if (shutter.size() != gain.size()) {
- LOG(RPiAgc, Error)
- << "AgcExposureMode: expect same number of exposure and gain entries in exposure profile";
- return -EINVAL;
+ /*
+ * When there is only a single channel we can read the old style syntax.
+ * Otherwise we expect a "channels" keyword followed by a list of configurations.
+ */
+ if (!params.contains("channels")) {
+ LOG(RPiAgc, Debug) << "Single channel only";
+ channelData_.emplace_back();
+ return channelData_.back().channel.read(params, getHardwareConfig());
}
- return 0;
-}
-
-static std::tuple<int, std::string>
-readExposureModes(std::map<std::string, AgcExposureMode> &exposureModes,
- const libcamera::YamlObject &params)
-{
- std::string first;
- int ret;
-
- for (const auto &[key, value] : params.asDict()) {
- AgcExposureMode exposureMode;
- ret = exposureMode.read(value);
+ const auto &channels = params["channels"].asList();
+ for (auto ch = channels.begin(); ch != channels.end(); ch++) {
+ LOG(RPiAgc, Debug) << "Read AGC channel";
+ channelData_.emplace_back();
+ int ret = channelData_.back().channel.read(*ch, getHardwareConfig());
if (ret)
- return { ret, {} };
-
- exposureModes[key] = std::move(exposureMode);
- if (first.empty())
- first = key;
+ return ret;
}
- return { 0, first };
-}
-
-int AgcConstraint::read(const libcamera::YamlObject &params)
-{
- std::string boundString = params["bound"].get<std::string>("");
- transform(boundString.begin(), boundString.end(),
- boundString.begin(), ::toupper);
- if (boundString != "UPPER" && boundString != "LOWER") {
- LOG(RPiAgc, Error) << "AGC constraint type should be UPPER or LOWER";
- return -EINVAL;
+ LOG(RPiAgc, Debug) << "Read " << channelData_.size() << " channel(s)";
+ if (channelData_.empty()) {
+ LOG(RPiAgc, Error) << "No AGC channels provided";
+ return -1;
}
- bound = boundString == "UPPER" ? Bound::UPPER : Bound::LOWER;
-
- auto value = params["q_lo"].get<double>();
- if (!value)
- return -EINVAL;
- qLo = *value;
-
- value = params["q_hi"].get<double>();
- if (!value)
- return -EINVAL;
- qHi = *value;
-
- return yTarget.read(params["y_target"]);
-}
-static std::tuple<int, AgcConstraintMode>
-readConstraintMode(const libcamera::YamlObject &params)
-{
- AgcConstraintMode mode;
- int ret;
-
- for (const auto &p : params.asList()) {
- AgcConstraint constraint;
- ret = constraint.read(p);
- if (ret)
- return { ret, {} };
-
- mode.push_back(std::move(constraint));
- }
-
- return { 0, mode };
+ return 0;
}
-static std::tuple<int, std::string>
-readConstraintModes(std::map<std::string, AgcConstraintMode> &constraintModes,
- const libcamera::YamlObject &params)
+int Agc::checkChannel(unsigned int channelIndex) const
{
- std::string first;
- int ret;
-
- for (const auto &[key, value] : params.asDict()) {
- std::tie(ret, constraintModes[key]) = readConstraintMode(value);
- if (ret)
- return { ret, {} };
-
- if (first.empty())
- first = key;
+ if (channelIndex >= channelData_.size()) {
+ LOG(RPiAgc, Warning) << "AGC channel " << channelIndex << " not available";
+ return -1;
}
- return { 0, first };
-}
-
-int AgcConfig::read(const libcamera::YamlObject &params)
-{
- LOG(RPiAgc, Debug) << "AgcConfig";
- int ret;
-
- std::tie(ret, defaultMeteringMode) =
- readMeteringModes(meteringModes, params["metering_modes"]);
- if (ret)
- return ret;
- std::tie(ret, defaultExposureMode) =
- readExposureModes(exposureModes, params["exposure_modes"]);
- if (ret)
- return ret;
- std::tie(ret, defaultConstraintMode) =
- readConstraintModes(constraintModes, params["constraint_modes"]);
- if (ret)
- return ret;
-
- ret = yTarget.read(params["y_target"]);
- if (ret)
- return ret;
-
- speed = params["speed"].get<double>(0.2);
- startupFrames = params["startup_frames"].get<uint16_t>(10);
- convergenceFrames = params["convergence_frames"].get<unsigned int>(6);
- fastReduceThreshold = params["fast_reduce_threshold"].get<double>(0.4);
- baseEv = params["base_ev"].get<double>(1.0);
-
- /* Start with quite a low value as ramping up is easier than ramping down. */
- defaultExposureTime = params["default_exposure_time"].get<double>(1000) * 1us;
- defaultAnalogueGain = params["default_analogue_gain"].get<double>(1.0);
-
return 0;
}
-Agc::ExposureValues::ExposureValues()
- : shutter(0s), analogueGain(0),
- totalExposure(0s), totalExposureNoDG(0s)
+void Agc::disableAuto(unsigned int channelIndex)
{
-}
-
-Agc::Agc(Controller *controller)
- : AgcAlgorithm(controller), meteringMode_(nullptr),
- exposureMode_(nullptr), constraintMode_(nullptr),
- frameCount_(0), lockCount_(0),
- lastTargetExposure_(0s), ev_(1.0), flickerPeriod_(0s),
- maxShutter_(0s), fixedShutter_(0s), fixedAnalogueGain_(0.0)
-{
- memset(&awb_, 0, sizeof(awb_));
- /*
- * Setting status_.totalExposureValue_ to zero initially tells us
- * it's not been calculated yet (i.e. Process hasn't yet run).
- */
- status_ = {};
- status_.ev = ev_;
-}
+ if (checkChannel(channelIndex))
+ return;
-char const *Agc::name() const
-{
- return NAME;
+ LOG(RPiAgc, Debug) << "disableAuto for channel " << channelIndex;
+ channelData_[channelIndex].channel.disableAuto();
}
-int Agc::read(const libcamera::YamlObject &params)
+void Agc::enableAuto(unsigned int channelIndex)
{
- LOG(RPiAgc, Debug) << "Agc";
-
- int ret = config_.read(params);
- if (ret)
- return ret;
-
- const Size &size = getHardwareConfig().agcZoneWeights;
- for (auto const &modes : config_.meteringModes) {
- if (modes.second.weights.size() != size.width * size.height) {
- LOG(RPiAgc, Error) << "AgcMeteringMode: Incorrect number of weights";
- return -EINVAL;
- }
- }
+ if (checkChannel(channelIndex))
+ return;
- /*
- * Set the config's defaults (which are the first ones it read) as our
- * current modes, until someone changes them. (they're all known to
- * exist at this point)
- */
- meteringModeName_ = config_.defaultMeteringMode;
- meteringMode_ = &config_.meteringModes[meteringModeName_];
- exposureModeName_ = config_.defaultExposureMode;
- exposureMode_ = &config_.exposureModes[exposureModeName_];
- constraintModeName_ = config_.defaultConstraintMode;
- constraintMode_ = &config_.constraintModes[constraintModeName_];
- /* Set up the "last shutter/gain" values, in case AGC starts "disabled". */
- status_.shutterTime = config_.defaultExposureTime;
- status_.analogueGain = config_.defaultAnalogueGain;
- return 0;
-}
-
-void Agc::disableAuto()
-{
- fixedShutter_ = status_.shutterTime;
- fixedAnalogueGain_ = status_.analogueGain;
-}
-
-void Agc::enableAuto()
-{
- fixedShutter_ = 0s;
- fixedAnalogueGain_ = 0;
+ LOG(RPiAgc, Debug) << "enableAuto for channel " << channelIndex;
+ channelData_[channelIndex].channel.enableAuto();
}
unsigned int Agc::getConvergenceFrames() const
{
- /*
- * If shutter and gain have been explicitly set, there is no
- * convergence to happen, so no need to drop any frames - return zero.
- */
- if (fixedShutter_ && fixedAnalogueGain_)
- return 0;
- else
- return config_.convergenceFrames;
+ /* If there are n channels, it presumably takes n times as long to converge. */
+ return channelData_[0].channel.getConvergenceFrames() * activeChannels_.size();
}
std::vector<double> const &Agc::getWeights() const
{
/*
- * In case someone calls setMeteringMode and then this before the
- * algorithm has run and updated the meteringMode_ pointer.
+ * In future the metering weights may be determined differently, making it
+ * difficult to associate different sets of weight with different channels.
+ * Therefore we shall impose a limitation, at least for now, that all
+ * channels will use the same weights.
*/
- auto it = config_.meteringModes.find(meteringModeName_);
- if (it == config_.meteringModes.end())
- return meteringMode_->weights;
- return it->second.weights;
+ return channelData_[0].channel.getWeights();
}
-void Agc::setEv(double ev)
+void Agc::setEv(unsigned int channelIndex, double ev)
{
- ev_ = ev;
-}
+ if (checkChannel(channelIndex))
+ return;
-void Agc::setFlickerPeriod(Duration flickerPeriod)
-{
- flickerPeriod_ = flickerPeriod;
+ LOG(RPiAgc, Debug) << "setEv " << ev << " for channel " << channelIndex;
+ channelData_[channelIndex].channel.setEv(ev);
}
-void Agc::setMaxShutter(Duration maxShutter)
+void Agc::setFlickerPeriod(unsigned int channelIndex, Duration flickerPeriod)
{
- maxShutter_ = maxShutter;
-}
+ if (checkChannel(channelIndex))
+ return;
-void Agc::setFixedShutter(Duration fixedShutter)
-{
- fixedShutter_ = fixedShutter;
- /* Set this in case someone calls disableAuto() straight after. */
- status_.shutterTime = limitShutter(fixedShutter_);
+ LOG(RPiAgc, Debug) << "setFlickerPeriod " << flickerPeriod
+ << " for channel " << channelIndex;
+ channelData_[channelIndex].channel.setFlickerPeriod(flickerPeriod);
}
-void Agc::setFixedAnalogueGain(double fixedAnalogueGain)
-{
- fixedAnalogueGain_ = fixedAnalogueGain;
- /* Set this in case someone calls disableAuto() straight after. */
- status_.analogueGain = limitGain(fixedAnalogueGain);
-}
-
-void Agc::setMeteringMode(std::string const &meteringModeName)
-{
- meteringModeName_ = meteringModeName;
-}
-
-void Agc::setExposureMode(std::string const &exposureModeName)
-{
- exposureModeName_ = exposureModeName;
-}
-
-void Agc::setConstraintMode(std::string const &constraintModeName)
-{
- constraintModeName_ = constraintModeName;
-}
-
-void Agc::switchMode(CameraMode const &cameraMode,
- Metadata *metadata)
+void Agc::setMaxShutter(Duration maxShutter)
{
- /* AGC expects the mode sensitivity always to be non-zero. */
- ASSERT(cameraMode.sensitivity);
-
- housekeepConfig();
-
- /*
- * Store the mode in the local state. We must cache the sensitivity of
- * of the previous mode for the calculations below.
- */
- double lastSensitivity = mode_.sensitivity;
- mode_ = cameraMode;
-
- Duration fixedShutter = limitShutter(fixedShutter_);
- if (fixedShutter && fixedAnalogueGain_) {
- /* We're going to reset the algorithm here with these fixed values. */
-
- fetchAwbStatus(metadata);
- double minColourGain = std::min({ awb_.gainR, awb_.gainG, awb_.gainB, 1.0 });
- ASSERT(minColourGain != 0.0);
-
- /* This is the equivalent of computeTargetExposure and applyDigitalGain. */
- target_.totalExposureNoDG = fixedShutter_ * fixedAnalogueGain_;
- target_.totalExposure = target_.totalExposureNoDG / minColourGain;
-
- /* Equivalent of filterExposure. This resets any "history". */
- filtered_ = target_;
-
- /* Equivalent of divideUpExposure. */
- filtered_.shutter = fixedShutter;
- filtered_.analogueGain = fixedAnalogueGain_;
- } else if (status_.totalExposureValue) {
- /*
- * On a mode switch, various things could happen:
- * - the exposure profile might change
- * - a fixed exposure or gain might be set
- * - the new mode's sensitivity might be different
- * We cope with the last of these by scaling the target values. After
- * that we just need to re-divide the exposure/gain according to the
- * current exposure profile, which takes care of everything else.
- */
-
- double ratio = lastSensitivity / cameraMode.sensitivity;
- target_.totalExposureNoDG *= ratio;
- target_.totalExposure *= ratio;
- filtered_.totalExposureNoDG *= ratio;
- filtered_.totalExposure *= ratio;
-
- divideUpExposure();
- } else {
- /*
- * We come through here on startup, when at least one of the shutter
- * or gain has not been fixed. We must still write those values out so
- * that they will be applied immediately. We supply some arbitrary defaults
- * for any that weren't set.
- */
-
- /* Equivalent of divideUpExposure. */
- filtered_.shutter = fixedShutter ? fixedShutter : config_.defaultExposureTime;
- filtered_.analogueGain = fixedAnalogueGain_ ? fixedAnalogueGain_ : config_.defaultAnalogueGain;
- }
-
- writeAndFinish(metadata, false);
+ /* Frame durations will be the same across all channels too. */
+ for (auto &data : channelData_)
+ data.channel.setMaxShutter(maxShutter);
}
-void Agc::prepare(Metadata *imageMetadata)
+void Agc::setFixedShutter(unsigned int channelIndex, Duration fixedShutter)
{
- Duration totalExposureValue = status_.totalExposureValue;
- AgcStatus delayedStatus;
- AgcPrepareStatus prepareStatus;
-
- if (!imageMetadata->get("agc.delayed_status", delayedStatus))
- totalExposureValue = delayedStatus.totalExposureValue;
-
- prepareStatus.digitalGain = 1.0;
- prepareStatus.locked = false;
-
- if (status_.totalExposureValue) {
- /* Process has run, so we have meaningful values. */
- DeviceStatus deviceStatus;
- if (imageMetadata->get("device.status", deviceStatus) == 0) {
- Duration actualExposure = deviceStatus.shutterSpeed *
- deviceStatus.analogueGain;
- if (actualExposure) {
- double digitalGain = totalExposureValue / actualExposure;
- LOG(RPiAgc, Debug) << "Want total exposure " << totalExposureValue;
- /*
- * Never ask for a gain < 1.0, and also impose
- * some upper limit. Make it customisable?
- */
- prepareStatus.digitalGain = std::max(1.0, std::min(digitalGain, 4.0));
- LOG(RPiAgc, Debug) << "Actual exposure " << actualExposure;
- LOG(RPiAgc, Debug) << "Use digitalGain " << prepareStatus.digitalGain;
- LOG(RPiAgc, Debug) << "Effective exposure "
- << actualExposure * prepareStatus.digitalGain;
- /* Decide whether AEC/AGC has converged. */
- prepareStatus.locked = updateLockStatus(deviceStatus);
- }
- } else
- LOG(RPiAgc, Warning) << name() << ": no device metadata";
- imageMetadata->set("agc.prepare_status", prepareStatus);
- }
-}
+ if (checkChannel(channelIndex))
+ return;
-void Agc::process(StatisticsPtr &stats, Metadata *imageMetadata)
-{
- frameCount_++;
- /*
- * First a little bit of housekeeping, fetching up-to-date settings and
- * configuration, that kind of thing.
- */
- housekeepConfig();
- /* Fetch the AWB status immediately, so that we can assume it's there. */
- fetchAwbStatus(imageMetadata);
- /* Get the current exposure values for the frame that's just arrived. */
- fetchCurrentExposure(imageMetadata);
- /* Compute the total gain we require relative to the current exposure. */
- double gain, targetY;
- computeGain(stats, imageMetadata, gain, targetY);
- /* Now compute the target (final) exposure which we think we want. */
- computeTargetExposure(gain);
- /* The results have to be filtered so as not to change too rapidly. */
- filterExposure();
- /*
- * Some of the exposure has to be applied as digital gain, so work out
- * what that is. This function also tells us whether it's decided to
- * "desaturate" the image more quickly.
- */
- bool desaturate = applyDigitalGain(gain, targetY);
- /*
- * The last thing is to divide up the exposure value into a shutter time
- * and analogue gain, according to the current exposure mode.
- */
- divideUpExposure();
- /* Finally advertise what we've done. */
- writeAndFinish(imageMetadata, desaturate);
+ LOG(RPiAgc, Debug) << "setFixedShutter " << fixedShutter
+ << " for channel " << channelIndex;
+ channelData_[channelIndex].channel.setFixedShutter(fixedShutter);
}
-bool Agc::updateLockStatus(DeviceStatus const &deviceStatus)
+void Agc::setFixedAnalogueGain(unsigned int channelIndex, double fixedAnalogueGain)
{
- const double errorFactor = 0.10; /* make these customisable? */
- const int maxLockCount = 5;
- /* Reset "lock count" when we exceed this multiple of errorFactor */
- const double resetMargin = 1.5;
+ if (checkChannel(channelIndex))
+ return;
- /* Add 200us to the exposure time error to allow for line quantisation. */
- Duration exposureError = lastDeviceStatus_.shutterSpeed * errorFactor + 200us;
- double gainError = lastDeviceStatus_.analogueGain * errorFactor;
- Duration targetError = lastTargetExposure_ * errorFactor;
-
- /*
- * Note that we don't know the exposure/gain limits of the sensor, so
- * the values we keep requesting may be unachievable. For this reason
- * we only insist that we're close to values in the past few frames.
- */
- if (deviceStatus.shutterSpeed > lastDeviceStatus_.shutterSpeed - exposureError &&
- deviceStatus.shutterSpeed < lastDeviceStatus_.shutterSpeed + exposureError &&
- deviceStatus.analogueGain > lastDeviceStatus_.analogueGain - gainError &&
- deviceStatus.analogueGain < lastDeviceStatus_.analogueGain + gainError &&
- status_.targetExposureValue > lastTargetExposure_ - targetError &&
- status_.targetExposureValue < lastTargetExposure_ + targetError)
- lockCount_ = std::min(lockCount_ + 1, maxLockCount);
- else if (deviceStatus.shutterSpeed < lastDeviceStatus_.shutterSpeed - resetMargin * exposureError ||
- deviceStatus.shutterSpeed > lastDeviceStatus_.shutterSpeed + resetMargin * exposureError ||
- deviceStatus.analogueGain < lastDeviceStatus_.analogueGain - resetMargin * gainError ||
- deviceStatus.analogueGain > lastDeviceStatus_.analogueGain + resetMargin * gainError ||
- status_.targetExposureValue < lastTargetExposure_ - resetMargin * targetError ||
- status_.targetExposureValue > lastTargetExposure_ + resetMargin * targetError)
- lockCount_ = 0;
-
- lastDeviceStatus_ = deviceStatus;
- lastTargetExposure_ = status_.targetExposureValue;
-
- LOG(RPiAgc, Debug) << "Lock count updated to " << lockCount_;
- return lockCount_ == maxLockCount;
+ LOG(RPiAgc, Debug) << "setFixedAnalogueGain " << fixedAnalogueGain
+ << " for channel " << channelIndex;
+ channelData_[channelIndex].channel.setFixedAnalogueGain(fixedAnalogueGain);
}
-void Agc::housekeepConfig()
+void Agc::setMeteringMode(std::string const &meteringModeName)
{
- /* First fetch all the up-to-date settings, so no one else has to do it. */
- status_.ev = ev_;
- status_.fixedShutter = limitShutter(fixedShutter_);
- status_.fixedAnalogueGain = fixedAnalogueGain_;
- status_.flickerPeriod = flickerPeriod_;
- LOG(RPiAgc, Debug) << "ev " << status_.ev << " fixedShutter "
- << status_.fixedShutter << " fixedAnalogueGain "
- << status_.fixedAnalogueGain;
- /*
- * Make sure the "mode" pointers point to the up-to-date things, if
- * they've changed.
- */
- if (meteringModeName_ != status_.meteringMode) {
- auto it = config_.meteringModes.find(meteringModeName_);
- if (it == config_.meteringModes.end()) {
- LOG(RPiAgc, Warning) << "No metering mode " << meteringModeName_;
- meteringModeName_ = status_.meteringMode;
- } else {
- meteringMode_ = &it->second;
- status_.meteringMode = meteringModeName_;
- }
- }
- if (exposureModeName_ != status_.exposureMode) {
- auto it = config_.exposureModes.find(exposureModeName_);
- if (it == config_.exposureModes.end()) {
- LOG(RPiAgc, Warning) << "No exposure profile " << exposureModeName_;
- exposureModeName_ = status_.exposureMode;
- } else {
- exposureMode_ = &it->second;
- status_.exposureMode = exposureModeName_;
- }
- }
- if (constraintModeName_ != status_.constraintMode) {
- auto it = config_.constraintModes.find(constraintModeName_);
- if (it == config_.constraintModes.end()) {
- LOG(RPiAgc, Warning) << "No constraint list " << constraintModeName_;
- constraintModeName_ = status_.constraintMode;
- } else {
- constraintMode_ = &it->second;
- status_.constraintMode = constraintModeName_;
- }
- }
- LOG(RPiAgc, Debug) << "exposureMode "
- << exposureModeName_ << " constraintMode "
- << constraintModeName_ << " meteringMode "
- << meteringModeName_;
+ /* Metering modes will be the same across all channels too. */
+ for (auto &data : channelData_)
+ data.channel.setMeteringMode(meteringModeName);
}
-void Agc::fetchCurrentExposure(Metadata *imageMetadata)
+void Agc::setExposureMode(unsigned int channelIndex, std::string const &exposureModeName)
{
- std::unique_lock<Metadata> lock(*imageMetadata);
- DeviceStatus *deviceStatus =
- imageMetadata->getLocked<DeviceStatus>("device.status");
- if (!deviceStatus)
- LOG(RPiAgc, Fatal) << "No device metadata";
- current_.shutter = deviceStatus->shutterSpeed;
- current_.analogueGain = deviceStatus->analogueGain;
- AgcStatus *agcStatus =
- imageMetadata->getLocked<AgcStatus>("agc.status");
- current_.totalExposure = agcStatus ? agcStatus->totalExposureValue : 0s;
- current_.totalExposureNoDG = current_.shutter * current_.analogueGain;
-}
+ if (checkChannel(channelIndex))
+ return;
-void Agc::fetchAwbStatus(Metadata *imageMetadata)
-{
- awb_.gainR = 1.0; /* in case not found in metadata */
- awb_.gainG = 1.0;
- awb_.gainB = 1.0;
- if (imageMetadata->get("awb.status", awb_) != 0)
- LOG(RPiAgc, Debug) << "No AWB status found";
+ LOG(RPiAgc, Debug) << "setExposureMode " << exposureModeName
+ << " for channel " << channelIndex;
+ channelData_[channelIndex].channel.setExposureMode(exposureModeName);
}
-static double computeInitialY(StatisticsPtr &stats, AwbStatus const &awb,
- std::vector<double> &weights, double gain)
+void Agc::setConstraintMode(unsigned int channelIndex, std::string const &constraintModeName)
{
- constexpr uint64_t maxVal = 1 << Statistics::NormalisationFactorPow2;
+ if (checkChannel(channelIndex))
+ return;
- ASSERT(weights.size() == stats->agcRegions.numRegions());
-
- /*
- * Note that the weights are applied by the IPA to the statistics directly,
- * before they are given to us here.
- */
- double rSum = 0, gSum = 0, bSum = 0, pixelSum = 0;
- for (unsigned int i = 0; i < stats->agcRegions.numRegions(); i++) {
- auto &region = stats->agcRegions.get(i);
- rSum += std::min<double>(region.val.rSum * gain, (maxVal - 1) * region.counted);
- gSum += std::min<double>(region.val.gSum * gain, (maxVal - 1) * region.counted);
- bSum += std::min<double>(region.val.bSum * gain, (maxVal - 1) * region.counted);
- pixelSum += region.counted;
- }
- if (pixelSum == 0.0) {
- LOG(RPiAgc, Warning) << "computeInitialY: pixelSum is zero";
- return 0;
- }
- double ySum = rSum * awb.gainR * .299 +
- gSum * awb.gainG * .587 +
- bSum * awb.gainB * .114;
- return ySum / pixelSum / maxVal;
+ channelData_[channelIndex].channel.setConstraintMode(constraintModeName);
}
-/*
- * We handle extra gain through EV by adjusting our Y targets. However, you
- * simply can't monitor histograms once they get very close to (or beyond!)
- * saturation, so we clamp the Y targets to this value. It does mean that EV
- * increases don't necessarily do quite what you might expect in certain
- * (contrived) cases.
- */
-
-static constexpr double EvGainYTargetLimit = 0.9;
-
-static double constraintComputeGain(AgcConstraint &c, const Histogram &h, double lux,
- double evGain, double &targetY)
+template<typename T>
+std::ostream &operator<<(std::ostream &os, const std::vector<T> &v)
{
- targetY = c.yTarget.eval(c.yTarget.domain().clip(lux));
- targetY = std::min(EvGainYTargetLimit, targetY * evGain);
- double iqm = h.interQuantileMean(c.qLo, c.qHi);
- return (targetY * h.bins()) / iqm;
+ os << "{";
+ for (const auto &e : v)
+ os << " " << e;
+ os << " }";
+ return os;
}
-void Agc::computeGain(StatisticsPtr &statistics, Metadata *imageMetadata,
- double &gain, double &targetY)
+void Agc::setActiveChannels(const std::vector<unsigned int> &activeChannels)
{
- struct LuxStatus lux = {};
- lux.lux = 400; /* default lux level to 400 in case no metadata found */
- if (imageMetadata->get("lux.status", lux) != 0)
- LOG(RPiAgc, Warning) << "No lux level found";
- const Histogram &h = statistics->yHist;
- double evGain = status_.ev * config_.baseEv;
- /*
- * The initial gain and target_Y come from some of the regions. After
- * that we consider the histogram constraints.
- */
- targetY = config_.yTarget.eval(config_.yTarget.domain().clip(lux.lux));
- targetY = std::min(EvGainYTargetLimit, targetY * evGain);
-
- /*
- * Do this calculation a few times as brightness increase can be
- * non-linear when there are saturated regions.
- */
- gain = 1.0;
- for (int i = 0; i < 8; i++) {
- double initialY = computeInitialY(statistics, awb_, meteringMode_->weights, gain);
- double extraGain = std::min(10.0, targetY / (initialY + .001));
- gain *= extraGain;
- LOG(RPiAgc, Debug) << "Initial Y " << initialY << " target " << targetY
- << " gives gain " << gain;
- if (extraGain < 1.01) /* close enough */
- break;
- }
-
- for (auto &c : *constraintMode_) {
- double newTargetY;
- double newGain = constraintComputeGain(c, h, lux.lux, evGain, newTargetY);
- LOG(RPiAgc, Debug) << "Constraint has target_Y "
- << newTargetY << " giving gain " << newGain;
- if (c.bound == AgcConstraint::Bound::LOWER && newGain > gain) {
- LOG(RPiAgc, Debug) << "Lower bound constraint adopted";
- gain = newGain;
- targetY = newTargetY;
- } else if (c.bound == AgcConstraint::Bound::UPPER && newGain < gain) {
- LOG(RPiAgc, Debug) << "Upper bound constraint adopted";
- gain = newGain;
- targetY = newTargetY;
- }
+ if (activeChannels.empty()) {
+ LOG(RPiAgc, Warning) << "No active AGC channels supplied";
+ return;
}
- LOG(RPiAgc, Debug) << "Final gain " << gain << " (target_Y " << targetY << " ev "
- << status_.ev << " base_ev " << config_.baseEv
- << ")";
-}
-
-void Agc::computeTargetExposure(double gain)
-{
- if (status_.fixedShutter && status_.fixedAnalogueGain) {
- /*
- * When ag and shutter are both fixed, we need to drive the
- * total exposure so that we end up with a digital gain of at least
- * 1/minColourGain. Otherwise we'd desaturate channels causing
- * white to go cyan or magenta.
- */
- double minColourGain = std::min({ awb_.gainR, awb_.gainG, awb_.gainB, 1.0 });
- ASSERT(minColourGain != 0.0);
- target_.totalExposure =
- status_.fixedShutter * status_.fixedAnalogueGain / minColourGain;
- } else {
- /*
- * The statistics reflect the image without digital gain, so the final
- * total exposure we're aiming for is:
- */
- target_.totalExposure = current_.totalExposureNoDG * gain;
- /* The final target exposure is also limited to what the exposure mode allows. */
- Duration maxShutter = status_.fixedShutter
- ? status_.fixedShutter
- : exposureMode_->shutter.back();
- maxShutter = limitShutter(maxShutter);
- Duration maxTotalExposure =
- maxShutter *
- (status_.fixedAnalogueGain != 0.0
- ? status_.fixedAnalogueGain
- : exposureMode_->gain.back());
- target_.totalExposure = std::min(target_.totalExposure, maxTotalExposure);
- }
- LOG(RPiAgc, Debug) << "Target totalExposure " << target_.totalExposure;
-}
-bool Agc::applyDigitalGain(double gain, double targetY)
-{
- double minColourGain = std::min({ awb_.gainR, awb_.gainG, awb_.gainB, 1.0 });
- ASSERT(minColourGain != 0.0);
- double dg = 1.0 / minColourGain;
- /*
- * I think this pipeline subtracts black level and rescales before we
- * get the stats, so no need to worry about it.
- */
- LOG(RPiAgc, Debug) << "after AWB, target dg " << dg << " gain " << gain
- << " target_Y " << targetY;
- /*
- * Finally, if we're trying to reduce exposure but the target_Y is
- * "close" to 1.0, then the gain computed for that constraint will be
- * only slightly less than one, because the measured Y can never be
- * larger than 1.0. When this happens, demand a large digital gain so
- * that the exposure can be reduced, de-saturating the image much more
- * quickly (and we then approach the correct value more quickly from
- * below).
- */
- bool desaturate = targetY > config_.fastReduceThreshold &&
- gain < sqrt(targetY);
- if (desaturate)
- dg /= config_.fastReduceThreshold;
- LOG(RPiAgc, Debug) << "Digital gain " << dg << " desaturate? " << desaturate;
- filtered_.totalExposureNoDG = filtered_.totalExposure / dg;
- LOG(RPiAgc, Debug) << "Target totalExposureNoDG " << filtered_.totalExposureNoDG;
- return desaturate;
-}
-
-void Agc::filterExposure()
-{
- double speed = config_.speed;
- /*
- * AGC adapts instantly if both shutter and gain are directly specified
- * or we're in the startup phase.
- */
- if ((status_.fixedShutter && status_.fixedAnalogueGain) ||
- frameCount_ <= config_.startupFrames)
- speed = 1.0;
- if (!filtered_.totalExposure) {
- filtered_.totalExposure = target_.totalExposure;
- } else {
- /*
- * If close to the result go faster, to save making so many
- * micro-adjustments on the way. (Make this customisable?)
- */
- if (filtered_.totalExposure < 1.2 * target_.totalExposure &&
- filtered_.totalExposure > 0.8 * target_.totalExposure)
- speed = sqrt(speed);
- filtered_.totalExposure = speed * target_.totalExposure +
- filtered_.totalExposure * (1.0 - speed);
- }
- LOG(RPiAgc, Debug) << "After filtering, totalExposure " << filtered_.totalExposure
- << " no dg " << filtered_.totalExposureNoDG;
-}
+ for (auto index : activeChannels)
+ if (checkChannel(index))
+ return;
-void Agc::divideUpExposure()
-{
- /*
- * Sending the fixed shutter/gain cases through the same code may seem
- * unnecessary, but it will make more sense when extend this to cover
- * variable aperture.
- */
- Duration exposureValue = filtered_.totalExposureNoDG;
- Duration shutterTime;
- double analogueGain;
- shutterTime = status_.fixedShutter ? status_.fixedShutter
- : exposureMode_->shutter[0];
- shutterTime = limitShutter(shutterTime);
- analogueGain = status_.fixedAnalogueGain != 0.0 ? status_.fixedAnalogueGain
- : exposureMode_->gain[0];
- analogueGain = limitGain(analogueGain);
- if (shutterTime * analogueGain < exposureValue) {
- for (unsigned int stage = 1;
- stage < exposureMode_->gain.size(); stage++) {
- if (!status_.fixedShutter) {
- Duration stageShutter =
- limitShutter(exposureMode_->shutter[stage]);
- if (stageShutter * analogueGain >= exposureValue) {
- shutterTime = exposureValue / analogueGain;
- break;
- }
- shutterTime = stageShutter;
- }
- if (status_.fixedAnalogueGain == 0.0) {
- if (exposureMode_->gain[stage] * shutterTime >= exposureValue) {
- analogueGain = exposureValue / shutterTime;
- break;
- }
- analogueGain = exposureMode_->gain[stage];
- analogueGain = limitGain(analogueGain);
- }
- }
- }
- LOG(RPiAgc, Debug) << "Divided up shutter and gain are " << shutterTime << " and "
- << analogueGain;
- /*
- * Finally adjust shutter time for flicker avoidance (require both
- * shutter and gain not to be fixed).
- */
- if (!status_.fixedShutter && !status_.fixedAnalogueGain &&
- status_.flickerPeriod) {
- int flickerPeriods = shutterTime / status_.flickerPeriod;
- if (flickerPeriods) {
- Duration newShutterTime = flickerPeriods * status_.flickerPeriod;
- analogueGain *= shutterTime / newShutterTime;
- /*
- * We should still not allow the ag to go over the
- * largest value in the exposure mode. Note that this
- * may force more of the total exposure into the digital
- * gain as a side-effect.
- */
- analogueGain = std::min(analogueGain, exposureMode_->gain.back());
- analogueGain = limitGain(analogueGain);
- shutterTime = newShutterTime;
- }
- LOG(RPiAgc, Debug) << "After flicker avoidance, shutter "
- << shutterTime << " gain " << analogueGain;
- }
- filtered_.shutter = shutterTime;
- filtered_.analogueGain = analogueGain;
+ LOG(RPiAgc, Debug) << "setActiveChannels " << activeChannels;
+ activeChannels_ = activeChannels;
}
-void Agc::writeAndFinish(Metadata *imageMetadata, bool desaturate)
+void Agc::switchMode(CameraMode const &cameraMode,
+ Metadata *metadata)
{
- status_.totalExposureValue = filtered_.totalExposure;
- status_.targetExposureValue = desaturate ? 0s : target_.totalExposureNoDG;
- status_.shutterTime = filtered_.shutter;
- status_.analogueGain = filtered_.analogueGain;
- /*
- * Write to metadata as well, in case anyone wants to update the camera
- * immediately.
- */
- imageMetadata->set("agc.status", status_);
- LOG(RPiAgc, Debug) << "Output written, total exposure requested is "
- << filtered_.totalExposure;
- LOG(RPiAgc, Debug) << "Camera exposure update: shutter time " << filtered_.shutter
- << " analogue gain " << filtered_.analogueGain;
+ LOG(RPiAgc, Debug) << "switchMode for channel 0";
+ channelData_[0].channel.switchMode(cameraMode, metadata);
}
-Duration Agc::limitShutter(Duration shutter)
+void Agc::prepare(Metadata *imageMetadata)
{
- /*
- * shutter == 0 is a special case for fixed shutter values, and must pass
- * through unchanged
- */
- if (!shutter)
- return shutter;
-
- shutter = std::clamp(shutter, mode_.minShutter, maxShutter_);
- return shutter;
+ LOG(RPiAgc, Debug) << "prepare for channel 0";
+ channelData_[0].channel.prepare(imageMetadata);
}
-double Agc::limitGain(double gain) const
+void Agc::process(StatisticsPtr &stats, Metadata *imageMetadata)
{
- /*
- * Only limit the lower bounds of the gain value to what the sensor limits.
- * The upper bound on analogue gain will be made up with additional digital
- * gain applied by the ISP.
- *
- * gain == 0.0 is a special case for fixed shutter values, and must pass
- * through unchanged
- */
- if (!gain)
- return gain;
-
- gain = std::max(gain, mode_.minAnalogueGain);
- return gain;
+ LOG(RPiAgc, Debug) << "process for channel 0";
+ channelData_[0].channel.process(stats, imageMetadata);
}
/* Register algorithm with the system. */