diff --git a/src/ipa/rpi/controller/agc_status.h b/src/ipa/rpi/controller/agc_status.h index d4cedcf4..956d6abf 100644 --- a/src/ipa/rpi/controller/agc_status.h +++ b/src/ipa/rpi/controller/agc_status.h @@ -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; diff --git a/src/ipa/rpi/controller/rpi/agc_channel.cpp b/src/ipa/rpi/controller/rpi/agc_channel.cpp index 564beaf1..e56f955a 100644 --- a/src/ipa/rpi/controller/rpi/agc_channel.cpp +++ b/src/ipa/rpi/controller/rpi/agc_channel.cpp @@ -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; } diff --git a/src/ipa/rpi/controller/rpi/agc_channel.h b/src/ipa/rpi/controller/rpi/agc_channel.h index 93229128..42d85ec1 100644 --- a/src/ipa/rpi/controller/rpi/agc_channel.h +++ b/src/ipa/rpi/controller/rpi/agc_channel.h @@ -135,6 +135,7 @@ private: libcamera::utils::Duration exposureTime; double analogueGain; + double digitalGain; libcamera::utils::Duration totalExposure; libcamera::utils::Duration totalExposureNoDG; /* without digital gain */ };