@@ -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 */
};