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.cpp338
1 files changed, 338 insertions, 0 deletions
diff --git a/src/ipa/rpi/controller/rpi/agc.cpp b/src/ipa/rpi/controller/rpi/agc.cpp
new file mode 100644
index 00000000..6549dedd
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/agc.cpp
@@ -0,0 +1,338 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * agc.cpp - AGC/AEC control algorithm
+ */
+
+#include "agc.h"
+
+#include <libcamera/base/log.h>
+
+#include "../metadata.h"
+
+using namespace RPiController;
+using namespace libcamera;
+using libcamera::utils::Duration;
+using namespace std::literals::chrono_literals;
+
+LOG_DEFINE_CATEGORY(RPiAgc)
+
+#define NAME "rpi.agc"
+
+Agc::Agc(Controller *controller)
+ : AgcAlgorithm(controller),
+ activeChannels_({ 0 }), index_(0)
+{
+}
+
+char const *Agc::name() const
+{
+ return NAME;
+}
+
+int Agc::read(const libcamera::YamlObject &params)
+{
+ /*
+ * 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";
+ channelTotalExposures_.resize(1, 0s);
+ channelData_.emplace_back();
+ return channelData_.back().channel.read(params, getHardwareConfig());
+ }
+
+ 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;
+ }
+
+ LOG(RPiAgc, Debug) << "Read " << channelData_.size() << " channel(s)";
+ if (channelData_.empty()) {
+ LOG(RPiAgc, Error) << "No AGC channels provided";
+ return -1;
+ }
+
+ channelTotalExposures_.resize(channelData_.size(), 0s);
+
+ return 0;
+}
+
+int Agc::checkChannel(unsigned int channelIndex) const
+{
+ if (channelIndex >= channelData_.size()) {
+ LOG(RPiAgc, Warning) << "AGC channel " << channelIndex << " not available";
+ return -1;
+ }
+
+ return 0;
+}
+
+void Agc::disableAuto()
+{
+ LOG(RPiAgc, Debug) << "disableAuto";
+
+ /* All channels are enabled/disabled together. */
+ for (auto &data : channelData_)
+ data.channel.disableAuto();
+}
+
+void Agc::enableAuto()
+{
+ LOG(RPiAgc, Debug) << "enableAuto";
+
+ /* All channels are enabled/disabled together. */
+ for (auto &data : channelData_)
+ data.channel.enableAuto();
+}
+
+unsigned int Agc::getConvergenceFrames() const
+{
+ /* 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 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.
+ */
+ return channelData_[0].channel.getWeights();
+}
+
+void Agc::setEv(unsigned int channelIndex, double ev)
+{
+ if (checkChannel(channelIndex))
+ return;
+
+ LOG(RPiAgc, Debug) << "setEv " << ev << " for channel " << channelIndex;
+ channelData_[channelIndex].channel.setEv(ev);
+}
+
+void Agc::setFlickerPeriod(Duration flickerPeriod)
+{
+ LOG(RPiAgc, Debug) << "setFlickerPeriod " << flickerPeriod;
+
+ /* Flicker period will be the same across all channels. */
+ for (auto &data : channelData_)
+ data.channel.setFlickerPeriod(flickerPeriod);
+}
+
+void Agc::setMaxShutter(Duration maxShutter)
+{
+ /* Frame durations will be the same across all channels too. */
+ for (auto &data : channelData_)
+ data.channel.setMaxShutter(maxShutter);
+}
+
+void Agc::setFixedShutter(unsigned int channelIndex, Duration fixedShutter)
+{
+ if (checkChannel(channelIndex))
+ return;
+
+ LOG(RPiAgc, Debug) << "setFixedShutter " << fixedShutter
+ << " for channel " << channelIndex;
+ channelData_[channelIndex].channel.setFixedShutter(fixedShutter);
+}
+
+void Agc::setFixedAnalogueGain(unsigned int channelIndex, double fixedAnalogueGain)
+{
+ if (checkChannel(channelIndex))
+ return;
+
+ LOG(RPiAgc, Debug) << "setFixedAnalogueGain " << fixedAnalogueGain
+ << " for channel " << channelIndex;
+ channelData_[channelIndex].channel.setFixedAnalogueGain(fixedAnalogueGain);
+}
+
+void Agc::setMeteringMode(std::string const &meteringModeName)
+{
+ /* Metering modes will be the same across all channels too. */
+ for (auto &data : channelData_)
+ data.channel.setMeteringMode(meteringModeName);
+}
+
+void Agc::setExposureMode(std::string const &exposureModeName)
+{
+ LOG(RPiAgc, Debug) << "setExposureMode " << exposureModeName;
+
+ /* Exposure mode will be the same across all channels. */
+ for (auto &data : channelData_)
+ data.channel.setExposureMode(exposureModeName);
+}
+
+void Agc::setConstraintMode(std::string const &constraintModeName)
+{
+ LOG(RPiAgc, Debug) << "setConstraintMode " << constraintModeName;
+
+ /* Constraint mode will be the same across all channels. */
+ for (auto &data : channelData_)
+ data.channel.setConstraintMode(constraintModeName);
+}
+
+template<typename T>
+std::ostream &operator<<(std::ostream &os, const std::vector<T> &v)
+{
+ os << "{";
+ for (const auto &e : v)
+ os << " " << e;
+ os << " }";
+ return os;
+}
+
+void Agc::setActiveChannels(const std::vector<unsigned int> &activeChannels)
+{
+ if (activeChannels.empty()) {
+ LOG(RPiAgc, Warning) << "No active AGC channels supplied";
+ return;
+ }
+
+ for (auto index : activeChannels)
+ if (checkChannel(index))
+ return;
+
+ LOG(RPiAgc, Debug) << "setActiveChannels " << activeChannels;
+ activeChannels_ = activeChannels;
+ index_ = 0;
+}
+
+void Agc::switchMode(CameraMode const &cameraMode,
+ Metadata *metadata)
+{
+ /*
+ * We run switchMode on every channel, and then we're going to start over
+ * with the first active channel again which means that this channel's
+ * status needs to be the one we leave in the metadata.
+ */
+ AgcStatus status;
+
+ for (unsigned int channelIndex = 0; channelIndex < channelData_.size(); channelIndex++) {
+ LOG(RPiAgc, Debug) << "switchMode for channel " << channelIndex;
+ channelData_[channelIndex].channel.switchMode(cameraMode, metadata);
+ if (channelIndex == activeChannels_[0])
+ metadata->get("agc.status", status);
+ }
+
+ status.channel = activeChannels_[0];
+ metadata->set("agc.status", status);
+ index_ = 0;
+}
+
+static void getDelayedChannelIndex(Metadata *metadata, const char *message, unsigned int &channelIndex)
+{
+ std::unique_lock<RPiController::Metadata> lock(*metadata);
+ AgcStatus *status = metadata->getLocked<AgcStatus>("agc.delayed_status");
+ if (status)
+ channelIndex = status->channel;
+ else {
+ /* This does happen at startup, otherwise it would be a Warning or Error. */
+ LOG(RPiAgc, Debug) << message;
+ }
+}
+
+static libcamera::utils::Duration
+setCurrentChannelIndexGetExposure(Metadata *metadata, const char *message, unsigned int channelIndex)
+{
+ std::unique_lock<RPiController::Metadata> lock(*metadata);
+ AgcStatus *status = metadata->getLocked<AgcStatus>("agc.status");
+ libcamera::utils::Duration dur = 0s;
+
+ if (status) {
+ status->channel = channelIndex;
+ dur = status->totalExposureValue;
+ } else {
+ /* This does happen at startup, otherwise it would be a Warning or Error. */
+ LOG(RPiAgc, Debug) << message;
+ }
+
+ return dur;
+}
+
+void Agc::prepare(Metadata *imageMetadata)
+{
+ /*
+ * The DeviceStatus in the metadata should be correct for the image we
+ * are processing. The delayed status should tell us what channel this frame
+ * was from, so we will use that channel's prepare method.
+ *
+ * \todo To be honest, there's not much that's stateful in the prepare methods
+ * so we should perhaps re-evaluate whether prepare even needs to be done
+ * "per channel".
+ */
+ unsigned int channelIndex = activeChannels_[0];
+ getDelayedChannelIndex(imageMetadata, "prepare: no delayed status", channelIndex);
+
+ LOG(RPiAgc, Debug) << "prepare for channel " << channelIndex;
+ channelData_[channelIndex].channel.prepare(imageMetadata);
+}
+
+void Agc::process(StatisticsPtr &stats, Metadata *imageMetadata)
+{
+ /*
+ * We want to generate values for the next channel in round robin fashion
+ * (i.e. the channel at location index_ in the activeChannel list), even though
+ * the statistics we have will be for a different channel (which we find
+ * again from the delayed status).
+ */
+
+ /* Generate updated AGC values for channel for new channel that we are requesting. */
+ unsigned int channelIndex = activeChannels_[index_];
+ AgcChannelData &channelData = channelData_[channelIndex];
+ /* The stats that arrived with this image correspond to the following channel. */
+ unsigned int statsIndex = 0;
+ getDelayedChannelIndex(imageMetadata, "process: no delayed status for stats", statsIndex);
+ LOG(RPiAgc, Debug) << "process for channel " << channelIndex;
+
+ /*
+ * We keep a cache of the most recent DeviceStatus and stats for each channel,
+ * so that we can invoke the next channel's process method with the most up to date
+ * values.
+ */
+ LOG(RPiAgc, Debug) << "Save DeviceStatus and stats for channel " << statsIndex;
+ DeviceStatus deviceStatus;
+ if (imageMetadata->get<DeviceStatus>("device.status", deviceStatus) == 0)
+ channelData_[statsIndex].deviceStatus = deviceStatus;
+ else
+ /* Every frame should have a DeviceStatus. */
+ LOG(RPiAgc, Error) << "process: no device status found";
+ channelData_[statsIndex].statistics = stats;
+
+ /*
+ * Finally fetch the most recent DeviceStatus and stats for the new channel, if both
+ * exist, and call process(). We must make the agc.status metadata record correctly
+ * which channel this is.
+ */
+ StatisticsPtr *statsPtr = &stats;
+ if (channelData.statistics && channelData.deviceStatus) {
+ deviceStatus = *channelData.deviceStatus;
+ statsPtr = &channelData.statistics;
+ } else {
+ /* Can also happen when new channels start. */
+ LOG(RPiAgc, Debug) << "process: channel " << channelIndex << " not seen yet";
+ }
+
+ channelData.channel.process(*statsPtr, deviceStatus, imageMetadata, channelTotalExposures_);
+ auto dur = setCurrentChannelIndexGetExposure(imageMetadata, "process: no AGC status found",
+ channelIndex);
+ if (dur)
+ channelTotalExposures_[channelIndex] = dur;
+
+ /* And onto the next channel for the next call. */
+ index_ = (index_ + 1) % activeChannels_.size();
+}
+
+/* Register algorithm with the system. */
+static Algorithm *create(Controller *controller)
+{
+ return (Algorithm *)new Agc(controller);
+}
+static RegisterAlgorithm reg(NAME, &create);