Message ID | 20240709144950.3277837-8-dan.scally@ideasonboard.com |
---|---|
State | Superseded |
Headers | show |
Series |
|
Related | show |
Quoting Daniel Scally (2024-07-09 15:49:47) > Add a new algorithm and associated infrastructure for Agc. The > tuning files for uncalibrated sensors is extended to enable the > algorithm. > > Acked-by: Nayden Kanchev <nayden.kanchev@arm.com> > Co-developed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com> > --- > Changes in v2: > > - Use the union rather than reinterpret_cast<>() to abstract the block > > src/ipa/mali-c55/algorithms/agc.cpp | 440 ++++++++++++++++++++++++ > src/ipa/mali-c55/algorithms/agc.h | 82 +++++ > src/ipa/mali-c55/algorithms/meson.build | 1 + > src/ipa/mali-c55/data/uncalibrated.yaml | 1 + > src/ipa/mali-c55/ipa_context.h | 32 ++ > src/ipa/mali-c55/mali-c55.cpp | 54 ++- > 6 files changed, 608 insertions(+), 2 deletions(-) > create mode 100644 src/ipa/mali-c55/algorithms/agc.cpp > create mode 100644 src/ipa/mali-c55/algorithms/agc.h > > diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp > new file mode 100644 > index 00000000..bbd3118c > --- /dev/null > +++ b/src/ipa/mali-c55/algorithms/agc.cpp > @@ -0,0 +1,440 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2024, Ideas On Board Oy > + * > + * agc.cpp - AGC/AEC mean-based control algorithm > + */ > + > +#include "agc.h" > + > +#include <math.h> > + > +#include <libcamera/base/log.h> > + > +#include <libcamera/control_ids.h> > +#include <libcamera/property_ids.h> > + > +namespace libcamera { > + > +using namespace std::literals::chrono_literals; > + > +namespace ipa::mali_c55::algorithms { > + > +LOG_DEFINE_CATEGORY(MaliC55Agc) > + > +/* > + * Number of histogram bins. This is only true for the specific configuration we > + * set to the ISP; 4 separate histograms of 256 bins each. If that configuration > + * ever changes then this constant will need updating. > + */ > +static constexpr unsigned int kNumHistogramBins = 256; > + > +/* > + * The Mali-C55 ISP has a digital gain block which allows setting gain in Q5.8 > + * format, a range of 0.0 to (very nearly) 32.0. We clamp from 1.0 to the actual > + * max value. > + */ > +static constexpr double kMinDigitalGain = 1.0; > +static constexpr double kMaxDigitalGain = 8191 * pow(2, -8); Here and elsewhere, I think we should be using std::pow(,) and <math> not <math.h>. > + > +uint32_t AgcStatistics::decodeBinValue(uint16_t binVal) > +{ > + int exponent = (binVal & 0xf000) >> 12; > + int mantissa = binVal & 0xfff; > + > + if (!exponent) > + return mantissa * 2; > + else > + return (mantissa + 4096) * pow(2, exponent); > +} > + > +/* > + * We configure the ISP to give us 4 histograms of 256 bins each, with > + * a single histogram per colour channel (R/Gr/Gb/B). The memory space > + * containing the data is a single block containing all 4 histograms > + * with the position of each colour's histogram within it dependent on > + * the bayer pattern of the data input to the ISP. > + * > + * NOTE: The validity of this function depends on the parameters we have > + * configured. With different skip/offset x, y values not all of the > + * colour channels would be populated, and they may not be in the same > + * planes as calculated here. > + */ > +int AgcStatistics::setBayerOrderIndices(BayerFormat::Order bayerOrder) > +{ > + switch (bayerOrder) { > + case BayerFormat::Order::RGGB: > + rIndex_ = 0; > + grIndex_ = 1; > + gbIndex_ = 2; > + bIndex_ = 3; > + break; > + case BayerFormat::Order::GRBG: > + grIndex_ = 0; > + rIndex_ = 1; > + bIndex_ = 2; > + gbIndex_ = 3; > + break; > + case BayerFormat::Order::GBRG: > + gbIndex_ = 0; > + bIndex_ = 1; > + rIndex_ = 2; > + grIndex_ = 3; > + break; > + case BayerFormat::Order::BGGR: > + bIndex_ = 0; > + gbIndex_ = 1; > + grIndex_ = 2; > + rIndex_ = 3; > + break; > + default: > + LOG(MaliC55Agc, Error) > + << "Invalid bayer format " << bayerOrder; > + return -EINVAL; > + } > + > + return 0; > +} > + > +void AgcStatistics::parseStatistics(const mali_c55_stats_buffer *stats) > +{ > + uint32_t r[256], g[256], b[256], y[256]; > + > + /* > + * We need to decode the bin values for each histogram from their 16-bit > + * compressed values to a 32-bit value. We also take the average of the > + * Gr/Gb values into a single green histogram. > + */ > + for (unsigned int i = 0; i < 256; i++) { > + r[i] = decodeBinValue(stats->ae_1024bin_hist.bins[i + (256 * rIndex_)]); > + g[i] = (decodeBinValue(stats->ae_1024bin_hist.bins[i + (256 * grIndex_)]) + > + decodeBinValue(stats->ae_1024bin_hist.bins[i + (256 * gbIndex_)])) / 2; > + b[i] = decodeBinValue(stats->ae_1024bin_hist.bins[i + (256 * bIndex_)]); > + > + y[i] = (r[i] * .299) + (g[i] * .587) + (b[i] * .114); Which conversion is this? ( I can guess, but raw numbers aren't friendly). I'm sure this is something we should add a named helper for in libipa and would be used commonly across IPAs. > + } > + > + rHist = Histogram(Span<uint32_t>(r, kNumHistogramBins)); > + gHist = Histogram(Span<uint32_t>(g, kNumHistogramBins)); > + bHist = Histogram(Span<uint32_t>(b, kNumHistogramBins)); > + yHist = Histogram(Span<uint32_t>(y, kNumHistogramBins)); > +} > + > +Agc::Agc() > + : AgcMeanLuminance() > +{ > +} > + > +int Agc::init(IPAContext &context, const YamlObject &tuningData) > +{ > + int ret = parseTuningData(tuningData); > + if (ret) > + return ret; > + > + context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true); > + context.ctrlMap[&controls::DigitalGain] = ControlInfo( > + static_cast<float>(kMinDigitalGain), > + static_cast<float>(kMaxDigitalGain), > + static_cast<float>(kMinDigitalGain) > + ); > + context.ctrlMap.merge(controls()); > + > + return 0; > +} > + > +int Agc::configure(IPAContext &context, > + [[maybe_unused]] const IPACameraSensorInfo &configInfo) > +{ > + int ret = statistics_.setBayerOrderIndices(context.configuration.sensor.bayerOrder); > + if (ret) > + return ret; > + > + /* > + * Defaults; we use whatever the sensor's default exposure is and the > + * minimum analogue gain. AEGC is _active_ by default. > + */ > + context.activeState.agc.autoEnabled = true; > + context.activeState.agc.automatic.sensorGain = context.configuration.agc.minAnalogueGain; > + context.activeState.agc.automatic.exposure = context.configuration.agc.defaultExposure; > + context.activeState.agc.automatic.ispGain = kMinDigitalGain; > + context.activeState.agc.manual.sensorGain = context.configuration.agc.minAnalogueGain; > + context.activeState.agc.manual.exposure = context.configuration.agc.defaultExposure; > + context.activeState.agc.manual.ispGain = kMinDigitalGain; > + context.activeState.agc.constraintMode = constraintModes().begin()->first; > + context.activeState.agc.exposureMode = exposureModeHelpers().begin()->first; > + > + /* \todo Run this again when FrameDurationLimits is passed in */ > + setLimits(context.configuration.agc.minShutterSpeed, > + context.configuration.agc.maxShutterSpeed, > + context.configuration.agc.minAnalogueGain, > + context.configuration.agc.maxAnalogueGain); > + > + resetFrameCount(); > + > + return 0; > +} > + > +void Agc::queueRequest(IPAContext &context, const uint32_t frame, > + [[maybe_unused]] IPAFrameContext &frameContext, > + const ControlList &controls) > +{ > + auto &agc = context.activeState.agc; > + > + const auto &constraintMode = controls.get(controls::AeConstraintMode); > + agc.constraintMode = constraintMode.value_or(agc.constraintMode); > + > + const auto &exposureMode = controls.get(controls::AeExposureMode); > + agc.exposureMode = exposureMode.value_or(agc.exposureMode); > + > + const auto &agcEnable = controls.get(controls::AeEnable); > + if (agcEnable && *agcEnable != agc.autoEnabled) { > + agc.autoEnabled = *agcEnable; > + > + LOG(MaliC55Agc, Info) > + << (agc.autoEnabled ? "Enabling" : "Disabling") > + << " AGC"; > + } > + > + /* > + * If the automatic exposure and gain is enabled we have no further work > + * to do here... > + */ > + if (agc.autoEnabled) > + return; > + > + /* > + * ...otherwise we need to look for exposure and gain controls and use > + * those to set the activeState. > + */ > + const auto &exposure = controls.get(controls::ExposureTime); > + if (exposure) { > + agc.manual.exposure = *exposure * 1.0us / context.configuration.sensor.lineDuration; > + > + LOG(MaliC55Agc, Debug) > + << "Exposure set to " << agc.manual.exposure > + << " on request sequence " << frame; > + } > + > + const auto &analogueGain = controls.get(controls::AnalogueGain); > + if (analogueGain) { > + agc.manual.sensorGain = *analogueGain; > + > + LOG(MaliC55Agc, Debug) > + << "Analogue gain set to " << agc.manual.sensorGain > + << " on request sequence " << frame; > + } > + > + const auto &digitalGain = controls.get(controls::DigitalGain); > + if (digitalGain) { > + agc.manual.ispGain = *digitalGain; > + > + LOG(MaliC55Agc, Debug) > + << "Digital gain set to " << agc.manual.ispGain > + << " on request sequence " << frame; > + } > +} > + > +size_t Agc::fillGainParamBlock(IPAContext &context, IPAFrameContext &frameContext, > + mali_c55_params_block block) > +{ > + IPAActiveState &activeState = context.activeState; > + double gain; > + > + if (activeState.agc.autoEnabled) > + gain = activeState.agc.automatic.ispGain; > + else > + gain = activeState.agc.manual.ispGain; > + > + block.header->type = MALI_C55_PARAM_BLOCK_DIGITAL_GAIN; > + block.header->enabled = true; > + block.header->size = sizeof(struct mali_c55_params_digital_gain); > + > + block.digital_gain->gain = int(gain * pow(2, 8)); Casts / Q4.8 helpers would be beneficial again. > + frameContext.agc.ispGain = gain; > + > + return block.header->size; > +} > + > +size_t Agc::fillParamsBuffer(mali_c55_params_block block, > + enum mali_c55_param_block_type type) > +{ > + block.header->type = type; > + block.header->enabled = true; > + block.header->size = sizeof(struct mali_c55_params_aexp_hist); > + > + /* Collect every 3rd pixel horizontally */ > + block.aexp_hist->skip_x = 1; > + /* Start from first column */ > + block.aexp_hist->offset_x = 0; > + /* Collect every pixel vertically */ > + block.aexp_hist->skip_y = 0; > + /* Start from the first row */ > + block.aexp_hist->offset_y = 0; > + /* 1x scaling (i.e. none) */ > + block.aexp_hist->scale_bottom = 0; > + block.aexp_hist->scale_top = 0; > + /* Collect all Bayer planes into 4 separate histograms */ > + block.aexp_hist->plane_mode = 1; > + /* Tap the data immediately after the digital gain block */ > + block.aexp_hist->tap_point = MALI_C55_AEXP_HIST_TAP_FS; > + > + return block.header->size; > +} > + > +size_t Agc::fillWeightsArrayBuffer(mali_c55_params_block block, > + enum mali_c55_param_block_type type) > +{ > + block.header->type = type; > + block.header->enabled = true; > + block.header->size = sizeof(struct mali_c55_params_aexp_weights); > + > + /* We use every zone - a 15x15 grid */ > + block.aexp_weights->nodes_used_horiz = 15; > + block.aexp_weights->nodes_used_vert = 15; > + > + /* > + * We uniformly weight the zones to 1 - this results in the collected > + * histograms containing a true pixel count, which we can then use to > + * approximate colour channel averages for the image. > + */ > + Span<uint8_t> weights{ > + block.aexp_weights->zone_weights, > + MALI_C55_MAX_ZONES > + }; > + std::fill(weights.begin(), weights.end(), 1); > + > + return block.header->size; > +} > + > +void Agc::prepare(IPAContext &context, const uint32_t frame, > + IPAFrameContext &frameContext, mali_c55_params_buffer *params) > +{ > + mali_c55_params_block block; > + > + block.data = ¶ms->data[params->total_size]; > + params->total_size += fillGainParamBlock(context, frameContext, block); > + > + if (frame > 0) > + return; > + > + block.data = ¶ms->data[params->total_size]; > + params->total_size += fillParamsBuffer(block, > + MALI_C55_PARAM_BLOCK_AEXP_HIST); > + > + block.data = ¶ms->data[params->total_size]; > + params->total_size += fillWeightsArrayBuffer(block, > + MALI_C55_PARAM_BLOCK_AEXP_HIST_WEIGHTS); > + > + block.data = ¶ms->data[params->total_size]; > + params->total_size += fillParamsBuffer(block, > + MALI_C55_PARAM_BLOCK_AEXP_IHIST); > + > + block.data = ¶ms->data[params->total_size]; > + params->total_size += fillWeightsArrayBuffer(block, > + MALI_C55_PARAM_BLOCK_AEXP_IHIST_WEIGHTS); > +} > + > +double Agc::estimateLuminance(const double gain) const > +{ > + double rAvg = statistics_.rHist.interQuantileMean(0, 1) * gain; > + double gAvg = statistics_.gHist.interQuantileMean(0, 1) * gain; > + double bAvg = statistics_.bHist.interQuantileMean(0, 1) * gain; > + double yAvg = (rAvg * .299) + (gAvg * .587) + (bAvg * .114); And here's that REC601 calculation repeated again! > + > + return yAvg / kNumHistogramBins; > +} > + > +/** > + * The function estimates the correlated color temperature using > + * from RGB color space input. > + * In physics and color science, the Planckian locus or black body locus is > + * the path or locus that the color of an incandescent black body would take > + * in a particular chromaticity space as the blackbody temperature changes. > + * > + * If a narrow range of color temperatures is considered (those encapsulating > + * daylight being the most practical case) one can approximate the Planckian > + * locus in order to calculate the CCT in terms of chromaticity coordinates. > + * > + * More detailed information can be found in: > + * https://en.wikipedia.org/wiki/Color_temperature#Approximation > + */ > +uint32_t Agc::estimateCCT() const > +{ > + double red = statistics_.rHist.interQuantileMean(0, 1); > + double green = statistics_.gHist.interQuantileMean(0, 1); > + double blue = statistics_.bHist.interQuantileMean(0, 1); > + > + /* Convert the RGB values to CIE tristimulus values (XYZ) */ > + double X = (-0.14282) * (red) + (1.54924) * (green) + (-0.95641) * (blue); > + double Y = (-0.32466) * (red) + (1.57837) * (green) + (-0.73191) * (blue); > + double Z = (-0.68202) * (red) + (0.77073) * (green) + (0.56332) * (blue); > + > + /* Calculate the normalized chromaticity values */ > + double x = X / (X + Y + Z); > + double y = Y / (X + Y + Z); > + > + /* Calculate CCT */ > + double n = (x - 0.3320) / (0.1858 - y); > + uint32_t ct = 449 * n * n * n + 3525 * n * n + 6823.3 * n + 5520.33; > + > + LOG(MaliC55Agc, Debug) << "Estimated Colour Temperature: " << ct; > + > + return ct; All of this after getting the r,g,b looks like an exact copy of Awb::estimateCCT(double red, double green, double blue) in src/ipa/ipu3/algorithms/awb.cpp. I think that's a clear sign we should move the code to a helper - or be calling something within libipa. I'm afraid I would say if we're duplicating code in libipa based IPA modules - we should be moving the code to a common location first, then use it. > +} > + > +void Agc::process(IPAContext &context, > + [[maybe_unused]] const uint32_t frame, > + IPAFrameContext &frameContext, > + const mali_c55_stats_buffer *stats, > + [[maybe_unused]] ControlList &metadata) > +{ > + IPASessionConfiguration &configuration = context.configuration; > + IPAActiveState &activeState = context.activeState; > + > + if (!stats) { > + LOG(MaliC55Agc, Error) << "No statistics buffer passed to Agc"; > + return; > + } > + > + statistics_.parseStatistics(stats); > + context.activeState.agc.temperatureK = estimateCCT(); > + > + /* > + * The Agc algorithm needs to know the effective exposure value that was > + * applied to the sensor when the statistics were collected. > + */ > + uint32_t exposure = frameContext.agc.exposure; > + double analogueGain = frameContext.agc.sensorGain; > + double digitalGain = frameContext.agc.ispGain; > + double totalGain = analogueGain * digitalGain; > + utils::Duration currentShutter = exposure * configuration.sensor.lineDuration; > + utils::Duration effectiveExposureValue = currentShutter * totalGain; > + > + utils::Duration shutterTime; > + double aGain, dGain; > + std::tie(shutterTime, aGain, dGain) = > + calculateNewEv(activeState.agc.constraintMode, > + activeState.agc.exposureMode, statistics_.yHist, > + effectiveExposureValue); > + > + dGain = std::clamp(dGain, kMinDigitalGain, kMaxDigitalGain); > + > + LOG(MaliC55Agc, Debug) > + << "Divided up shutter, analogue gain and digital gain are " > + << shutterTime << ", " << aGain << " and " << dGain; > + > + activeState.agc.automatic.exposure = shutterTime / configuration.sensor.lineDuration; > + activeState.agc.automatic.sensorGain = aGain; > + activeState.agc.automatic.ispGain = dGain; > + > + metadata.set(controls::ExposureTime, currentShutter.get<std::micro>()); > + metadata.set(controls::AnalogueGain, frameContext.agc.sensorGain); > + metadata.set(controls::DigitalGain, frameContext.agc.ispGain); > + metadata.set(controls::ColourTemperature, context.activeState.agc.temperatureK); > +} > + > +REGISTER_IPA_ALGORITHM(Agc, "Agc") > + > +} /* namespace ipa::mali_c55::algorithms */ > + > +} /* namespace libcamera */ > diff --git a/src/ipa/mali-c55/algorithms/agc.h b/src/ipa/mali-c55/algorithms/agc.h > new file mode 100644 > index 00000000..91e257a3 > --- /dev/null > +++ b/src/ipa/mali-c55/algorithms/agc.h > @@ -0,0 +1,82 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2023, Ideas on Board Oy > + * > + * agc.h - Mali C55 AGC/AEC mean-based control algorithm > + */ > + > +#pragma once > + > +#include <libcamera/base/utils.h> > + > +#include "libcamera/internal/bayer_format.h" > + > +#include "libipa/agc_mean_luminance.h" > +#include "libipa/histogram.h" > + > +#include "algorithm.h" > +#include "ipa_context.h" > + > +namespace libcamera { > + > +namespace ipa::mali_c55::algorithms { > + > +class AgcStatistics > +{ > +public: > + AgcStatistics() > + { > + } > + > + int setBayerOrderIndices(BayerFormat::Order bayerOrder); > + uint32_t decodeBinValue(uint16_t binVal); > + void parseStatistics(const mali_c55_stats_buffer *stats); > + > + Histogram rHist; > + Histogram gHist; > + Histogram bHist; > + Histogram yHist; > +private: > + unsigned int rIndex_; > + unsigned int grIndex_; > + unsigned int gbIndex_; > + unsigned int bIndex_; > +}; > + > +class Agc : public Algorithm, public AgcMeanLuminance > +{ > +public: > + Agc(); > + ~Agc() = default; > + > + int init(IPAContext &context, const YamlObject &tuningData) override; > + int configure(IPAContext &context, > + const IPACameraSensorInfo &configInfo) override; > + void queueRequest(IPAContext &context, const uint32_t frame, > + IPAFrameContext &frameContext, > + const ControlList &controls) override; > + void prepare(IPAContext &context, const uint32_t frame, > + IPAFrameContext &frameContext, > + mali_c55_params_buffer *params) override; > + void process(IPAContext &context, const uint32_t frame, > + IPAFrameContext &frameContext, > + const mali_c55_stats_buffer *stats, > + ControlList &metadata) override; > + > +private: > + double estimateLuminance(const double gain) const override; > + size_t fillGainParamBlock(IPAContext &context, > + IPAFrameContext &frameContext, > + mali_c55_params_block block); > + size_t fillParamsBuffer(mali_c55_params_block block, > + enum mali_c55_param_block_type type); > + size_t fillWeightsArrayBuffer(mali_c55_params_block block, > + enum mali_c55_param_block_type type); > + uint32_t estimateCCT() const; > + > + AgcStatistics statistics_; > +}; > + > +} /* namespace ipa::mali_c55::algorithms */ > + > +} /* namespace libcamera */ > diff --git a/src/ipa/mali-c55/algorithms/meson.build b/src/ipa/mali-c55/algorithms/meson.build > index d84432b9..96808431 100644 > --- a/src/ipa/mali-c55/algorithms/meson.build > +++ b/src/ipa/mali-c55/algorithms/meson.build > @@ -1,5 +1,6 @@ > # SPDX-License-Identifier: CC0-1.0 > > mali_c55_ipa_algorithms = files([ > + 'agc.cpp', > 'blc.cpp', > ]) > diff --git a/src/ipa/mali-c55/data/uncalibrated.yaml b/src/ipa/mali-c55/data/uncalibrated.yaml > index 2cdc39a8..6dcc0295 100644 > --- a/src/ipa/mali-c55/data/uncalibrated.yaml > +++ b/src/ipa/mali-c55/data/uncalibrated.yaml > @@ -3,4 +3,5 @@ > --- > version: 1 > algorithms: > + - Agc: > ... > diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h > index 9e408a17..73a7cd78 100644 > --- a/src/ipa/mali-c55/ipa_context.h > +++ b/src/ipa/mali-c55/ipa_context.h > @@ -7,8 +7,11 @@ > > #pragma once > > +#include <libcamera/base/utils.h> > #include <libcamera/controls.h> > > +#include "libcamera/internal/bayer_format.h" > + > #include <libipa/fc_queue.h> > > namespace libcamera { > @@ -16,15 +19,44 @@ namespace libcamera { > namespace ipa::mali_c55 { > > struct IPASessionConfiguration { > + struct { > + utils::Duration minShutterSpeed; > + utils::Duration maxShutterSpeed; > + uint32_t defaultExposure; > + double minAnalogueGain; > + double maxAnalogueGain; > + } agc; > + > + struct { > + BayerFormat::Order bayerOrder; > + utils::Duration lineDuration; > + } sensor; > }; > > struct IPAActiveState { > + struct { > + struct { > + uint32_t exposure; > + double sensorGain; > + double ispGain; > + } automatic; > + struct { > + uint32_t exposure; > + double sensorGain; > + double ispGain; > + } manual; > + bool autoEnabled; > + uint32_t constraintMode; > + uint32_t exposureMode; > + uint32_t temperatureK; > + } agc; > }; > > struct IPAFrameContext : public FrameContext { > struct { > uint32_t exposure; > double sensorGain; > + double ispGain; > } agc; > }; > > diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp > index 3daac3af..56936b86 100644 > --- a/src/ipa/mali-c55/mali-c55.cpp > +++ b/src/ipa/mali-c55/mali-c55.cpp > @@ -33,6 +33,8 @@ namespace libcamera { > > LOG_DEFINE_CATEGORY(IPAMaliC55) > > +using namespace std::literals::chrono_literals; > + > namespace ipa::mali_c55 { > > /* Maximum number of frame contexts to be held */ > @@ -60,6 +62,9 @@ protected: > std::string logPrefix() const override; > > private: > + void updateSessionConfiguration(const IPACameraSensorInfo &info, > + const ControlInfoMap &sensorControls, > + BayerFormat::Order bayerOrder); > void updateControls(const IPACameraSensorInfo &sensorInfo, > const ControlInfoMap &sensorControls, > ControlInfoMap *ipaControls); > @@ -133,7 +138,21 @@ int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig > > void IPAMaliC55::setControls() > { > + IPAActiveState &activeState = context_.activeState; > + uint32_t exposure; > + uint32_t gain; > + > + if (activeState.agc.autoEnabled) { > + exposure = activeState.agc.automatic.exposure; > + gain = camHelper_->gainCode(activeState.agc.automatic.sensorGain); > + } else { > + exposure = activeState.agc.manual.exposure; > + gain = camHelper_->gainCode(activeState.agc.manual.sensorGain); > + } > + > ControlList ctrls(sensorControls_); > + ctrls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure)); > + ctrls.set(V4L2_CID_ANALOGUE_GAIN, static_cast<int32_t>(gain)); > > setSensorControls.emit(ctrls); > } > @@ -148,6 +167,36 @@ void IPAMaliC55::stop() > context_.frameContexts.clear(); > } > > +void IPAMaliC55::updateSessionConfiguration(const IPACameraSensorInfo &info, > + const ControlInfoMap &sensorControls, > + BayerFormat::Order bayerOrder) > +{ > + context_.configuration.sensor.bayerOrder = bayerOrder; > + > + const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second; > + int32_t minExposure = v4l2Exposure.min().get<int32_t>(); > + int32_t maxExposure = v4l2Exposure.max().get<int32_t>(); > + int32_t defExposure = v4l2Exposure.def().get<int32_t>(); > + > + const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; > + int32_t minGain = v4l2Gain.min().get<int32_t>(); > + int32_t maxGain = v4l2Gain.max().get<int32_t>(); > + > + /* > + * When the AGC computes the new exposure values for a frame, it needs > + * to know the limits for shutter speed and analogue gain. > + * As it depends on the sensor, update it with the controls. > + * > + * \todo take VBLANK into account for maximum shutter speed > + */ > + context_.configuration.sensor.lineDuration = info.minLineLength * 1.0s / info.pixelRate; > + context_.configuration.agc.minShutterSpeed = minExposure * context_.configuration.sensor.lineDuration; > + context_.configuration.agc.maxShutterSpeed = maxExposure * context_.configuration.sensor.lineDuration; > + context_.configuration.agc.defaultExposure = defExposure; > + context_.configuration.agc.minAnalogueGain = camHelper_->gain(minGain); > + context_.configuration.agc.maxAnalogueGain = camHelper_->gain(maxGain); > +} > + > void IPAMaliC55::updateControls(const IPACameraSensorInfo &sensorInfo, > const ControlInfoMap &sensorControls, > ControlInfoMap *ipaControls) > @@ -209,8 +258,7 @@ void IPAMaliC55::updateControls(const IPACameraSensorInfo &sensorInfo, > *ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls); > } > > -int IPAMaliC55::configure(const IPAConfigInfo &ipaConfig, > - [[maybe_unused]] uint8_t bayerOrder, > +int IPAMaliC55::configure(const IPAConfigInfo &ipaConfig, uint8_t bayerOrder, > ControlInfoMap *ipaControls) > { > sensorControls_ = ipaConfig.sensorControls; > @@ -222,6 +270,8 @@ int IPAMaliC55::configure(const IPAConfigInfo &ipaConfig, > > const IPACameraSensorInfo &info = ipaConfig.sensorInfo; > > + updateSessionConfiguration(info, ipaConfig.sensorControls, > + static_cast<BayerFormat::Order>(bayerOrder)); > updateControls(info, ipaConfig.sensorControls, ipaControls); > > for (auto const &a : algorithms()) { > -- > 2.34.1 >
diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp new file mode 100644 index 00000000..bbd3118c --- /dev/null +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -0,0 +1,440 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board Oy + * + * agc.cpp - AGC/AEC mean-based control algorithm + */ + +#include "agc.h" + +#include <math.h> + +#include <libcamera/base/log.h> + +#include <libcamera/control_ids.h> +#include <libcamera/property_ids.h> + +namespace libcamera { + +using namespace std::literals::chrono_literals; + +namespace ipa::mali_c55::algorithms { + +LOG_DEFINE_CATEGORY(MaliC55Agc) + +/* + * Number of histogram bins. This is only true for the specific configuration we + * set to the ISP; 4 separate histograms of 256 bins each. If that configuration + * ever changes then this constant will need updating. + */ +static constexpr unsigned int kNumHistogramBins = 256; + +/* + * The Mali-C55 ISP has a digital gain block which allows setting gain in Q5.8 + * format, a range of 0.0 to (very nearly) 32.0. We clamp from 1.0 to the actual + * max value. + */ +static constexpr double kMinDigitalGain = 1.0; +static constexpr double kMaxDigitalGain = 8191 * pow(2, -8); + +uint32_t AgcStatistics::decodeBinValue(uint16_t binVal) +{ + int exponent = (binVal & 0xf000) >> 12; + int mantissa = binVal & 0xfff; + + if (!exponent) + return mantissa * 2; + else + return (mantissa + 4096) * pow(2, exponent); +} + +/* + * We configure the ISP to give us 4 histograms of 256 bins each, with + * a single histogram per colour channel (R/Gr/Gb/B). The memory space + * containing the data is a single block containing all 4 histograms + * with the position of each colour's histogram within it dependent on + * the bayer pattern of the data input to the ISP. + * + * NOTE: The validity of this function depends on the parameters we have + * configured. With different skip/offset x, y values not all of the + * colour channels would be populated, and they may not be in the same + * planes as calculated here. + */ +int AgcStatistics::setBayerOrderIndices(BayerFormat::Order bayerOrder) +{ + switch (bayerOrder) { + case BayerFormat::Order::RGGB: + rIndex_ = 0; + grIndex_ = 1; + gbIndex_ = 2; + bIndex_ = 3; + break; + case BayerFormat::Order::GRBG: + grIndex_ = 0; + rIndex_ = 1; + bIndex_ = 2; + gbIndex_ = 3; + break; + case BayerFormat::Order::GBRG: + gbIndex_ = 0; + bIndex_ = 1; + rIndex_ = 2; + grIndex_ = 3; + break; + case BayerFormat::Order::BGGR: + bIndex_ = 0; + gbIndex_ = 1; + grIndex_ = 2; + rIndex_ = 3; + break; + default: + LOG(MaliC55Agc, Error) + << "Invalid bayer format " << bayerOrder; + return -EINVAL; + } + + return 0; +} + +void AgcStatistics::parseStatistics(const mali_c55_stats_buffer *stats) +{ + uint32_t r[256], g[256], b[256], y[256]; + + /* + * We need to decode the bin values for each histogram from their 16-bit + * compressed values to a 32-bit value. We also take the average of the + * Gr/Gb values into a single green histogram. + */ + for (unsigned int i = 0; i < 256; i++) { + r[i] = decodeBinValue(stats->ae_1024bin_hist.bins[i + (256 * rIndex_)]); + g[i] = (decodeBinValue(stats->ae_1024bin_hist.bins[i + (256 * grIndex_)]) + + decodeBinValue(stats->ae_1024bin_hist.bins[i + (256 * gbIndex_)])) / 2; + b[i] = decodeBinValue(stats->ae_1024bin_hist.bins[i + (256 * bIndex_)]); + + y[i] = (r[i] * .299) + (g[i] * .587) + (b[i] * .114); + } + + rHist = Histogram(Span<uint32_t>(r, kNumHistogramBins)); + gHist = Histogram(Span<uint32_t>(g, kNumHistogramBins)); + bHist = Histogram(Span<uint32_t>(b, kNumHistogramBins)); + yHist = Histogram(Span<uint32_t>(y, kNumHistogramBins)); +} + +Agc::Agc() + : AgcMeanLuminance() +{ +} + +int Agc::init(IPAContext &context, const YamlObject &tuningData) +{ + int ret = parseTuningData(tuningData); + if (ret) + return ret; + + context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true); + context.ctrlMap[&controls::DigitalGain] = ControlInfo( + static_cast<float>(kMinDigitalGain), + static_cast<float>(kMaxDigitalGain), + static_cast<float>(kMinDigitalGain) + ); + context.ctrlMap.merge(controls()); + + return 0; +} + +int Agc::configure(IPAContext &context, + [[maybe_unused]] const IPACameraSensorInfo &configInfo) +{ + int ret = statistics_.setBayerOrderIndices(context.configuration.sensor.bayerOrder); + if (ret) + return ret; + + /* + * Defaults; we use whatever the sensor's default exposure is and the + * minimum analogue gain. AEGC is _active_ by default. + */ + context.activeState.agc.autoEnabled = true; + context.activeState.agc.automatic.sensorGain = context.configuration.agc.minAnalogueGain; + context.activeState.agc.automatic.exposure = context.configuration.agc.defaultExposure; + context.activeState.agc.automatic.ispGain = kMinDigitalGain; + context.activeState.agc.manual.sensorGain = context.configuration.agc.minAnalogueGain; + context.activeState.agc.manual.exposure = context.configuration.agc.defaultExposure; + context.activeState.agc.manual.ispGain = kMinDigitalGain; + context.activeState.agc.constraintMode = constraintModes().begin()->first; + context.activeState.agc.exposureMode = exposureModeHelpers().begin()->first; + + /* \todo Run this again when FrameDurationLimits is passed in */ + setLimits(context.configuration.agc.minShutterSpeed, + context.configuration.agc.maxShutterSpeed, + context.configuration.agc.minAnalogueGain, + context.configuration.agc.maxAnalogueGain); + + resetFrameCount(); + + return 0; +} + +void Agc::queueRequest(IPAContext &context, const uint32_t frame, + [[maybe_unused]] IPAFrameContext &frameContext, + const ControlList &controls) +{ + auto &agc = context.activeState.agc; + + const auto &constraintMode = controls.get(controls::AeConstraintMode); + agc.constraintMode = constraintMode.value_or(agc.constraintMode); + + const auto &exposureMode = controls.get(controls::AeExposureMode); + agc.exposureMode = exposureMode.value_or(agc.exposureMode); + + const auto &agcEnable = controls.get(controls::AeEnable); + if (agcEnable && *agcEnable != agc.autoEnabled) { + agc.autoEnabled = *agcEnable; + + LOG(MaliC55Agc, Info) + << (agc.autoEnabled ? "Enabling" : "Disabling") + << " AGC"; + } + + /* + * If the automatic exposure and gain is enabled we have no further work + * to do here... + */ + if (agc.autoEnabled) + return; + + /* + * ...otherwise we need to look for exposure and gain controls and use + * those to set the activeState. + */ + const auto &exposure = controls.get(controls::ExposureTime); + if (exposure) { + agc.manual.exposure = *exposure * 1.0us / context.configuration.sensor.lineDuration; + + LOG(MaliC55Agc, Debug) + << "Exposure set to " << agc.manual.exposure + << " on request sequence " << frame; + } + + const auto &analogueGain = controls.get(controls::AnalogueGain); + if (analogueGain) { + agc.manual.sensorGain = *analogueGain; + + LOG(MaliC55Agc, Debug) + << "Analogue gain set to " << agc.manual.sensorGain + << " on request sequence " << frame; + } + + const auto &digitalGain = controls.get(controls::DigitalGain); + if (digitalGain) { + agc.manual.ispGain = *digitalGain; + + LOG(MaliC55Agc, Debug) + << "Digital gain set to " << agc.manual.ispGain + << " on request sequence " << frame; + } +} + +size_t Agc::fillGainParamBlock(IPAContext &context, IPAFrameContext &frameContext, + mali_c55_params_block block) +{ + IPAActiveState &activeState = context.activeState; + double gain; + + if (activeState.agc.autoEnabled) + gain = activeState.agc.automatic.ispGain; + else + gain = activeState.agc.manual.ispGain; + + block.header->type = MALI_C55_PARAM_BLOCK_DIGITAL_GAIN; + block.header->enabled = true; + block.header->size = sizeof(struct mali_c55_params_digital_gain); + + block.digital_gain->gain = int(gain * pow(2, 8)); + frameContext.agc.ispGain = gain; + + return block.header->size; +} + +size_t Agc::fillParamsBuffer(mali_c55_params_block block, + enum mali_c55_param_block_type type) +{ + block.header->type = type; + block.header->enabled = true; + block.header->size = sizeof(struct mali_c55_params_aexp_hist); + + /* Collect every 3rd pixel horizontally */ + block.aexp_hist->skip_x = 1; + /* Start from first column */ + block.aexp_hist->offset_x = 0; + /* Collect every pixel vertically */ + block.aexp_hist->skip_y = 0; + /* Start from the first row */ + block.aexp_hist->offset_y = 0; + /* 1x scaling (i.e. none) */ + block.aexp_hist->scale_bottom = 0; + block.aexp_hist->scale_top = 0; + /* Collect all Bayer planes into 4 separate histograms */ + block.aexp_hist->plane_mode = 1; + /* Tap the data immediately after the digital gain block */ + block.aexp_hist->tap_point = MALI_C55_AEXP_HIST_TAP_FS; + + return block.header->size; +} + +size_t Agc::fillWeightsArrayBuffer(mali_c55_params_block block, + enum mali_c55_param_block_type type) +{ + block.header->type = type; + block.header->enabled = true; + block.header->size = sizeof(struct mali_c55_params_aexp_weights); + + /* We use every zone - a 15x15 grid */ + block.aexp_weights->nodes_used_horiz = 15; + block.aexp_weights->nodes_used_vert = 15; + + /* + * We uniformly weight the zones to 1 - this results in the collected + * histograms containing a true pixel count, which we can then use to + * approximate colour channel averages for the image. + */ + Span<uint8_t> weights{ + block.aexp_weights->zone_weights, + MALI_C55_MAX_ZONES + }; + std::fill(weights.begin(), weights.end(), 1); + + return block.header->size; +} + +void Agc::prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, mali_c55_params_buffer *params) +{ + mali_c55_params_block block; + + block.data = ¶ms->data[params->total_size]; + params->total_size += fillGainParamBlock(context, frameContext, block); + + if (frame > 0) + return; + + block.data = ¶ms->data[params->total_size]; + params->total_size += fillParamsBuffer(block, + MALI_C55_PARAM_BLOCK_AEXP_HIST); + + block.data = ¶ms->data[params->total_size]; + params->total_size += fillWeightsArrayBuffer(block, + MALI_C55_PARAM_BLOCK_AEXP_HIST_WEIGHTS); + + block.data = ¶ms->data[params->total_size]; + params->total_size += fillParamsBuffer(block, + MALI_C55_PARAM_BLOCK_AEXP_IHIST); + + block.data = ¶ms->data[params->total_size]; + params->total_size += fillWeightsArrayBuffer(block, + MALI_C55_PARAM_BLOCK_AEXP_IHIST_WEIGHTS); +} + +double Agc::estimateLuminance(const double gain) const +{ + double rAvg = statistics_.rHist.interQuantileMean(0, 1) * gain; + double gAvg = statistics_.gHist.interQuantileMean(0, 1) * gain; + double bAvg = statistics_.bHist.interQuantileMean(0, 1) * gain; + double yAvg = (rAvg * .299) + (gAvg * .587) + (bAvg * .114); + + return yAvg / kNumHistogramBins; +} + +/** + * The function estimates the correlated color temperature using + * from RGB color space input. + * In physics and color science, the Planckian locus or black body locus is + * the path or locus that the color of an incandescent black body would take + * in a particular chromaticity space as the blackbody temperature changes. + * + * If a narrow range of color temperatures is considered (those encapsulating + * daylight being the most practical case) one can approximate the Planckian + * locus in order to calculate the CCT in terms of chromaticity coordinates. + * + * More detailed information can be found in: + * https://en.wikipedia.org/wiki/Color_temperature#Approximation + */ +uint32_t Agc::estimateCCT() const +{ + double red = statistics_.rHist.interQuantileMean(0, 1); + double green = statistics_.gHist.interQuantileMean(0, 1); + double blue = statistics_.bHist.interQuantileMean(0, 1); + + /* Convert the RGB values to CIE tristimulus values (XYZ) */ + double X = (-0.14282) * (red) + (1.54924) * (green) + (-0.95641) * (blue); + double Y = (-0.32466) * (red) + (1.57837) * (green) + (-0.73191) * (blue); + double Z = (-0.68202) * (red) + (0.77073) * (green) + (0.56332) * (blue); + + /* Calculate the normalized chromaticity values */ + double x = X / (X + Y + Z); + double y = Y / (X + Y + Z); + + /* Calculate CCT */ + double n = (x - 0.3320) / (0.1858 - y); + uint32_t ct = 449 * n * n * n + 3525 * n * n + 6823.3 * n + 5520.33; + + LOG(MaliC55Agc, Debug) << "Estimated Colour Temperature: " << ct; + + return ct; +} + +void Agc::process(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const mali_c55_stats_buffer *stats, + [[maybe_unused]] ControlList &metadata) +{ + IPASessionConfiguration &configuration = context.configuration; + IPAActiveState &activeState = context.activeState; + + if (!stats) { + LOG(MaliC55Agc, Error) << "No statistics buffer passed to Agc"; + return; + } + + statistics_.parseStatistics(stats); + context.activeState.agc.temperatureK = estimateCCT(); + + /* + * The Agc algorithm needs to know the effective exposure value that was + * applied to the sensor when the statistics were collected. + */ + uint32_t exposure = frameContext.agc.exposure; + double analogueGain = frameContext.agc.sensorGain; + double digitalGain = frameContext.agc.ispGain; + double totalGain = analogueGain * digitalGain; + utils::Duration currentShutter = exposure * configuration.sensor.lineDuration; + utils::Duration effectiveExposureValue = currentShutter * totalGain; + + utils::Duration shutterTime; + double aGain, dGain; + std::tie(shutterTime, aGain, dGain) = + calculateNewEv(activeState.agc.constraintMode, + activeState.agc.exposureMode, statistics_.yHist, + effectiveExposureValue); + + dGain = std::clamp(dGain, kMinDigitalGain, kMaxDigitalGain); + + LOG(MaliC55Agc, Debug) + << "Divided up shutter, analogue gain and digital gain are " + << shutterTime << ", " << aGain << " and " << dGain; + + activeState.agc.automatic.exposure = shutterTime / configuration.sensor.lineDuration; + activeState.agc.automatic.sensorGain = aGain; + activeState.agc.automatic.ispGain = dGain; + + metadata.set(controls::ExposureTime, currentShutter.get<std::micro>()); + metadata.set(controls::AnalogueGain, frameContext.agc.sensorGain); + metadata.set(controls::DigitalGain, frameContext.agc.ispGain); + metadata.set(controls::ColourTemperature, context.activeState.agc.temperatureK); +} + +REGISTER_IPA_ALGORITHM(Agc, "Agc") + +} /* namespace ipa::mali_c55::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/mali-c55/algorithms/agc.h b/src/ipa/mali-c55/algorithms/agc.h new file mode 100644 index 00000000..91e257a3 --- /dev/null +++ b/src/ipa/mali-c55/algorithms/agc.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Ideas on Board Oy + * + * agc.h - Mali C55 AGC/AEC mean-based control algorithm + */ + +#pragma once + +#include <libcamera/base/utils.h> + +#include "libcamera/internal/bayer_format.h" + +#include "libipa/agc_mean_luminance.h" +#include "libipa/histogram.h" + +#include "algorithm.h" +#include "ipa_context.h" + +namespace libcamera { + +namespace ipa::mali_c55::algorithms { + +class AgcStatistics +{ +public: + AgcStatistics() + { + } + + int setBayerOrderIndices(BayerFormat::Order bayerOrder); + uint32_t decodeBinValue(uint16_t binVal); + void parseStatistics(const mali_c55_stats_buffer *stats); + + Histogram rHist; + Histogram gHist; + Histogram bHist; + Histogram yHist; +private: + unsigned int rIndex_; + unsigned int grIndex_; + unsigned int gbIndex_; + unsigned int bIndex_; +}; + +class Agc : public Algorithm, public AgcMeanLuminance +{ +public: + Agc(); + ~Agc() = default; + + int init(IPAContext &context, const YamlObject &tuningData) override; + int configure(IPAContext &context, + const IPACameraSensorInfo &configInfo) override; + void queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + mali_c55_params_buffer *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const mali_c55_stats_buffer *stats, + ControlList &metadata) override; + +private: + double estimateLuminance(const double gain) const override; + size_t fillGainParamBlock(IPAContext &context, + IPAFrameContext &frameContext, + mali_c55_params_block block); + size_t fillParamsBuffer(mali_c55_params_block block, + enum mali_c55_param_block_type type); + size_t fillWeightsArrayBuffer(mali_c55_params_block block, + enum mali_c55_param_block_type type); + uint32_t estimateCCT() const; + + AgcStatistics statistics_; +}; + +} /* namespace ipa::mali_c55::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/mali-c55/algorithms/meson.build b/src/ipa/mali-c55/algorithms/meson.build index d84432b9..96808431 100644 --- a/src/ipa/mali-c55/algorithms/meson.build +++ b/src/ipa/mali-c55/algorithms/meson.build @@ -1,5 +1,6 @@ # SPDX-License-Identifier: CC0-1.0 mali_c55_ipa_algorithms = files([ + 'agc.cpp', 'blc.cpp', ]) diff --git a/src/ipa/mali-c55/data/uncalibrated.yaml b/src/ipa/mali-c55/data/uncalibrated.yaml index 2cdc39a8..6dcc0295 100644 --- a/src/ipa/mali-c55/data/uncalibrated.yaml +++ b/src/ipa/mali-c55/data/uncalibrated.yaml @@ -3,4 +3,5 @@ --- version: 1 algorithms: + - Agc: ... diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h index 9e408a17..73a7cd78 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -7,8 +7,11 @@ #pragma once +#include <libcamera/base/utils.h> #include <libcamera/controls.h> +#include "libcamera/internal/bayer_format.h" + #include <libipa/fc_queue.h> namespace libcamera { @@ -16,15 +19,44 @@ namespace libcamera { namespace ipa::mali_c55 { struct IPASessionConfiguration { + struct { + utils::Duration minShutterSpeed; + utils::Duration maxShutterSpeed; + uint32_t defaultExposure; + double minAnalogueGain; + double maxAnalogueGain; + } agc; + + struct { + BayerFormat::Order bayerOrder; + utils::Duration lineDuration; + } sensor; }; struct IPAActiveState { + struct { + struct { + uint32_t exposure; + double sensorGain; + double ispGain; + } automatic; + struct { + uint32_t exposure; + double sensorGain; + double ispGain; + } manual; + bool autoEnabled; + uint32_t constraintMode; + uint32_t exposureMode; + uint32_t temperatureK; + } agc; }; struct IPAFrameContext : public FrameContext { struct { uint32_t exposure; double sensorGain; + double ispGain; } agc; }; diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp index 3daac3af..56936b86 100644 --- a/src/ipa/mali-c55/mali-c55.cpp +++ b/src/ipa/mali-c55/mali-c55.cpp @@ -33,6 +33,8 @@ namespace libcamera { LOG_DEFINE_CATEGORY(IPAMaliC55) +using namespace std::literals::chrono_literals; + namespace ipa::mali_c55 { /* Maximum number of frame contexts to be held */ @@ -60,6 +62,9 @@ protected: std::string logPrefix() const override; private: + void updateSessionConfiguration(const IPACameraSensorInfo &info, + const ControlInfoMap &sensorControls, + BayerFormat::Order bayerOrder); void updateControls(const IPACameraSensorInfo &sensorInfo, const ControlInfoMap &sensorControls, ControlInfoMap *ipaControls); @@ -133,7 +138,21 @@ int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig void IPAMaliC55::setControls() { + IPAActiveState &activeState = context_.activeState; + uint32_t exposure; + uint32_t gain; + + if (activeState.agc.autoEnabled) { + exposure = activeState.agc.automatic.exposure; + gain = camHelper_->gainCode(activeState.agc.automatic.sensorGain); + } else { + exposure = activeState.agc.manual.exposure; + gain = camHelper_->gainCode(activeState.agc.manual.sensorGain); + } + ControlList ctrls(sensorControls_); + ctrls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure)); + ctrls.set(V4L2_CID_ANALOGUE_GAIN, static_cast<int32_t>(gain)); setSensorControls.emit(ctrls); } @@ -148,6 +167,36 @@ void IPAMaliC55::stop() context_.frameContexts.clear(); } +void IPAMaliC55::updateSessionConfiguration(const IPACameraSensorInfo &info, + const ControlInfoMap &sensorControls, + BayerFormat::Order bayerOrder) +{ + context_.configuration.sensor.bayerOrder = bayerOrder; + + const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second; + int32_t minExposure = v4l2Exposure.min().get<int32_t>(); + int32_t maxExposure = v4l2Exposure.max().get<int32_t>(); + int32_t defExposure = v4l2Exposure.def().get<int32_t>(); + + const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; + int32_t minGain = v4l2Gain.min().get<int32_t>(); + int32_t maxGain = v4l2Gain.max().get<int32_t>(); + + /* + * When the AGC computes the new exposure values for a frame, it needs + * to know the limits for shutter speed and analogue gain. + * As it depends on the sensor, update it with the controls. + * + * \todo take VBLANK into account for maximum shutter speed + */ + context_.configuration.sensor.lineDuration = info.minLineLength * 1.0s / info.pixelRate; + context_.configuration.agc.minShutterSpeed = minExposure * context_.configuration.sensor.lineDuration; + context_.configuration.agc.maxShutterSpeed = maxExposure * context_.configuration.sensor.lineDuration; + context_.configuration.agc.defaultExposure = defExposure; + context_.configuration.agc.minAnalogueGain = camHelper_->gain(minGain); + context_.configuration.agc.maxAnalogueGain = camHelper_->gain(maxGain); +} + void IPAMaliC55::updateControls(const IPACameraSensorInfo &sensorInfo, const ControlInfoMap &sensorControls, ControlInfoMap *ipaControls) @@ -209,8 +258,7 @@ void IPAMaliC55::updateControls(const IPACameraSensorInfo &sensorInfo, *ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls); } -int IPAMaliC55::configure(const IPAConfigInfo &ipaConfig, - [[maybe_unused]] uint8_t bayerOrder, +int IPAMaliC55::configure(const IPAConfigInfo &ipaConfig, uint8_t bayerOrder, ControlInfoMap *ipaControls) { sensorControls_ = ipaConfig.sensorControls; @@ -222,6 +270,8 @@ int IPAMaliC55::configure(const IPAConfigInfo &ipaConfig, const IPACameraSensorInfo &info = ipaConfig.sensorInfo; + updateSessionConfiguration(info, ipaConfig.sensorControls, + static_cast<BayerFormat::Order>(bayerOrder)); updateControls(info, ipaConfig.sensorControls, ipaControls); for (auto const &a : algorithms()) {