@@ -30,6 +30,7 @@ struct AgcStatus {
libcamera::utils::Duration targetExposureValue; /* (unfiltered) target total exposure AGC is aiming for */
libcamera::utils::Duration exposureTime;
double analogueGain;
+ double digitalGain;
std::string exposureMode;
std::string constraintMode;
std::string meteringMode;
@@ -266,7 +266,7 @@ int AgcConfig::read(const libcamera::YamlObject ¶ms)
}
AgcChannel::ExposureValues::ExposureValues()
- : exposureTime(0s), analogueGain(0),
+ : exposureTime(0s), analogueGain(0), digitalGain(0),
totalExposure(0s), totalExposureNoDG(0s)
{
}
@@ -434,17 +434,10 @@ void AgcChannel::switchMode(CameraMode const &cameraMode,
mode_ = cameraMode;
Duration fixedExposureTime = limitExposureTime(fixedExposureTime_);
+ double fixedGain = limitGain(fixedGain_);
if (fixedExposureTime && fixedGain_) {
- /* This is the equivalent of computeTargetExposure and applyDigitalGain. */
- target_.totalExposureNoDG = fixedExposureTime_ * fixedGain_;
- target_.totalExposure = target_.totalExposureNoDG;
-
- /* Equivalent of filterExposure. This resets any "history". */
- filtered_ = target_;
-
- /* Equivalent of divideUpExposure. */
- filtered_.exposureTime = fixedExposureTime;
- filtered_.analogueGain = fixedGain_;
+ filtered_.totalExposureNoDG = fixedExposureTime * fixedGain;
+ filtered_.totalExposure = filtered_.totalExposureNoDG;
} else if (status_.totalExposureValue) {
/*
* On a mode switch, various things could happen:
@@ -457,12 +450,8 @@ void AgcChannel::switchMode(CameraMode const &cameraMode,
*/
double ratio = lastSensitivity / cameraMode.sensitivity;
- target_.totalExposure *= ratio;
- target_.totalExposureNoDG = target_.totalExposure;
filtered_.totalExposure *= ratio;
filtered_.totalExposureNoDG = filtered_.totalExposure;
-
- divideUpExposure();
} else {
/*
* We come through here on startup, when at least one of the
@@ -472,55 +461,46 @@ void AgcChannel::switchMode(CameraMode const &cameraMode,
* weren't set.
*/
- /* Equivalent of divideUpExposure. */
- filtered_.exposureTime = fixedExposureTime ? fixedExposureTime : config_.defaultExposureTime;
- filtered_.analogueGain = fixedGain_ ? fixedGain_ : config_.defaultAnalogueGain;
+ Duration exposureTime = fixedExposureTime ? fixedExposureTime : config_.defaultExposureTime;
+ double gain = fixedGain ? fixedGain : config_.defaultAnalogueGain;
+ filtered_.totalExposure = exposureTime * gain;
+ filtered_.totalExposureNoDG = filtered_.totalExposure;
}
+ /* Setting target_ to filtered_ removes any history from before the mode switch. */
+ target_ = filtered_;
+ divideUpExposure();
+
writeAndFinish(metadata, false);
}
void AgcChannel::prepare(Metadata *imageMetadata)
{
- Duration totalExposureValue = status_.totalExposureValue;
- AgcStatus delayedStatus;
+ DeviceStatus deviceStatus;
AgcPrepareStatus prepareStatus;
- /* Fetch the AWB status now because AWB also sets it in the prepare method. */
- fetchAwbStatus(imageMetadata);
-
- if (!imageMetadata->get("agc.delayed_status", delayedStatus))
- totalExposureValue = delayedStatus.totalExposureValue;
-
- prepareStatus.digitalGain = 1.0;
prepareStatus.locked = false;
+ prepareStatus.digitalGain = 1.0;
- if (status_.totalExposureValue) {
- /* Process has run, so we have meaningful values. */
- DeviceStatus deviceStatus;
- if (imageMetadata->get("device.status", deviceStatus) == 0) {
- Duration actualExposure = deviceStatus.exposureTime *
- 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,
- config_.maxDigitalGain));
- 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) << "AgcChannel: no device metadata";
- imageMetadata->set("agc.prepare_status", prepareStatus);
+ if (!imageMetadata->get("device.status", deviceStatus)) {
+ prepareStatus.locked = updateLockStatus(deviceStatus);
+
+ /*
+ * For now, the IPA code is still expecting the digital gain to come back in
+ * the prepare_status. To keep things happy, we'll just fill in the value that
+ * we calculated previously and put in the AgcStatus (which comes back as the
+ * "delayed" status). Once the rest of the IPA code is updated, we'll be able
+ * to remove this, and indeed remove the digitalGain from the AgcPrepareStatus.
+ */
+ AgcStatus delayedStatus;
+ if (!imageMetadata->get("agc.delayed_status", delayedStatus))
+ prepareStatus.digitalGain = delayedStatus.digitalGain;
+ else
+ /* After a mode switch, this must be correct until new values come through. */
+ prepareStatus.digitalGain = status_.digitalGain;
}
+
+ imageMetadata->set("agc.prepare_status", prepareStatus);
}
void AgcChannel::process(StatisticsPtr &stats, DeviceStatus const &deviceStatus,
@@ -606,7 +586,7 @@ void AgcChannel::housekeepConfig()
/* First fetch all the up-to-date settings, so no one else has to do it. */
status_.ev = ev_;
status_.fixedExposureTime = limitExposureTime(fixedExposureTime_);
- status_.fixedGain = fixedGain_;
+ status_.fixedGain = limitGain(fixedGain_);
status_.flickerPeriod = flickerPeriod_;
LOG(RPiAgc, Debug) << "ev " << status_.ev << " fixedExposureTime "
<< status_.fixedExposureTime << " fixedGain "
@@ -657,6 +637,9 @@ void AgcChannel::fetchCurrentExposure(DeviceStatus const &deviceStatus)
current_.analogueGain = deviceStatus.analogueGain;
current_.totalExposure = 0s; /* this value is unused */
current_.totalExposureNoDG = current_.exposureTime * current_.analogueGain;
+ LOG(RPiAgc, Debug) << "Current frame: exposure time " << current_.exposureTime
+ << " ag " << current_.analogueGain
+ << " (total " << current_.totalExposureNoDG << ")";
}
void AgcChannel::fetchAwbStatus(Metadata *imageMetadata)
@@ -808,14 +791,12 @@ void AgcChannel::computeTargetExposure(double gain)
target_.totalExposure = current_.totalExposureNoDG * gain;
/* The final target exposure is also limited to what the exposure mode allows. */
Duration maxExposureTime = status_.fixedExposureTime
- ? status_.fixedExposureTime
- : exposureMode_->exposureTime.back();
+ ? status_.fixedExposureTime
+ : exposureMode_->exposureTime.back();
maxExposureTime = limitExposureTime(maxExposureTime);
- Duration maxTotalExposure =
- maxExposureTime *
- (status_.fixedGain != 0.0
- ? status_.fixedGain
- : exposureMode_->gain.back());
+ double maxGain = status_.fixedGain ? status_.fixedGain : exposureMode_->gain.back();
+ maxGain = limitGain(maxGain);
+ Duration maxTotalExposure = maxExposureTime * maxGain;
target_.totalExposure = std::min(target_.totalExposure, maxTotalExposure);
}
LOG(RPiAgc, Debug) << "Target totalExposure " << target_.totalExposure;
@@ -824,8 +805,6 @@ void AgcChannel::computeTargetExposure(double gain)
bool AgcChannel::applyChannelConstraints(const AgcChannelTotalExposures &channelTotalExposures)
{
bool channelBound = false;
- LOG(RPiAgc, Debug)
- << "Total exposure before channel constraints " << filtered_.totalExposure;
for (const auto &constraint : config_.channelConstraints) {
LOG(RPiAgc, Debug)
@@ -860,7 +839,7 @@ bool AgcChannel::applyChannelConstraints(const AgcChannelTotalExposures &channel
bool AgcChannel::applyDigitalGain(double gain, double targetY, bool channelBound)
{
- double dg = 1.0;
+ filtered_.totalExposureNoDG = filtered_.totalExposure;
/*
* Finally, if we're trying to reduce exposure but the target_Y is
@@ -871,15 +850,14 @@ bool AgcChannel::applyDigitalGain(double gain, double targetY, bool channelBound
* quickly (and we then approach the correct value more quickly from
* below).
*/
- bool desaturate = false;
- if (config_.desaturate)
- desaturate = !channelBound &&
- 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;
+ bool desaturate = config_.desaturate && !channelBound &&
+ targetY > config_.fastReduceThreshold && gain < sqrt(targetY);
+
+ if (desaturate) {
+ filtered_.totalExposureNoDG *= config_.fastReduceThreshold;
+ LOG(RPiAgc, Debug) << "Desaturating, exposure no dg " << filtered_.totalExposureNoDG;
+ }
+
return desaturate;
}
@@ -915,8 +893,7 @@ void AgcChannel::filterExposure()
filtered_.totalExposure = speed * target_.totalExposure +
filtered_.totalExposure * (1.0 - speed);
}
- LOG(RPiAgc, Debug) << "After filtering, totalExposure " << filtered_.totalExposure
- << " no dg " << filtered_.totalExposureNoDG;
+ LOG(RPiAgc, Debug) << "After filtering, totalExposure " << filtered_.totalExposure;
}
void AgcChannel::divideUpExposure()
@@ -957,9 +934,7 @@ void AgcChannel::divideUpExposure()
}
}
}
- LOG(RPiAgc, Debug)
- << "Divided up exposure time and gain are " << exposureTime
- << " and " << gain;
+
/*
* Finally adjust exposure time for flicker avoidance (require both
* exposure time and gain not to be fixed).
@@ -970,22 +945,30 @@ void AgcChannel::divideUpExposure()
if (flickerPeriods) {
Duration newExposureTime = flickerPeriods * status_.flickerPeriod;
gain *= exposureTime / newExposureTime;
- /*
- * 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.
- */
- gain = std::min(gain, exposureMode_->gain.back());
- gain = limitGain(gain);
exposureTime = newExposureTime;
}
LOG(RPiAgc, Debug) << "After flicker avoidance, exposure time "
<< exposureTime << " gain " << gain;
}
+
+ /* Limit analogue gain to maximum allowed. */
+ double analogueGain = std::min(gain, mode_.maxAnalogueGain);
+
+ /* Finally work out the digital gain that we will need. */
+ filtered_.totalExposureNoDG = analogueGain * exposureTime;
+ double digitalGain = filtered_.totalExposure / filtered_.totalExposureNoDG;
+ /* Limit dg by what is allowed. */
+ digitalGain = std::min(digitalGain, config_.maxDigitalGain);
+ /* Update total exposure, in case the dg went down. */
+ filtered_.totalExposure = filtered_.totalExposureNoDG * digitalGain;
+
filtered_.exposureTime = exposureTime;
- /* We ask for all the gain as analogue gain; prepare() will be told what we got. */
- filtered_.analogueGain = gain;
+ filtered_.analogueGain = analogueGain;
+ filtered_.digitalGain = digitalGain;
+ LOG(RPiAgc, Debug) << "DivideUpExposure: total " << filtered_.totalExposure
+ << " no dg " << filtered_.totalExposureNoDG;
+ LOG(RPiAgc, Debug) << "DivideUpExposure: exp " << exposureTime
+ << " ag " << gain << " dg " << digitalGain;
}
void AgcChannel::writeAndFinish(Metadata *imageMetadata, bool desaturate)
@@ -994,6 +977,7 @@ void AgcChannel::writeAndFinish(Metadata *imageMetadata, bool desaturate)
status_.targetExposureValue = desaturate ? 0s : target_.totalExposure;
status_.exposureTime = filtered_.exposureTime;
status_.analogueGain = filtered_.analogueGain;
+ status_.digitalGain = filtered_.digitalGain;
/*
* Write to metadata as well, in case anyone wants to update the camera
* immediately.
@@ -1001,8 +985,6 @@ void AgcChannel::writeAndFinish(Metadata *imageMetadata, bool desaturate)
imageMetadata->set("agc.status", status_);
LOG(RPiAgc, Debug) << "Output written, total exposure requested is "
<< filtered_.totalExposure;
- LOG(RPiAgc, Debug) << "Camera exposure update: exposure time " << filtered_.exposureTime
- << " analogue gain " << filtered_.analogueGain;
}
Duration AgcChannel::limitExposureTime(Duration exposureTime)
@@ -1031,6 +1013,7 @@ double AgcChannel::limitGain(double gain) const
if (!gain)
return gain;
- gain = std::max(gain, mode_.minAnalogueGain);
+ gain = std::clamp(gain, mode_.minAnalogueGain,
+ mode_.maxAnalogueGain * config_.maxDigitalGain);
return gain;
}
@@ -135,6 +135,7 @@ private:
libcamera::utils::Duration exposureTime;
double analogueGain;
+ double digitalGain;
libcamera::utils::Duration totalExposure;
libcamera::utils::Duration totalExposureNoDG; /* without digital gain */
};
Previously we let prepare() do the work by comparing the desired total exposure against the shutter time and analogue gain. This can cause the image to "wink" at high framerates because we may skip running prepare() to get the new digital gain even when the delayed AGC status (which came out of an earlier call to process()) shows that a change was required. Now we're taking explicit control of the digital gain by calculating it ourselves so that we can output it in the standard AgcStatus object. This means that whenever the delayed AGC status changes, we have the correct digital gain to go with it. Signed-off-by: David Plowman <david.plowman@raspberrypi.com> --- src/ipa/rpi/controller/agc_status.h | 1 + src/ipa/rpi/controller/rpi/agc_channel.cpp | 163 +++++++++------------ src/ipa/rpi/controller/rpi/agc_channel.h | 1 + 3 files changed, 75 insertions(+), 90 deletions(-)