| Message ID | 20260615-libipa-algorithms-v1-2-e949c937422e@ideasonboard.com |
|---|---|
| State | Superseded |
| Headers | show |
| Series |
|
| Related | show |
Quoting Jacopo Mondi (2026-06-15 15:05:27) > Port the Mali-C55 Awb algorithm to use the new libipa implementation > of AwbAlgorithm. > > The awbAlgo_ class member is initialized with the Q<4, 8> type that > represents the register format and the MaliC55AwbStats type handles > the WB statistics format as produced by the Mali-C55 which already > calculates the mean values as R/G and B/G values. > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > --- > src/ipa/mali-c55/algorithms/awb.cpp | 198 ++++++++++++++++++------------------ > src/ipa/mali-c55/algorithms/awb.h | 30 ++++-- > src/ipa/mali-c55/ipa_context.cpp | 6 ++ > src/ipa/mali-c55/ipa_context.h | 17 ++-- > src/ipa/mali-c55/mali-c55.cpp | 27 +++-- > 5 files changed, 152 insertions(+), 126 deletions(-) > > diff --git a/src/ipa/mali-c55/algorithms/awb.cpp b/src/ipa/mali-c55/algorithms/awb.cpp > index 8a671b52be59..97964f395325 100644 > --- a/src/ipa/mali-c55/algorithms/awb.cpp > +++ b/src/ipa/mali-c55/algorithms/awb.cpp > @@ -2,7 +2,7 @@ > /* > * Copyright (C) 2024, Ideas On Board Oy > * > - * Mali C55 grey world auto white balance algorithm > + * Mali C55 auto white balance algorithm > */ > > #include "awb.h" > @@ -14,63 +14,65 @@ > > #include <libcamera/control_ids.h> > > -#include "libipa/fixedpoint.h" > - > namespace libcamera { > > namespace ipa::mali_c55::algorithms { > > LOG_DEFINE_CATEGORY(MaliC55Awb) > > -/* Number of frames at which we should run AWB at full speed */ > -static constexpr uint32_t kNumStartupFrames = 4; > +/* \todo Mali-C55 doesn't support the Lux algorithm. */ > +static constexpr unsigned int kDefaultLux = 500; > + > +class MaliC55AwbStats final : public AwbStats > +{ > +public: > + MaliC55AwbStats() = default; > + MaliC55AwbStats(const RGB<double> &rgbMeans) > + : AwbStats(rgbMeans) > + { > + /* The Mali-C55 ISP already provides stats as R/G and B/G ratios. */ > + rg_ = rgbMeans_.r(); > + bg_ = rgbMeans_.b(); > + } > > -Awb::Awb() > + /* Minimum mean value below which AWB can't operate. */ > + double minColourValue() const override > + { > + return 0.2; > + } > +}; > + > +/** > + * \copydoc libcamera::ipa::Algorithm::init > + */ > +int Awb::init(IPAContext &context, const ValueNode &tuningData) > { > + return awbAlgo_.init(tuningData, context.ctrlMap); > } > > -int Awb::configure([[maybe_unused]] IPAContext &context, > +/** > + * \copydoc libcamera::ipa::Algorithm::configure > + */ > +int Awb::configure(IPAContext &context, > [[maybe_unused]] const IPACameraSensorInfo &configInfo) > { > - /* > - * Initially we have no idea what the colour balance will be like, so > - * for the first frame we will make no assumptions and leave the R/B > - * channels unmodified. > - */ > - context.activeState.awb.rGain = 1.0f; > - context.activeState.awb.bGain = 1.0f; > - > - return 0; > + return awbAlgo_.configure(context.activeState.awb, > + context.configuration.awb); > } > > -void Awb::fillGainsParamBlock(MaliC55Params *params, IPAContext &context, > - IPAFrameContext &frameContext) > +/** > + * \copydoc libcamera::ipa::Algorithm::queueRequest > + */ > +void Awb::queueRequest(IPAContext &context, > + const uint32_t frame, > + IPAFrameContext &frameContext, > + const ControlList &controls) > { > - UQ<4, 8> rGain = context.activeState.awb.rGain; > - UQ<4, 8> bGain = context.activeState.awb.bGain; > - > - /* > - * The gains here map as follows: > - * gain00 = R > - * gain01 = Gr > - * gain10 = Gb > - * gain11 = B > - * > - * This holds true regardless of the bayer order of the input data, as > - * the mapping is done internally in the ISP. > - */ > - auto block = params->block<MaliC55Blocks::AwbGains>(); > - > - block->gain00 = rGain.quantized(); > - block->gain01 = UQ<4, 8>(1.0f).quantized(); > - block->gain10 = UQ<4, 8>(1.0f).quantized(); > - block->gain11 = bGain.quantized(); > - > - frameContext.awb.rGain = rGain; > - frameContext.awb.bGain = bGain; > + awbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb, > + controls); > } > > -void Awb::fillConfigParamBlock(MaliC55Params *params) > +void Awb::configAwbMeas(MaliC55Params *params) Is 'Meas' correct ? Perhaps configureAwbMeans() > { > auto block = params->block<MaliC55Blocks::AwbConfig>(); > > @@ -117,56 +119,77 @@ void Awb::fillConfigParamBlock(MaliC55Params *params) > block->cb_low = 64; > } > > +/** > + * \copydoc libcamera::ipa::Algorithm::prepare > + */ > void Awb::prepare(IPAContext &context, const uint32_t frame, > IPAFrameContext &frameContext, MaliC55Params *params) > { > - fillGainsParamBlock(params, context, frameContext); > + awbAlgo_.prepare(context.activeState.awb, frameContext.awb); > + > + /* > + * The gains here map as follows: > + * gain00 = R > + * gain01 = Gr > + * gain10 = Gb > + * gain11 = B > + * > + * This holds true regardless of the bayer order of the input data, as > + * the mapping is done internally in the ISP. > + */ > + auto block = params->block<MaliC55Blocks::AwbGains>(); > + block.setEnabled(true); > + > + block->gain00 = UQ<4, 8>(static_cast<float>(frameContext.awb.gains.r())) > + .quantized(); I really wonder if we should just use float in the libipa AWB implementation. I can't see why we're using double there ... > + block->gain01 = UQ<4, 8>(1.0f).quantized(); > + block->gain10 = UQ<4, 8>(1.0f).quantized(); > + block->gain11 = UQ<4, 8>(static_cast<float>(frameContext.awb.gains.b())) > + .quantized(); > > if (frame > 0) > return; > > - fillConfigParamBlock(params); > + configAwbMeas(params); > } > > -void Awb::process(IPAContext &context, const uint32_t frame, > - IPAFrameContext &frameContext, const mali_c55_stats_buffer *stats, > - [[maybe_unused]] ControlList &metadata) > +MaliC55AwbStats Awb::calculateRgbMeans(const IPAFrameContext &frameContext, > + const mali_c55_stats_buffer *stats) const > { > - const struct mali_c55_awb_average_ratios *awb_ratios = stats->awb_ratios; > + const struct mali_c55_awb_average_ratios *awb = stats->awb_ratios; > > /* > * The ISP produces average R:G and B:G ratios for zones. We take the > - * average of all the zones with data and simply invert them to provide > - * gain figures that we can apply to approximate a grey world. > + * average of all the zones with data and calculate the mean values. > */ > - unsigned int counted_zones = 0; > - float rgSum = 0, bgSum = 0; > + unsigned int active_zones = 0; > + double rgSum = 0, bgSum = 0; > > for (unsigned int i = 0; i < 225; i++) { > - if (!awb_ratios[i].num_pixels) > + if (!awb[i].num_pixels) > continue; > > /* > - * The statistics are in Q4.8 format, so we convert to float > + * The statistics are in Q4.8 format, so we convert to double > * here. > */ > - rgSum += UQ<4, 8>(awb_ratios[i].avg_rg_gr).value(); > - bgSum += UQ<4, 8>(awb_ratios[i].avg_bg_br).value(); > - counted_zones++; > + rgSum += UQ<4, 8>(awb[i].avg_rg_gr).value(); > + bgSum += UQ<4, 8>(awb[i].avg_bg_br).value(); > + active_zones++; > } > > /* > * Sometimes the first frame's statistics have no valid pixels, in which > * case we'll just assume a grey world until they say otherwise. > */ > - float rgAvg, bgAvg; > - if (!counted_zones) { > - rgAvg = 1.0; > - bgAvg = 1.0; > - } else { > - rgAvg = rgSum / counted_zones; > - bgAvg = bgSum / counted_zones; > - } > + if (!active_zones) > + return {}; > + > + RGB<double> rgbMeans = { { > + rgSum / active_zones, > + 1.0, > + bgSum / active_zones, > + } }; > > /* > * The statistics are generated _after_ white balancing is performed in > @@ -174,41 +197,22 @@ void Awb::process(IPAContext &context, const uint32_t frame, > * figure by the gains that were applied when the statistics for this > * frame were generated. > */ > - float rRatio = rgAvg / frameContext.awb.rGain.value(); > - float bRatio = bgAvg / frameContext.awb.bGain.value(); > + rgbMeans /= frameContext.awb.gains.max(0.01); > > - /* > - * And then we can simply invert the ratio to find the gain we should > - * apply. > - */ > - float rGain = 1 / rRatio; > - float bGain = 1 / bRatio; > + return MaliC55AwbStats(rgbMeans); > +} > > - /* > - * Running at full speed, this algorithm results in oscillations in the > - * colour balance. To remove those we dampen the speed at which it makes > - * changes in gain, unless we're in the startup phase in which case we > - * want to fix the miscolouring as quickly as possible. > - */ > - float speed = frame < kNumStartupFrames ? 1.0f : 0.2f; > - rGain = speed * rGain + context.activeState.awb.rGain.value() * (1.0f - speed); > - bGain = speed * bGain + context.activeState.awb.bGain.value() * (1.0f - speed); > - > - context.activeState.awb.rGain = rGain; > - context.activeState.awb.bGain = bGain; > - > - metadata.set(controls::ColourGains, { > - frameContext.awb.rGain.value(), > - frameContext.awb.bGain.value(), > - }); > - > - LOG(MaliC55Awb, Debug) << "For frame number " << frame << ": " > - << "Average R/G Ratio: " << rgAvg > - << ", Average B/G Ratio: " << bgAvg > - << "\nrGain applied to this frame: " << frameContext.awb.rGain > - << ", bGain applied to this frame: " << frameContext.awb.bGain > - << "\nrGain to apply: " << context.activeState.awb.rGain > - << ", bGain to apply: " << context.activeState.awb.bGain; > +/** > + * \copydoc libcamera::ipa::Algorithm::process > + */ > +void Awb::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, > + IPAFrameContext &frameContext, const mali_c55_stats_buffer *stats, > + ControlList &metadata) > +{ > + MaliC55AwbStats awbStats = calculateRgbMeans(frameContext, stats); > + > + awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats, > + kDefaultLux, metadata); > } > > REGISTER_IPA_ALGORITHM(Awb, "Awb") > diff --git a/src/ipa/mali-c55/algorithms/awb.h b/src/ipa/mali-c55/algorithms/awb.h > index 683a62af263a..cc7ef583eafc 100644 > --- a/src/ipa/mali-c55/algorithms/awb.h > +++ b/src/ipa/mali-c55/algorithms/awb.h > @@ -2,24 +2,41 @@ > /* > * Copyright (C) 2024, Ideas on Board Oy > * > - * Mali C55 grey world auto white balance algorithm > + * Mali C55 auto white balance algorithm > */ > > +#pragma once > + > +#include <linux/media/arm/mali-c55-config.h> > + > +#include <libcamera/controls.h> > + > +#include "libcamera/internal/value_node.h" > + > +#include "libipa/awb.h" > +#include "libipa/fixedpoint.h" > + > #include "algorithm.h" > #include "ipa_context.h" > +#include "params.h" > > namespace libcamera { > > namespace ipa::mali_c55::algorithms { > > +class MaliC55AwbStats; > + > class Awb : public Algorithm > { > public: > - Awb(); > ~Awb() = default; > > + int init(IPAContext &context, const ValueNode &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, > MaliC55Params *params) override; > @@ -29,10 +46,11 @@ public: > ControlList &metadata) override; > > private: > - void fillGainsParamBlock(MaliC55Params *params, > - IPAContext &context, > - IPAFrameContext &frameContext); > - void fillConfigParamBlock(MaliC55Params *params); > + void configAwbMeas(MaliC55Params *params); > + MaliC55AwbStats calculateRgbMeans(const IPAFrameContext &frameContext, > + const mali_c55_stats_buffer *stats) const; > + > + AwbAlgorithm<UQ<4, 8>> awbAlgo_; > }; > > } /* namespace ipa::mali_c55::algorithms */ > diff --git a/src/ipa/mali-c55/ipa_context.cpp b/src/ipa/mali-c55/ipa_context.cpp > index 1b203e2b2605..a1e4c39f3870 100644 > --- a/src/ipa/mali-c55/ipa_context.cpp > +++ b/src/ipa/mali-c55/ipa_context.cpp > @@ -96,6 +96,12 @@ namespace libcamera::ipa::mali_c55 { > * > * \var IPAContext::frameContexts > * \brief Ring buffer of per-frame contexts > + * > + * \var IPAContext::ctrlMap > + * \brief A ControlInfoMap::Map of controls populated by the algorithms > + * > + * \var IPAContext::camHelper > + * \brief The camera sensor helper > */ > > } /* namespace libcamera::ipa::mali_c55 */ > diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h > index ac4b83773803..3d884ea17eb8 100644 > --- a/src/ipa/mali-c55/ipa_context.h > +++ b/src/ipa/mali-c55/ipa_context.h > @@ -12,8 +12,10 @@ > > #include "libcamera/internal/bayer_format.h" > > +#include <libipa/camera_sensor_helper.h> > #include <libipa/fc_queue.h> > > +#include "libipa/awb.h" > #include "libipa/fixedpoint.h" > > namespace libcamera { > @@ -29,6 +31,8 @@ struct IPASessionConfiguration { > double maxAnalogueGain; > } agc; > > + ipa::awb::Session awb; > + > struct { > BayerFormat::Order bayerOrder; > utils::Duration lineDuration; > @@ -54,10 +58,7 @@ struct IPAActiveState { > uint32_t temperatureK; > } agc; > > - struct { > - UQ<4, 8> rGain; > - UQ<4, 8> bGain; > - } awb; > + ipa::awb::ActiveState awb; > }; > > struct IPAFrameContext : public FrameContext { > @@ -67,10 +68,7 @@ struct IPAFrameContext : public FrameContext { > UQ<5, 8> ispGain; > } agc; > > - struct { > - UQ<4, 8> rGain; > - UQ<4, 8> bGain; > - } awb; > + ipa::awb::FrameContext awb; > }; > > struct IPAContext { > @@ -85,6 +83,9 @@ struct IPAContext { > FCQueue<IPAFrameContext> frameContexts; > > ControlInfoMap::Map ctrlMap; > + > + /* Interface to the Camera Helper */ > + std::unique_ptr<CameraSensorHelper> camHelper; > }; > > } /* namespace ipa::mali_c55 */ > diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp > index 1d3af0627fdb..c35cc10bea01 100644 > --- a/src/ipa/mali-c55/mali-c55.cpp > +++ b/src/ipa/mali-c55/mali-c55.cpp > @@ -77,9 +77,6 @@ private: > > ControlInfoMap sensorControls_; > > - /* Interface to the Camera Helper */ > - std::unique_ptr<CameraSensorHelper> camHelper_; > - > /* Local parameter storage */ > struct IPAContext context_; > }; > @@ -101,8 +98,8 @@ std::string IPAMaliC55::logPrefix() const > int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig, > ControlInfoMap *ipaControls) > { > - camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); > - if (!camHelper_) { > + context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel); > + if (!context_.camHelper) { I had this split out during: - https://patchwork.libcamera.org/project/libcamera/list/?series=5486&state=* - https://patchwork.libcamera.org/patch/24590/ Which already had your review by tag too ;-) But I guess it's fine in here. > LOG(IPAMaliC55, Error) > << "Failed to create camera sensor helper for " > << settings.sensorModel; > @@ -145,10 +142,10 @@ void IPAMaliC55::setControls() > > if (activeState.agc.autoEnabled) { > exposure = activeState.agc.automatic.exposure; > - gain = camHelper_->gainCode(activeState.agc.automatic.sensorGain); > + gain = context_.camHelper->gainCode(activeState.agc.automatic.sensorGain); > } else { > exposure = activeState.agc.manual.exposure; > - gain = camHelper_->gainCode(activeState.agc.manual.sensorGain); > + gain = context_.camHelper->gainCode(activeState.agc.manual.sensorGain); > } > > ControlList ctrls(sensorControls_); > @@ -194,17 +191,17 @@ void IPAMaliC55::updateSessionConfiguration(const IPACameraSensorInfo &info, > 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); > + context_.configuration.agc.minAnalogueGain = context_.camHelper->gain(minGain); > + context_.configuration.agc.maxAnalogueGain = context_.camHelper->gain(maxGain); > > - if (camHelper_->blackLevel().has_value()) { > + if (context_.camHelper->blackLevel().has_value()) { I guess we should rebase https://patchwork.libcamera.org/patch/24591/ too. But that can be on top of this series now. My only thoughts are really that we are doing a lot to convert doubles and floats and it all feels a bit unnecessary to work on a value that will be clamped to a UQ4,8 (12 bit preceision) while float has an equivalent of 24 bit precision, and double gives 53. And perhaps the patch itself could be split up - but I can look the otherway on either case so: Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > /* > * The black level from CameraSensorHelper is a 16-bit value. > * The Mali-C55 ISP expects 20-bit settings, so we shift it to > * the appropriate width > */ > context_.configuration.sensor.blackLevel = > - camHelper_->blackLevel().value() << 4; > + context_.camHelper->blackLevel().value() << 4; > } > } > > @@ -255,9 +252,9 @@ void IPAMaliC55::updateControls(const IPACameraSensorInfo &sensorInfo, > > /* Compute the analogue gain limits. */ > const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; > - float minGain = camHelper_->gain(v4l2Gain.min().get<int32_t>()); > - float maxGain = camHelper_->gain(v4l2Gain.max().get<int32_t>()); > - float defGain = camHelper_->gain(v4l2Gain.def().get<int32_t>()); > + float minGain = context_.camHelper->gain(v4l2Gain.min().get<int32_t>()); > + float maxGain = context_.camHelper->gain(v4l2Gain.max().get<int32_t>()); > + float defGain = context_.camHelper->gain(v4l2Gain.def().get<int32_t>()); > ctrlMap[&controls::AnalogueGain] = ControlInfo(minGain, maxGain, defGain); > > /* > @@ -355,7 +352,7 @@ void IPAMaliC55::processStats(unsigned int request, unsigned int bufferId, > frameContext.agc.exposure = > sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>(); > frameContext.agc.sensorGain = > - camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>()); > + context_.camHelper->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>()); > > ControlList metadata(controls::controls); > > > -- > 2.54.0 >
Hi Kieran On Tue, Jun 16, 2026 at 12:55:32PM +0100, Kieran Bingham wrote: > Quoting Jacopo Mondi (2026-06-15 15:05:27) > > Port the Mali-C55 Awb algorithm to use the new libipa implementation > > of AwbAlgorithm. > > > > The awbAlgo_ class member is initialized with the Q<4, 8> type that > > represents the register format and the MaliC55AwbStats type handles > > the WB statistics format as produced by the Mali-C55 which already > > calculates the mean values as R/G and B/G values. > > > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > > --- > > src/ipa/mali-c55/algorithms/awb.cpp | 198 ++++++++++++++++++------------------ > > src/ipa/mali-c55/algorithms/awb.h | 30 ++++-- > > src/ipa/mali-c55/ipa_context.cpp | 6 ++ > > src/ipa/mali-c55/ipa_context.h | 17 ++-- > > src/ipa/mali-c55/mali-c55.cpp | 27 +++-- > > 5 files changed, 152 insertions(+), 126 deletions(-) > > > > diff --git a/src/ipa/mali-c55/algorithms/awb.cpp b/src/ipa/mali-c55/algorithms/awb.cpp > > index 8a671b52be59..97964f395325 100644 > > --- a/src/ipa/mali-c55/algorithms/awb.cpp > > +++ b/src/ipa/mali-c55/algorithms/awb.cpp > > @@ -2,7 +2,7 @@ > > /* > > * Copyright (C) 2024, Ideas On Board Oy > > * > > - * Mali C55 grey world auto white balance algorithm > > + * Mali C55 auto white balance algorithm > > */ > > > > #include "awb.h" > > @@ -14,63 +14,65 @@ > > > > #include <libcamera/control_ids.h> > > > > -#include "libipa/fixedpoint.h" > > - > > namespace libcamera { > > > > namespace ipa::mali_c55::algorithms { > > > > LOG_DEFINE_CATEGORY(MaliC55Awb) > > > > -/* Number of frames at which we should run AWB at full speed */ > > -static constexpr uint32_t kNumStartupFrames = 4; > > +/* \todo Mali-C55 doesn't support the Lux algorithm. */ > > +static constexpr unsigned int kDefaultLux = 500; > > + > > +class MaliC55AwbStats final : public AwbStats > > +{ > > +public: > > + MaliC55AwbStats() = default; > > + MaliC55AwbStats(const RGB<double> &rgbMeans) > > + : AwbStats(rgbMeans) > > + { > > + /* The Mali-C55 ISP already provides stats as R/G and B/G ratios. */ > > + rg_ = rgbMeans_.r(); > > + bg_ = rgbMeans_.b(); > > + } > > > > -Awb::Awb() > > + /* Minimum mean value below which AWB can't operate. */ > > + double minColourValue() const override > > + { > > + return 0.2; > > + } > > +}; > > + > > +/** > > + * \copydoc libcamera::ipa::Algorithm::init > > + */ > > +int Awb::init(IPAContext &context, const ValueNode &tuningData) > > { > > + return awbAlgo_.init(tuningData, context.ctrlMap); > > } > > > > -int Awb::configure([[maybe_unused]] IPAContext &context, > > +/** > > + * \copydoc libcamera::ipa::Algorithm::configure > > + */ > > +int Awb::configure(IPAContext &context, > > [[maybe_unused]] const IPACameraSensorInfo &configInfo) > > { > > - /* > > - * Initially we have no idea what the colour balance will be like, so > > - * for the first frame we will make no assumptions and leave the R/B > > - * channels unmodified. > > - */ > > - context.activeState.awb.rGain = 1.0f; > > - context.activeState.awb.bGain = 1.0f; > > - > > - return 0; > > + return awbAlgo_.configure(context.activeState.awb, > > + context.configuration.awb); > > } > > > > -void Awb::fillGainsParamBlock(MaliC55Params *params, IPAContext &context, > > - IPAFrameContext &frameContext) > > +/** > > + * \copydoc libcamera::ipa::Algorithm::queueRequest > > + */ > > +void Awb::queueRequest(IPAContext &context, > > + const uint32_t frame, > > + IPAFrameContext &frameContext, > > + const ControlList &controls) > > { > > - UQ<4, 8> rGain = context.activeState.awb.rGain; > > - UQ<4, 8> bGain = context.activeState.awb.bGain; > > - > > - /* > > - * The gains here map as follows: > > - * gain00 = R > > - * gain01 = Gr > > - * gain10 = Gb > > - * gain11 = B > > - * > > - * This holds true regardless of the bayer order of the input data, as > > - * the mapping is done internally in the ISP. > > - */ > > - auto block = params->block<MaliC55Blocks::AwbGains>(); > > - > > - block->gain00 = rGain.quantized(); > > - block->gain01 = UQ<4, 8>(1.0f).quantized(); > > - block->gain10 = UQ<4, 8>(1.0f).quantized(); > > - block->gain11 = bGain.quantized(); > > - > > - frameContext.awb.rGain = rGain; > > - frameContext.awb.bGain = bGain; > > + awbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb, > > + controls); > > } > > > > -void Awb::fillConfigParamBlock(MaliC55Params *params) > > +void Awb::configAwbMeas(MaliC55Params *params) > > Is 'Meas' correct ? Perhaps configureAwbMeans() > It's intentional for "Measurement". Should I change it ? > > { > > auto block = params->block<MaliC55Blocks::AwbConfig>(); > > > > @@ -117,56 +119,77 @@ void Awb::fillConfigParamBlock(MaliC55Params *params) > > block->cb_low = 64; > > } > > > > +/** > > + * \copydoc libcamera::ipa::Algorithm::prepare > > + */ > > void Awb::prepare(IPAContext &context, const uint32_t frame, > > IPAFrameContext &frameContext, MaliC55Params *params) > > { > > - fillGainsParamBlock(params, context, frameContext); > > + awbAlgo_.prepare(context.activeState.awb, frameContext.awb); > > + > > + /* > > + * The gains here map as follows: > > + * gain00 = R > > + * gain01 = Gr > > + * gain10 = Gb > > + * gain11 = B > > + * > > + * This holds true regardless of the bayer order of the input data, as > > + * the mapping is done internally in the ISP. > > + */ > > + auto block = params->block<MaliC55Blocks::AwbGains>(); > > + block.setEnabled(true); > > + > > + block->gain00 = UQ<4, 8>(static_cast<float>(frameContext.awb.gains.r())) > > + .quantized(); > > I really wonder if we should just use float in the libipa AWB > implementation. I can't see why we're using double there ... > I don't know, I think that's what we had already, didn't we ? > > + block->gain01 = UQ<4, 8>(1.0f).quantized(); > > + block->gain10 = UQ<4, 8>(1.0f).quantized(); > > + block->gain11 = UQ<4, 8>(static_cast<float>(frameContext.awb.gains.b())) > > + .quantized(); > > > > if (frame > 0) > > return; > > > > - fillConfigParamBlock(params); > > + configAwbMeas(params); > > } > > > > -void Awb::process(IPAContext &context, const uint32_t frame, > > - IPAFrameContext &frameContext, const mali_c55_stats_buffer *stats, > > - [[maybe_unused]] ControlList &metadata) > > +MaliC55AwbStats Awb::calculateRgbMeans(const IPAFrameContext &frameContext, > > + const mali_c55_stats_buffer *stats) const > > { > > - const struct mali_c55_awb_average_ratios *awb_ratios = stats->awb_ratios; > > + const struct mali_c55_awb_average_ratios *awb = stats->awb_ratios; > > > > /* > > * The ISP produces average R:G and B:G ratios for zones. We take the > > - * average of all the zones with data and simply invert them to provide > > - * gain figures that we can apply to approximate a grey world. > > + * average of all the zones with data and calculate the mean values. > > */ > > - unsigned int counted_zones = 0; > > - float rgSum = 0, bgSum = 0; > > + unsigned int active_zones = 0; > > + double rgSum = 0, bgSum = 0; > > > > for (unsigned int i = 0; i < 225; i++) { > > - if (!awb_ratios[i].num_pixels) > > + if (!awb[i].num_pixels) > > continue; > > > > /* > > - * The statistics are in Q4.8 format, so we convert to float > > + * The statistics are in Q4.8 format, so we convert to double > > * here. > > */ > > - rgSum += UQ<4, 8>(awb_ratios[i].avg_rg_gr).value(); > > - bgSum += UQ<4, 8>(awb_ratios[i].avg_bg_br).value(); > > - counted_zones++; > > + rgSum += UQ<4, 8>(awb[i].avg_rg_gr).value(); > > + bgSum += UQ<4, 8>(awb[i].avg_bg_br).value(); > > + active_zones++; > > } > > > > /* > > * Sometimes the first frame's statistics have no valid pixels, in which > > * case we'll just assume a grey world until they say otherwise. > > */ > > - float rgAvg, bgAvg; > > - if (!counted_zones) { > > - rgAvg = 1.0; > > - bgAvg = 1.0; > > - } else { > > - rgAvg = rgSum / counted_zones; > > - bgAvg = bgSum / counted_zones; > > - } > > + if (!active_zones) > > + return {}; > > + > > + RGB<double> rgbMeans = { { > > + rgSum / active_zones, > > + 1.0, > > + bgSum / active_zones, > > + } }; > > > > /* > > * The statistics are generated _after_ white balancing is performed in > > @@ -174,41 +197,22 @@ void Awb::process(IPAContext &context, const uint32_t frame, > > * figure by the gains that were applied when the statistics for this > > * frame were generated. > > */ > > - float rRatio = rgAvg / frameContext.awb.rGain.value(); > > - float bRatio = bgAvg / frameContext.awb.bGain.value(); > > + rgbMeans /= frameContext.awb.gains.max(0.01); > > > > - /* > > - * And then we can simply invert the ratio to find the gain we should > > - * apply. > > - */ > > - float rGain = 1 / rRatio; > > - float bGain = 1 / bRatio; > > + return MaliC55AwbStats(rgbMeans); > > +} > > > > - /* > > - * Running at full speed, this algorithm results in oscillations in the > > - * colour balance. To remove those we dampen the speed at which it makes > > - * changes in gain, unless we're in the startup phase in which case we > > - * want to fix the miscolouring as quickly as possible. > > - */ > > - float speed = frame < kNumStartupFrames ? 1.0f : 0.2f; > > - rGain = speed * rGain + context.activeState.awb.rGain.value() * (1.0f - speed); > > - bGain = speed * bGain + context.activeState.awb.bGain.value() * (1.0f - speed); > > - > > - context.activeState.awb.rGain = rGain; > > - context.activeState.awb.bGain = bGain; > > - > > - metadata.set(controls::ColourGains, { > > - frameContext.awb.rGain.value(), > > - frameContext.awb.bGain.value(), > > - }); > > - > > - LOG(MaliC55Awb, Debug) << "For frame number " << frame << ": " > > - << "Average R/G Ratio: " << rgAvg > > - << ", Average B/G Ratio: " << bgAvg > > - << "\nrGain applied to this frame: " << frameContext.awb.rGain > > - << ", bGain applied to this frame: " << frameContext.awb.bGain > > - << "\nrGain to apply: " << context.activeState.awb.rGain > > - << ", bGain to apply: " << context.activeState.awb.bGain; > > +/** > > + * \copydoc libcamera::ipa::Algorithm::process > > + */ > > +void Awb::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, > > + IPAFrameContext &frameContext, const mali_c55_stats_buffer *stats, > > + ControlList &metadata) > > +{ > > + MaliC55AwbStats awbStats = calculateRgbMeans(frameContext, stats); > > + > > + awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats, > > + kDefaultLux, metadata); > > } > > > > REGISTER_IPA_ALGORITHM(Awb, "Awb") > > diff --git a/src/ipa/mali-c55/algorithms/awb.h b/src/ipa/mali-c55/algorithms/awb.h > > index 683a62af263a..cc7ef583eafc 100644 > > --- a/src/ipa/mali-c55/algorithms/awb.h > > +++ b/src/ipa/mali-c55/algorithms/awb.h > > @@ -2,24 +2,41 @@ > > /* > > * Copyright (C) 2024, Ideas on Board Oy > > * > > - * Mali C55 grey world auto white balance algorithm > > + * Mali C55 auto white balance algorithm > > */ > > > > +#pragma once > > + > > +#include <linux/media/arm/mali-c55-config.h> > > + > > +#include <libcamera/controls.h> > > + > > +#include "libcamera/internal/value_node.h" > > + > > +#include "libipa/awb.h" > > +#include "libipa/fixedpoint.h" > > + > > #include "algorithm.h" > > #include "ipa_context.h" > > +#include "params.h" > > > > namespace libcamera { > > > > namespace ipa::mali_c55::algorithms { > > > > +class MaliC55AwbStats; > > + > > class Awb : public Algorithm > > { > > public: > > - Awb(); > > ~Awb() = default; > > > > + int init(IPAContext &context, const ValueNode &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, > > MaliC55Params *params) override; > > @@ -29,10 +46,11 @@ public: > > ControlList &metadata) override; > > > > private: > > - void fillGainsParamBlock(MaliC55Params *params, > > - IPAContext &context, > > - IPAFrameContext &frameContext); > > - void fillConfigParamBlock(MaliC55Params *params); > > + void configAwbMeas(MaliC55Params *params); > > + MaliC55AwbStats calculateRgbMeans(const IPAFrameContext &frameContext, > > + const mali_c55_stats_buffer *stats) const; > > + > > + AwbAlgorithm<UQ<4, 8>> awbAlgo_; > > }; > > > > } /* namespace ipa::mali_c55::algorithms */ > > diff --git a/src/ipa/mali-c55/ipa_context.cpp b/src/ipa/mali-c55/ipa_context.cpp > > index 1b203e2b2605..a1e4c39f3870 100644 > > --- a/src/ipa/mali-c55/ipa_context.cpp > > +++ b/src/ipa/mali-c55/ipa_context.cpp > > @@ -96,6 +96,12 @@ namespace libcamera::ipa::mali_c55 { > > * > > * \var IPAContext::frameContexts > > * \brief Ring buffer of per-frame contexts > > + * > > + * \var IPAContext::ctrlMap > > + * \brief A ControlInfoMap::Map of controls populated by the algorithms > > + * > > + * \var IPAContext::camHelper > > + * \brief The camera sensor helper > > */ > > > > } /* namespace libcamera::ipa::mali_c55 */ > > diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h > > index ac4b83773803..3d884ea17eb8 100644 > > --- a/src/ipa/mali-c55/ipa_context.h > > +++ b/src/ipa/mali-c55/ipa_context.h > > @@ -12,8 +12,10 @@ > > > > #include "libcamera/internal/bayer_format.h" > > > > +#include <libipa/camera_sensor_helper.h> > > #include <libipa/fc_queue.h> > > > > +#include "libipa/awb.h" > > #include "libipa/fixedpoint.h" > > > > namespace libcamera { > > @@ -29,6 +31,8 @@ struct IPASessionConfiguration { > > double maxAnalogueGain; > > } agc; > > > > + ipa::awb::Session awb; > > + > > struct { > > BayerFormat::Order bayerOrder; > > utils::Duration lineDuration; > > @@ -54,10 +58,7 @@ struct IPAActiveState { > > uint32_t temperatureK; > > } agc; > > > > - struct { > > - UQ<4, 8> rGain; > > - UQ<4, 8> bGain; > > - } awb; > > + ipa::awb::ActiveState awb; > > }; > > > > struct IPAFrameContext : public FrameContext { > > @@ -67,10 +68,7 @@ struct IPAFrameContext : public FrameContext { > > UQ<5, 8> ispGain; > > } agc; > > > > - struct { > > - UQ<4, 8> rGain; > > - UQ<4, 8> bGain; > > - } awb; > > + ipa::awb::FrameContext awb; > > }; > > > > struct IPAContext { > > @@ -85,6 +83,9 @@ struct IPAContext { > > FCQueue<IPAFrameContext> frameContexts; > > > > ControlInfoMap::Map ctrlMap; > > + > > + /* Interface to the Camera Helper */ > > + std::unique_ptr<CameraSensorHelper> camHelper; > > }; > > > > } /* namespace ipa::mali_c55 */ > > diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp > > index 1d3af0627fdb..c35cc10bea01 100644 > > --- a/src/ipa/mali-c55/mali-c55.cpp > > +++ b/src/ipa/mali-c55/mali-c55.cpp > > @@ -77,9 +77,6 @@ private: > > > > ControlInfoMap sensorControls_; > > > > - /* Interface to the Camera Helper */ > > - std::unique_ptr<CameraSensorHelper> camHelper_; > > - > > /* Local parameter storage */ > > struct IPAContext context_; > > }; > > @@ -101,8 +98,8 @@ std::string IPAMaliC55::logPrefix() const > > int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig, > > ControlInfoMap *ipaControls) > > { > > - camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); > > - if (!camHelper_) { > > + context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel); > > + if (!context_.camHelper) { > > I had this split out during: > - https://patchwork.libcamera.org/project/libcamera/list/?series=5486&state=* > > - https://patchwork.libcamera.org/patch/24590/ > > Which already had your review by tag too ;-) > But I guess it's fine in here. > I might have just squashed that patch in ? To be honest I don't remember.. > > > LOG(IPAMaliC55, Error) > > << "Failed to create camera sensor helper for " > > << settings.sensorModel; > > @@ -145,10 +142,10 @@ void IPAMaliC55::setControls() > > > > if (activeState.agc.autoEnabled) { > > exposure = activeState.agc.automatic.exposure; > > - gain = camHelper_->gainCode(activeState.agc.automatic.sensorGain); > > + gain = context_.camHelper->gainCode(activeState.agc.automatic.sensorGain); > > } else { > > exposure = activeState.agc.manual.exposure; > > - gain = camHelper_->gainCode(activeState.agc.manual.sensorGain); > > + gain = context_.camHelper->gainCode(activeState.agc.manual.sensorGain); > > } > > > > ControlList ctrls(sensorControls_); > > @@ -194,17 +191,17 @@ void IPAMaliC55::updateSessionConfiguration(const IPACameraSensorInfo &info, > > 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); > > + context_.configuration.agc.minAnalogueGain = context_.camHelper->gain(minGain); > > + context_.configuration.agc.maxAnalogueGain = context_.camHelper->gain(maxGain); > > > > - if (camHelper_->blackLevel().has_value()) { > > + if (context_.camHelper->blackLevel().has_value()) { > > I guess we should rebase https://patchwork.libcamera.org/patch/24591/ > too. > > But that can be on top of this series now. > > > My only thoughts are really that we are doing a lot to convert doubles > and floats and it all feels a bit unnecessary to work on a value that > will be clamped to a UQ4,8 (12 bit preceision) while float has an > equivalent of 24 bit precision, and double gives 53. Yes, it feels wrong. However I thought that if double were used there might have been a reason, but maybe there was not ? > > And perhaps the patch itself could be split up - but I can look the > otherway on either case so: To split out the moving of the camHelper_ ? Is it worth it ?? > > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > Thanks j > > /* > > * The black level from CameraSensorHelper is a 16-bit value. > > * The Mali-C55 ISP expects 20-bit settings, so we shift it to > > * the appropriate width > > */ > > context_.configuration.sensor.blackLevel = > > - camHelper_->blackLevel().value() << 4; > > + context_.camHelper->blackLevel().value() << 4; > > } > > } > > > > @@ -255,9 +252,9 @@ void IPAMaliC55::updateControls(const IPACameraSensorInfo &sensorInfo, > > > > /* Compute the analogue gain limits. */ > > const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; > > - float minGain = camHelper_->gain(v4l2Gain.min().get<int32_t>()); > > - float maxGain = camHelper_->gain(v4l2Gain.max().get<int32_t>()); > > - float defGain = camHelper_->gain(v4l2Gain.def().get<int32_t>()); > > + float minGain = context_.camHelper->gain(v4l2Gain.min().get<int32_t>()); > > + float maxGain = context_.camHelper->gain(v4l2Gain.max().get<int32_t>()); > > + float defGain = context_.camHelper->gain(v4l2Gain.def().get<int32_t>()); > > ctrlMap[&controls::AnalogueGain] = ControlInfo(minGain, maxGain, defGain); > > > > /* > > @@ -355,7 +352,7 @@ void IPAMaliC55::processStats(unsigned int request, unsigned int bufferId, > > frameContext.agc.exposure = > > sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>(); > > frameContext.agc.sensorGain = > > - camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>()); > > + context_.camHelper->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>()); > > > > ControlList metadata(controls::controls); > > > > > > -- > > 2.54.0 > >
Quoting Jacopo Mondi (2026-06-16 13:29:51) > Hi Kieran > > On Tue, Jun 16, 2026 at 12:55:32PM +0100, Kieran Bingham wrote: > > Quoting Jacopo Mondi (2026-06-15 15:05:27) > > > Port the Mali-C55 Awb algorithm to use the new libipa implementation > > > of AwbAlgorithm. > > > > > > The awbAlgo_ class member is initialized with the Q<4, 8> type that > > > represents the register format and the MaliC55AwbStats type handles > > > the WB statistics format as produced by the Mali-C55 which already > > > calculates the mean values as R/G and B/G values. > > > > > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > > > --- <snip> > > > > > > -void Awb::fillConfigParamBlock(MaliC55Params *params) > > > +void Awb::configAwbMeas(MaliC55Params *params) > > > > Is 'Meas' correct ? Perhaps configureAwbMeans() > > > > It's intentional for "Measurement". Should I change it ? It feels a bit unclear. Does it even need to change from fillConfigParamBlock? > > > { > > > auto block = params->block<MaliC55Blocks::AwbConfig>(); > > > > > > @@ -117,56 +119,77 @@ void Awb::fillConfigParamBlock(MaliC55Params *params) > > > block->cb_low = 64; > > > } > > > > > > +/** > > > + * \copydoc libcamera::ipa::Algorithm::prepare > > > + */ > > > void Awb::prepare(IPAContext &context, const uint32_t frame, > > > IPAFrameContext &frameContext, MaliC55Params *params) > > > { > > > - fillGainsParamBlock(params, context, frameContext); > > > + awbAlgo_.prepare(context.activeState.awb, frameContext.awb); > > > + > > > + /* > > > + * The gains here map as follows: > > > + * gain00 = R > > > + * gain01 = Gr > > > + * gain10 = Gb > > > + * gain11 = B > > > + * > > > + * This holds true regardless of the bayer order of the input data, as > > > + * the mapping is done internally in the ISP. > > > + */ > > > + auto block = params->block<MaliC55Blocks::AwbGains>(); > > > + block.setEnabled(true); > > > + > > > + block->gain00 = UQ<4, 8>(static_cast<float>(frameContext.awb.gains.r())) > > > + .quantized(); > > > > I really wonder if we should just use float in the libipa AWB > > implementation. I can't see why we're using double there ... > > > > I don't know, I think that's what we had already, didn't we ? Well maybe that's a cleanup on top to reduce some casting after we get a fresh baseline. <snip> > > > @@ -101,8 +98,8 @@ std::string IPAMaliC55::logPrefix() const > > > int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig, > > > ControlInfoMap *ipaControls) > > > { > > > - camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); > > > - if (!camHelper_) { > > > + context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel); > > > + if (!context_.camHelper) { > > > > I had this split out during: > > - https://patchwork.libcamera.org/project/libcamera/list/?series=5486&state=* > > > > - https://patchwork.libcamera.org/patch/24590/ > > > > Which already had your review by tag too ;-) > > But I guess it's fine in here. > > > > I might have just squashed that patch in ? > To be honest I don't remember.. > > > > > > LOG(IPAMaliC55, Error) > > > << "Failed to create camera sensor helper for " > > > << settings.sensorModel; > > > @@ -145,10 +142,10 @@ void IPAMaliC55::setControls() > > > > > > if (activeState.agc.autoEnabled) { > > > exposure = activeState.agc.automatic.exposure; > > > - gain = camHelper_->gainCode(activeState.agc.automatic.sensorGain); > > > + gain = context_.camHelper->gainCode(activeState.agc.automatic.sensorGain); > > > } else { > > > exposure = activeState.agc.manual.exposure; > > > - gain = camHelper_->gainCode(activeState.agc.manual.sensorGain); > > > + gain = context_.camHelper->gainCode(activeState.agc.manual.sensorGain); > > > } > > > > > > ControlList ctrls(sensorControls_); > > > @@ -194,17 +191,17 @@ void IPAMaliC55::updateSessionConfiguration(const IPACameraSensorInfo &info, > > > 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); > > > + context_.configuration.agc.minAnalogueGain = context_.camHelper->gain(minGain); > > > + context_.configuration.agc.maxAnalogueGain = context_.camHelper->gain(maxGain); > > > > > > - if (camHelper_->blackLevel().has_value()) { > > > + if (context_.camHelper->blackLevel().has_value()) { > > > > I guess we should rebase https://patchwork.libcamera.org/patch/24591/ > > too. > > > > But that can be on top of this series now. > > > > > > My only thoughts are really that we are doing a lot to convert doubles > > and floats and it all feels a bit unnecessary to work on a value that > > will be clamped to a UQ4,8 (12 bit preceision) while float has an > > equivalent of 24 bit precision, and double gives 53. > > Yes, it feels wrong. However I thought that if double were used there > might have been a reason, but maybe there was not ? > > > > > And perhaps the patch itself could be split up - but I can look the > > otherway on either case so: > > To split out the moving of the camHelper_ ? Is it worth it ?? No you don't have to for me - it was just confusing when I read through this first. > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > > > > Thanks > j >
Hi Jacopo On 15/06/2026 15:05, Jacopo Mondi wrote: > Port the Mali-C55 Awb algorithm to use the new libipa implementation > of AwbAlgorithm. > > The awbAlgo_ class member is initialized with the Q<4, 8> type that > represents the register format and the MaliC55AwbStats type handles > the WB statistics format as produced by the Mali-C55 which already > calculates the mean values as R/G and B/G values. > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > --- I have basically the exact same comments that Kieran already said, so I won't bother repeating them: Reviewed-and-Tested-by: Daniel Scally <dan.scally@ideasonboard.com> > src/ipa/mali-c55/algorithms/awb.cpp | 198 ++++++++++++++++++------------------ > src/ipa/mali-c55/algorithms/awb.h | 30 ++++-- > src/ipa/mali-c55/ipa_context.cpp | 6 ++ > src/ipa/mali-c55/ipa_context.h | 17 ++-- > src/ipa/mali-c55/mali-c55.cpp | 27 +++-- > 5 files changed, 152 insertions(+), 126 deletions(-) > > diff --git a/src/ipa/mali-c55/algorithms/awb.cpp b/src/ipa/mali-c55/algorithms/awb.cpp > index 8a671b52be59..97964f395325 100644 > --- a/src/ipa/mali-c55/algorithms/awb.cpp > +++ b/src/ipa/mali-c55/algorithms/awb.cpp > @@ -2,7 +2,7 @@ > /* > * Copyright (C) 2024, Ideas On Board Oy > * > - * Mali C55 grey world auto white balance algorithm > + * Mali C55 auto white balance algorithm > */ > > #include "awb.h" > @@ -14,63 +14,65 @@ > > #include <libcamera/control_ids.h> > > -#include "libipa/fixedpoint.h" > - > namespace libcamera { > > namespace ipa::mali_c55::algorithms { > > LOG_DEFINE_CATEGORY(MaliC55Awb) > > -/* Number of frames at which we should run AWB at full speed */ > -static constexpr uint32_t kNumStartupFrames = 4; > +/* \todo Mali-C55 doesn't support the Lux algorithm. */ > +static constexpr unsigned int kDefaultLux = 500; > + > +class MaliC55AwbStats final : public AwbStats > +{ > +public: > + MaliC55AwbStats() = default; > + MaliC55AwbStats(const RGB<double> &rgbMeans) > + : AwbStats(rgbMeans) > + { > + /* The Mali-C55 ISP already provides stats as R/G and B/G ratios. */ > + rg_ = rgbMeans_.r(); > + bg_ = rgbMeans_.b(); > + } > > -Awb::Awb() > + /* Minimum mean value below which AWB can't operate. */ > + double minColourValue() const override > + { > + return 0.2; > + } > +}; > + > +/** > + * \copydoc libcamera::ipa::Algorithm::init > + */ > +int Awb::init(IPAContext &context, const ValueNode &tuningData) > { > + return awbAlgo_.init(tuningData, context.ctrlMap); > } > > -int Awb::configure([[maybe_unused]] IPAContext &context, > +/** > + * \copydoc libcamera::ipa::Algorithm::configure > + */ > +int Awb::configure(IPAContext &context, > [[maybe_unused]] const IPACameraSensorInfo &configInfo) > { > - /* > - * Initially we have no idea what the colour balance will be like, so > - * for the first frame we will make no assumptions and leave the R/B > - * channels unmodified. > - */ > - context.activeState.awb.rGain = 1.0f; > - context.activeState.awb.bGain = 1.0f; > - > - return 0; > + return awbAlgo_.configure(context.activeState.awb, > + context.configuration.awb); > } > > -void Awb::fillGainsParamBlock(MaliC55Params *params, IPAContext &context, > - IPAFrameContext &frameContext) > +/** > + * \copydoc libcamera::ipa::Algorithm::queueRequest > + */ > +void Awb::queueRequest(IPAContext &context, > + const uint32_t frame, > + IPAFrameContext &frameContext, > + const ControlList &controls) > { > - UQ<4, 8> rGain = context.activeState.awb.rGain; > - UQ<4, 8> bGain = context.activeState.awb.bGain; > - > - /* > - * The gains here map as follows: > - * gain00 = R > - * gain01 = Gr > - * gain10 = Gb > - * gain11 = B > - * > - * This holds true regardless of the bayer order of the input data, as > - * the mapping is done internally in the ISP. > - */ > - auto block = params->block<MaliC55Blocks::AwbGains>(); > - > - block->gain00 = rGain.quantized(); > - block->gain01 = UQ<4, 8>(1.0f).quantized(); > - block->gain10 = UQ<4, 8>(1.0f).quantized(); > - block->gain11 = bGain.quantized(); > - > - frameContext.awb.rGain = rGain; > - frameContext.awb.bGain = bGain; > + awbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb, > + controls); > } > > -void Awb::fillConfigParamBlock(MaliC55Params *params) > +void Awb::configAwbMeas(MaliC55Params *params) > { > auto block = params->block<MaliC55Blocks::AwbConfig>(); > > @@ -117,56 +119,77 @@ void Awb::fillConfigParamBlock(MaliC55Params *params) > block->cb_low = 64; > } > > +/** > + * \copydoc libcamera::ipa::Algorithm::prepare > + */ > void Awb::prepare(IPAContext &context, const uint32_t frame, > IPAFrameContext &frameContext, MaliC55Params *params) > { > - fillGainsParamBlock(params, context, frameContext); > + awbAlgo_.prepare(context.activeState.awb, frameContext.awb); > + > + /* > + * The gains here map as follows: > + * gain00 = R > + * gain01 = Gr > + * gain10 = Gb > + * gain11 = B > + * > + * This holds true regardless of the bayer order of the input data, as > + * the mapping is done internally in the ISP. > + */ > + auto block = params->block<MaliC55Blocks::AwbGains>(); > + block.setEnabled(true); > + > + block->gain00 = UQ<4, 8>(static_cast<float>(frameContext.awb.gains.r())) > + .quantized(); > + block->gain01 = UQ<4, 8>(1.0f).quantized(); > + block->gain10 = UQ<4, 8>(1.0f).quantized(); > + block->gain11 = UQ<4, 8>(static_cast<float>(frameContext.awb.gains.b())) > + .quantized(); > > if (frame > 0) > return; > > - fillConfigParamBlock(params); > + configAwbMeas(params); > } > > -void Awb::process(IPAContext &context, const uint32_t frame, > - IPAFrameContext &frameContext, const mali_c55_stats_buffer *stats, > - [[maybe_unused]] ControlList &metadata) > +MaliC55AwbStats Awb::calculateRgbMeans(const IPAFrameContext &frameContext, > + const mali_c55_stats_buffer *stats) const > { > - const struct mali_c55_awb_average_ratios *awb_ratios = stats->awb_ratios; > + const struct mali_c55_awb_average_ratios *awb = stats->awb_ratios; > > /* > * The ISP produces average R:G and B:G ratios for zones. We take the > - * average of all the zones with data and simply invert them to provide > - * gain figures that we can apply to approximate a grey world. > + * average of all the zones with data and calculate the mean values. > */ > - unsigned int counted_zones = 0; > - float rgSum = 0, bgSum = 0; > + unsigned int active_zones = 0; > + double rgSum = 0, bgSum = 0; > > for (unsigned int i = 0; i < 225; i++) { > - if (!awb_ratios[i].num_pixels) > + if (!awb[i].num_pixels) > continue; > > /* > - * The statistics are in Q4.8 format, so we convert to float > + * The statistics are in Q4.8 format, so we convert to double > * here. > */ > - rgSum += UQ<4, 8>(awb_ratios[i].avg_rg_gr).value(); > - bgSum += UQ<4, 8>(awb_ratios[i].avg_bg_br).value(); > - counted_zones++; > + rgSum += UQ<4, 8>(awb[i].avg_rg_gr).value(); > + bgSum += UQ<4, 8>(awb[i].avg_bg_br).value(); > + active_zones++; > } > > /* > * Sometimes the first frame's statistics have no valid pixels, in which > * case we'll just assume a grey world until they say otherwise. > */ > - float rgAvg, bgAvg; > - if (!counted_zones) { > - rgAvg = 1.0; > - bgAvg = 1.0; > - } else { > - rgAvg = rgSum / counted_zones; > - bgAvg = bgSum / counted_zones; > - } > + if (!active_zones) > + return {}; > + > + RGB<double> rgbMeans = { { > + rgSum / active_zones, > + 1.0, > + bgSum / active_zones, > + } }; > > /* > * The statistics are generated _after_ white balancing is performed in > @@ -174,41 +197,22 @@ void Awb::process(IPAContext &context, const uint32_t frame, > * figure by the gains that were applied when the statistics for this > * frame were generated. > */ > - float rRatio = rgAvg / frameContext.awb.rGain.value(); > - float bRatio = bgAvg / frameContext.awb.bGain.value(); > + rgbMeans /= frameContext.awb.gains.max(0.01); > > - /* > - * And then we can simply invert the ratio to find the gain we should > - * apply. > - */ > - float rGain = 1 / rRatio; > - float bGain = 1 / bRatio; > + return MaliC55AwbStats(rgbMeans); > +} > > - /* > - * Running at full speed, this algorithm results in oscillations in the > - * colour balance. To remove those we dampen the speed at which it makes > - * changes in gain, unless we're in the startup phase in which case we > - * want to fix the miscolouring as quickly as possible. > - */ > - float speed = frame < kNumStartupFrames ? 1.0f : 0.2f; > - rGain = speed * rGain + context.activeState.awb.rGain.value() * (1.0f - speed); > - bGain = speed * bGain + context.activeState.awb.bGain.value() * (1.0f - speed); > - > - context.activeState.awb.rGain = rGain; > - context.activeState.awb.bGain = bGain; > - > - metadata.set(controls::ColourGains, { > - frameContext.awb.rGain.value(), > - frameContext.awb.bGain.value(), > - }); > - > - LOG(MaliC55Awb, Debug) << "For frame number " << frame << ": " > - << "Average R/G Ratio: " << rgAvg > - << ", Average B/G Ratio: " << bgAvg > - << "\nrGain applied to this frame: " << frameContext.awb.rGain > - << ", bGain applied to this frame: " << frameContext.awb.bGain > - << "\nrGain to apply: " << context.activeState.awb.rGain > - << ", bGain to apply: " << context.activeState.awb.bGain; > +/** > + * \copydoc libcamera::ipa::Algorithm::process > + */ > +void Awb::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, > + IPAFrameContext &frameContext, const mali_c55_stats_buffer *stats, > + ControlList &metadata) > +{ > + MaliC55AwbStats awbStats = calculateRgbMeans(frameContext, stats); > + > + awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats, > + kDefaultLux, metadata); > } > > REGISTER_IPA_ALGORITHM(Awb, "Awb") > diff --git a/src/ipa/mali-c55/algorithms/awb.h b/src/ipa/mali-c55/algorithms/awb.h > index 683a62af263a..cc7ef583eafc 100644 > --- a/src/ipa/mali-c55/algorithms/awb.h > +++ b/src/ipa/mali-c55/algorithms/awb.h > @@ -2,24 +2,41 @@ > /* > * Copyright (C) 2024, Ideas on Board Oy > * > - * Mali C55 grey world auto white balance algorithm > + * Mali C55 auto white balance algorithm > */ > > +#pragma once > + > +#include <linux/media/arm/mali-c55-config.h> > + > +#include <libcamera/controls.h> > + > +#include "libcamera/internal/value_node.h" > + > +#include "libipa/awb.h" > +#include "libipa/fixedpoint.h" > + > #include "algorithm.h" > #include "ipa_context.h" > +#include "params.h" > > namespace libcamera { > > namespace ipa::mali_c55::algorithms { > > +class MaliC55AwbStats; > + > class Awb : public Algorithm > { > public: > - Awb(); > ~Awb() = default; > > + int init(IPAContext &context, const ValueNode &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, > MaliC55Params *params) override; > @@ -29,10 +46,11 @@ public: > ControlList &metadata) override; > > private: > - void fillGainsParamBlock(MaliC55Params *params, > - IPAContext &context, > - IPAFrameContext &frameContext); > - void fillConfigParamBlock(MaliC55Params *params); > + void configAwbMeas(MaliC55Params *params); > + MaliC55AwbStats calculateRgbMeans(const IPAFrameContext &frameContext, > + const mali_c55_stats_buffer *stats) const; > + > + AwbAlgorithm<UQ<4, 8>> awbAlgo_; > }; > > } /* namespace ipa::mali_c55::algorithms */ > diff --git a/src/ipa/mali-c55/ipa_context.cpp b/src/ipa/mali-c55/ipa_context.cpp > index 1b203e2b2605..a1e4c39f3870 100644 > --- a/src/ipa/mali-c55/ipa_context.cpp > +++ b/src/ipa/mali-c55/ipa_context.cpp > @@ -96,6 +96,12 @@ namespace libcamera::ipa::mali_c55 { > * > * \var IPAContext::frameContexts > * \brief Ring buffer of per-frame contexts > + * > + * \var IPAContext::ctrlMap > + * \brief A ControlInfoMap::Map of controls populated by the algorithms > + * > + * \var IPAContext::camHelper > + * \brief The camera sensor helper > */ > > } /* namespace libcamera::ipa::mali_c55 */ > diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h > index ac4b83773803..3d884ea17eb8 100644 > --- a/src/ipa/mali-c55/ipa_context.h > +++ b/src/ipa/mali-c55/ipa_context.h > @@ -12,8 +12,10 @@ > > #include "libcamera/internal/bayer_format.h" > > +#include <libipa/camera_sensor_helper.h> > #include <libipa/fc_queue.h> > > +#include "libipa/awb.h" > #include "libipa/fixedpoint.h" > > namespace libcamera { > @@ -29,6 +31,8 @@ struct IPASessionConfiguration { > double maxAnalogueGain; > } agc; > > + ipa::awb::Session awb; > + > struct { > BayerFormat::Order bayerOrder; > utils::Duration lineDuration; > @@ -54,10 +58,7 @@ struct IPAActiveState { > uint32_t temperatureK; > } agc; > > - struct { > - UQ<4, 8> rGain; > - UQ<4, 8> bGain; > - } awb; > + ipa::awb::ActiveState awb; > }; > > struct IPAFrameContext : public FrameContext { > @@ -67,10 +68,7 @@ struct IPAFrameContext : public FrameContext { > UQ<5, 8> ispGain; > } agc; > > - struct { > - UQ<4, 8> rGain; > - UQ<4, 8> bGain; > - } awb; > + ipa::awb::FrameContext awb; > }; > > struct IPAContext { > @@ -85,6 +83,9 @@ struct IPAContext { > FCQueue<IPAFrameContext> frameContexts; > > ControlInfoMap::Map ctrlMap; > + > + /* Interface to the Camera Helper */ > + std::unique_ptr<CameraSensorHelper> camHelper; > }; > > } /* namespace ipa::mali_c55 */ > diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp > index 1d3af0627fdb..c35cc10bea01 100644 > --- a/src/ipa/mali-c55/mali-c55.cpp > +++ b/src/ipa/mali-c55/mali-c55.cpp > @@ -77,9 +77,6 @@ private: > > ControlInfoMap sensorControls_; > > - /* Interface to the Camera Helper */ > - std::unique_ptr<CameraSensorHelper> camHelper_; > - > /* Local parameter storage */ > struct IPAContext context_; > }; > @@ -101,8 +98,8 @@ std::string IPAMaliC55::logPrefix() const > int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig, > ControlInfoMap *ipaControls) > { > - camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); > - if (!camHelper_) { > + context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel); > + if (!context_.camHelper) { > LOG(IPAMaliC55, Error) > << "Failed to create camera sensor helper for " > << settings.sensorModel; > @@ -145,10 +142,10 @@ void IPAMaliC55::setControls() > > if (activeState.agc.autoEnabled) { > exposure = activeState.agc.automatic.exposure; > - gain = camHelper_->gainCode(activeState.agc.automatic.sensorGain); > + gain = context_.camHelper->gainCode(activeState.agc.automatic.sensorGain); > } else { > exposure = activeState.agc.manual.exposure; > - gain = camHelper_->gainCode(activeState.agc.manual.sensorGain); > + gain = context_.camHelper->gainCode(activeState.agc.manual.sensorGain); > } > > ControlList ctrls(sensorControls_); > @@ -194,17 +191,17 @@ void IPAMaliC55::updateSessionConfiguration(const IPACameraSensorInfo &info, > 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); > + context_.configuration.agc.minAnalogueGain = context_.camHelper->gain(minGain); > + context_.configuration.agc.maxAnalogueGain = context_.camHelper->gain(maxGain); > > - if (camHelper_->blackLevel().has_value()) { > + if (context_.camHelper->blackLevel().has_value()) { > /* > * The black level from CameraSensorHelper is a 16-bit value. > * The Mali-C55 ISP expects 20-bit settings, so we shift it to > * the appropriate width > */ > context_.configuration.sensor.blackLevel = > - camHelper_->blackLevel().value() << 4; > + context_.camHelper->blackLevel().value() << 4; > } > } > > @@ -255,9 +252,9 @@ void IPAMaliC55::updateControls(const IPACameraSensorInfo &sensorInfo, > > /* Compute the analogue gain limits. */ > const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; > - float minGain = camHelper_->gain(v4l2Gain.min().get<int32_t>()); > - float maxGain = camHelper_->gain(v4l2Gain.max().get<int32_t>()); > - float defGain = camHelper_->gain(v4l2Gain.def().get<int32_t>()); > + float minGain = context_.camHelper->gain(v4l2Gain.min().get<int32_t>()); > + float maxGain = context_.camHelper->gain(v4l2Gain.max().get<int32_t>()); > + float defGain = context_.camHelper->gain(v4l2Gain.def().get<int32_t>()); > ctrlMap[&controls::AnalogueGain] = ControlInfo(minGain, maxGain, defGain); > > /* > @@ -355,7 +352,7 @@ void IPAMaliC55::processStats(unsigned int request, unsigned int bufferId, > frameContext.agc.exposure = > sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>(); > frameContext.agc.sensorGain = > - camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>()); > + context_.camHelper->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>()); > > ControlList metadata(controls::controls); > >
Hi Kieran On Mon, Jun 22, 2026 at 07:03:07PM +0100, Kieran Bingham wrote: > Quoting Jacopo Mondi (2026-06-16 13:29:51) > > Hi Kieran > > > > On Tue, Jun 16, 2026 at 12:55:32PM +0100, Kieran Bingham wrote: > > > Quoting Jacopo Mondi (2026-06-15 15:05:27) > > > > Port the Mali-C55 Awb algorithm to use the new libipa implementation > > > > of AwbAlgorithm. > > > > > > > > The awbAlgo_ class member is initialized with the Q<4, 8> type that > > > > represents the register format and the MaliC55AwbStats type handles > > > > the WB statistics format as produced by the Mali-C55 which already > > > > calculates the mean values as R/G and B/G values. > > > > > > > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > > > > --- > > <snip> > > > > > > > > > -void Awb::fillConfigParamBlock(MaliC55Params *params) > > > > +void Awb::configAwbMeas(MaliC55Params *params) > > > > > > Is 'Meas' correct ? Perhaps configureAwbMeans() > > > > > > > It's intentional for "Measurement". Should I change it ? > > It feels a bit unclear. > > Does it even need to change from fillConfigParamBlock? > As per lsc, I found the existing name unnecessarily long and not clear. However, it's arbitrary, so I can backtrack > > > > > { > > > > auto block = params->block<MaliC55Blocks::AwbConfig>(); > > > > > > > > @@ -117,56 +119,77 @@ void Awb::fillConfigParamBlock(MaliC55Params *params) > > > > block->cb_low = 64; > > > > } > > > > > > > > +/** > > > > + * \copydoc libcamera::ipa::Algorithm::prepare > > > > + */ > > > > void Awb::prepare(IPAContext &context, const uint32_t frame, > > > > IPAFrameContext &frameContext, MaliC55Params *params) > > > > { > > > > - fillGainsParamBlock(params, context, frameContext); > > > > + awbAlgo_.prepare(context.activeState.awb, frameContext.awb); > > > > + > > > > + /* > > > > + * The gains here map as follows: > > > > + * gain00 = R > > > > + * gain01 = Gr > > > > + * gain10 = Gb > > > > + * gain11 = B > > > > + * > > > > + * This holds true regardless of the bayer order of the input data, as > > > > + * the mapping is done internally in the ISP. > > > > + */ > > > > + auto block = params->block<MaliC55Blocks::AwbGains>(); > > > > + block.setEnabled(true); > > > > + > > > > + block->gain00 = UQ<4, 8>(static_cast<float>(frameContext.awb.gains.r())) > > > > + .quantized(); > > > > > > I really wonder if we should just use float in the libipa AWB > > > implementation. I can't see why we're using double there ... > > > > > > > I don't know, I think that's what we had already, didn't we ? > > Well maybe that's a cleanup on top to reduce some casting after we get a > fresh baseline. > > <snip> > > > > > @@ -101,8 +98,8 @@ std::string IPAMaliC55::logPrefix() const > > > > int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig, > > > > ControlInfoMap *ipaControls) > > > > { > > > > - camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); > > > > - if (!camHelper_) { > > > > + context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel); > > > > + if (!context_.camHelper) { > > > > > > I had this split out during: > > > - https://patchwork.libcamera.org/project/libcamera/list/?series=5486&state=* > > > > > > - https://patchwork.libcamera.org/patch/24590/ > > > > > > Which already had your review by tag too ;-) > > > But I guess it's fine in here. > > > > > > > I might have just squashed that patch in ? > > To be honest I don't remember.. > > > > > > > > > LOG(IPAMaliC55, Error) > > > > << "Failed to create camera sensor helper for " > > > > << settings.sensorModel; > > > > @@ -145,10 +142,10 @@ void IPAMaliC55::setControls() > > > > > > > > if (activeState.agc.autoEnabled) { > > > > exposure = activeState.agc.automatic.exposure; > > > > - gain = camHelper_->gainCode(activeState.agc.automatic.sensorGain); > > > > + gain = context_.camHelper->gainCode(activeState.agc.automatic.sensorGain); > > > > } else { > > > > exposure = activeState.agc.manual.exposure; > > > > - gain = camHelper_->gainCode(activeState.agc.manual.sensorGain); > > > > + gain = context_.camHelper->gainCode(activeState.agc.manual.sensorGain); > > > > } > > > > > > > > ControlList ctrls(sensorControls_); > > > > @@ -194,17 +191,17 @@ void IPAMaliC55::updateSessionConfiguration(const IPACameraSensorInfo &info, > > > > 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); > > > > + context_.configuration.agc.minAnalogueGain = context_.camHelper->gain(minGain); > > > > + context_.configuration.agc.maxAnalogueGain = context_.camHelper->gain(maxGain); > > > > > > > > - if (camHelper_->blackLevel().has_value()) { > > > > + if (context_.camHelper->blackLevel().has_value()) { > > > > > > I guess we should rebase https://patchwork.libcamera.org/patch/24591/ > > > too. > > > > > > But that can be on top of this series now. > > > > > > > > > My only thoughts are really that we are doing a lot to convert doubles > > > and floats and it all feels a bit unnecessary to work on a value that > > > will be clamped to a UQ4,8 (12 bit preceision) while float has an > > > equivalent of 24 bit precision, and double gives 53. > > > > Yes, it feels wrong. However I thought that if double were used there > > might have been a reason, but maybe there was not ? > > > > > > > > And perhaps the patch itself could be split up - but I can look the > > > otherway on either case so: > > > > To split out the moving of the camHelper_ ? Is it worth it ?? > > No you don't have to for me - it was just confusing when I read through > this first. > > > > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > > > > > > > Thanks > > j > >
diff --git a/src/ipa/mali-c55/algorithms/awb.cpp b/src/ipa/mali-c55/algorithms/awb.cpp index 8a671b52be59..97964f395325 100644 --- a/src/ipa/mali-c55/algorithms/awb.cpp +++ b/src/ipa/mali-c55/algorithms/awb.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2024, Ideas On Board Oy * - * Mali C55 grey world auto white balance algorithm + * Mali C55 auto white balance algorithm */ #include "awb.h" @@ -14,63 +14,65 @@ #include <libcamera/control_ids.h> -#include "libipa/fixedpoint.h" - namespace libcamera { namespace ipa::mali_c55::algorithms { LOG_DEFINE_CATEGORY(MaliC55Awb) -/* Number of frames at which we should run AWB at full speed */ -static constexpr uint32_t kNumStartupFrames = 4; +/* \todo Mali-C55 doesn't support the Lux algorithm. */ +static constexpr unsigned int kDefaultLux = 500; + +class MaliC55AwbStats final : public AwbStats +{ +public: + MaliC55AwbStats() = default; + MaliC55AwbStats(const RGB<double> &rgbMeans) + : AwbStats(rgbMeans) + { + /* The Mali-C55 ISP already provides stats as R/G and B/G ratios. */ + rg_ = rgbMeans_.r(); + bg_ = rgbMeans_.b(); + } -Awb::Awb() + /* Minimum mean value below which AWB can't operate. */ + double minColourValue() const override + { + return 0.2; + } +}; + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Awb::init(IPAContext &context, const ValueNode &tuningData) { + return awbAlgo_.init(tuningData, context.ctrlMap); } -int Awb::configure([[maybe_unused]] IPAContext &context, +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int Awb::configure(IPAContext &context, [[maybe_unused]] const IPACameraSensorInfo &configInfo) { - /* - * Initially we have no idea what the colour balance will be like, so - * for the first frame we will make no assumptions and leave the R/B - * channels unmodified. - */ - context.activeState.awb.rGain = 1.0f; - context.activeState.awb.bGain = 1.0f; - - return 0; + return awbAlgo_.configure(context.activeState.awb, + context.configuration.awb); } -void Awb::fillGainsParamBlock(MaliC55Params *params, IPAContext &context, - IPAFrameContext &frameContext) +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Awb::queueRequest(IPAContext &context, + const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) { - UQ<4, 8> rGain = context.activeState.awb.rGain; - UQ<4, 8> bGain = context.activeState.awb.bGain; - - /* - * The gains here map as follows: - * gain00 = R - * gain01 = Gr - * gain10 = Gb - * gain11 = B - * - * This holds true regardless of the bayer order of the input data, as - * the mapping is done internally in the ISP. - */ - auto block = params->block<MaliC55Blocks::AwbGains>(); - - block->gain00 = rGain.quantized(); - block->gain01 = UQ<4, 8>(1.0f).quantized(); - block->gain10 = UQ<4, 8>(1.0f).quantized(); - block->gain11 = bGain.quantized(); - - frameContext.awb.rGain = rGain; - frameContext.awb.bGain = bGain; + awbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb, + controls); } -void Awb::fillConfigParamBlock(MaliC55Params *params) +void Awb::configAwbMeas(MaliC55Params *params) { auto block = params->block<MaliC55Blocks::AwbConfig>(); @@ -117,56 +119,77 @@ void Awb::fillConfigParamBlock(MaliC55Params *params) block->cb_low = 64; } +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ void Awb::prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, MaliC55Params *params) { - fillGainsParamBlock(params, context, frameContext); + awbAlgo_.prepare(context.activeState.awb, frameContext.awb); + + /* + * The gains here map as follows: + * gain00 = R + * gain01 = Gr + * gain10 = Gb + * gain11 = B + * + * This holds true regardless of the bayer order of the input data, as + * the mapping is done internally in the ISP. + */ + auto block = params->block<MaliC55Blocks::AwbGains>(); + block.setEnabled(true); + + block->gain00 = UQ<4, 8>(static_cast<float>(frameContext.awb.gains.r())) + .quantized(); + block->gain01 = UQ<4, 8>(1.0f).quantized(); + block->gain10 = UQ<4, 8>(1.0f).quantized(); + block->gain11 = UQ<4, 8>(static_cast<float>(frameContext.awb.gains.b())) + .quantized(); if (frame > 0) return; - fillConfigParamBlock(params); + configAwbMeas(params); } -void Awb::process(IPAContext &context, const uint32_t frame, - IPAFrameContext &frameContext, const mali_c55_stats_buffer *stats, - [[maybe_unused]] ControlList &metadata) +MaliC55AwbStats Awb::calculateRgbMeans(const IPAFrameContext &frameContext, + const mali_c55_stats_buffer *stats) const { - const struct mali_c55_awb_average_ratios *awb_ratios = stats->awb_ratios; + const struct mali_c55_awb_average_ratios *awb = stats->awb_ratios; /* * The ISP produces average R:G and B:G ratios for zones. We take the - * average of all the zones with data and simply invert them to provide - * gain figures that we can apply to approximate a grey world. + * average of all the zones with data and calculate the mean values. */ - unsigned int counted_zones = 0; - float rgSum = 0, bgSum = 0; + unsigned int active_zones = 0; + double rgSum = 0, bgSum = 0; for (unsigned int i = 0; i < 225; i++) { - if (!awb_ratios[i].num_pixels) + if (!awb[i].num_pixels) continue; /* - * The statistics are in Q4.8 format, so we convert to float + * The statistics are in Q4.8 format, so we convert to double * here. */ - rgSum += UQ<4, 8>(awb_ratios[i].avg_rg_gr).value(); - bgSum += UQ<4, 8>(awb_ratios[i].avg_bg_br).value(); - counted_zones++; + rgSum += UQ<4, 8>(awb[i].avg_rg_gr).value(); + bgSum += UQ<4, 8>(awb[i].avg_bg_br).value(); + active_zones++; } /* * Sometimes the first frame's statistics have no valid pixels, in which * case we'll just assume a grey world until they say otherwise. */ - float rgAvg, bgAvg; - if (!counted_zones) { - rgAvg = 1.0; - bgAvg = 1.0; - } else { - rgAvg = rgSum / counted_zones; - bgAvg = bgSum / counted_zones; - } + if (!active_zones) + return {}; + + RGB<double> rgbMeans = { { + rgSum / active_zones, + 1.0, + bgSum / active_zones, + } }; /* * The statistics are generated _after_ white balancing is performed in @@ -174,41 +197,22 @@ void Awb::process(IPAContext &context, const uint32_t frame, * figure by the gains that were applied when the statistics for this * frame were generated. */ - float rRatio = rgAvg / frameContext.awb.rGain.value(); - float bRatio = bgAvg / frameContext.awb.bGain.value(); + rgbMeans /= frameContext.awb.gains.max(0.01); - /* - * And then we can simply invert the ratio to find the gain we should - * apply. - */ - float rGain = 1 / rRatio; - float bGain = 1 / bRatio; + return MaliC55AwbStats(rgbMeans); +} - /* - * Running at full speed, this algorithm results in oscillations in the - * colour balance. To remove those we dampen the speed at which it makes - * changes in gain, unless we're in the startup phase in which case we - * want to fix the miscolouring as quickly as possible. - */ - float speed = frame < kNumStartupFrames ? 1.0f : 0.2f; - rGain = speed * rGain + context.activeState.awb.rGain.value() * (1.0f - speed); - bGain = speed * bGain + context.activeState.awb.bGain.value() * (1.0f - speed); - - context.activeState.awb.rGain = rGain; - context.activeState.awb.bGain = bGain; - - metadata.set(controls::ColourGains, { - frameContext.awb.rGain.value(), - frameContext.awb.bGain.value(), - }); - - LOG(MaliC55Awb, Debug) << "For frame number " << frame << ": " - << "Average R/G Ratio: " << rgAvg - << ", Average B/G Ratio: " << bgAvg - << "\nrGain applied to this frame: " << frameContext.awb.rGain - << ", bGain applied to this frame: " << frameContext.awb.bGain - << "\nrGain to apply: " << context.activeState.awb.rGain - << ", bGain to apply: " << context.activeState.awb.bGain; +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void Awb::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const mali_c55_stats_buffer *stats, + ControlList &metadata) +{ + MaliC55AwbStats awbStats = calculateRgbMeans(frameContext, stats); + + awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats, + kDefaultLux, metadata); } REGISTER_IPA_ALGORITHM(Awb, "Awb") diff --git a/src/ipa/mali-c55/algorithms/awb.h b/src/ipa/mali-c55/algorithms/awb.h index 683a62af263a..cc7ef583eafc 100644 --- a/src/ipa/mali-c55/algorithms/awb.h +++ b/src/ipa/mali-c55/algorithms/awb.h @@ -2,24 +2,41 @@ /* * Copyright (C) 2024, Ideas on Board Oy * - * Mali C55 grey world auto white balance algorithm + * Mali C55 auto white balance algorithm */ +#pragma once + +#include <linux/media/arm/mali-c55-config.h> + +#include <libcamera/controls.h> + +#include "libcamera/internal/value_node.h" + +#include "libipa/awb.h" +#include "libipa/fixedpoint.h" + #include "algorithm.h" #include "ipa_context.h" +#include "params.h" namespace libcamera { namespace ipa::mali_c55::algorithms { +class MaliC55AwbStats; + class Awb : public Algorithm { public: - Awb(); ~Awb() = default; + int init(IPAContext &context, const ValueNode &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, MaliC55Params *params) override; @@ -29,10 +46,11 @@ public: ControlList &metadata) override; private: - void fillGainsParamBlock(MaliC55Params *params, - IPAContext &context, - IPAFrameContext &frameContext); - void fillConfigParamBlock(MaliC55Params *params); + void configAwbMeas(MaliC55Params *params); + MaliC55AwbStats calculateRgbMeans(const IPAFrameContext &frameContext, + const mali_c55_stats_buffer *stats) const; + + AwbAlgorithm<UQ<4, 8>> awbAlgo_; }; } /* namespace ipa::mali_c55::algorithms */ diff --git a/src/ipa/mali-c55/ipa_context.cpp b/src/ipa/mali-c55/ipa_context.cpp index 1b203e2b2605..a1e4c39f3870 100644 --- a/src/ipa/mali-c55/ipa_context.cpp +++ b/src/ipa/mali-c55/ipa_context.cpp @@ -96,6 +96,12 @@ namespace libcamera::ipa::mali_c55 { * * \var IPAContext::frameContexts * \brief Ring buffer of per-frame contexts + * + * \var IPAContext::ctrlMap + * \brief A ControlInfoMap::Map of controls populated by the algorithms + * + * \var IPAContext::camHelper + * \brief The camera sensor helper */ } /* namespace libcamera::ipa::mali_c55 */ diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h index ac4b83773803..3d884ea17eb8 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -12,8 +12,10 @@ #include "libcamera/internal/bayer_format.h" +#include <libipa/camera_sensor_helper.h> #include <libipa/fc_queue.h> +#include "libipa/awb.h" #include "libipa/fixedpoint.h" namespace libcamera { @@ -29,6 +31,8 @@ struct IPASessionConfiguration { double maxAnalogueGain; } agc; + ipa::awb::Session awb; + struct { BayerFormat::Order bayerOrder; utils::Duration lineDuration; @@ -54,10 +58,7 @@ struct IPAActiveState { uint32_t temperatureK; } agc; - struct { - UQ<4, 8> rGain; - UQ<4, 8> bGain; - } awb; + ipa::awb::ActiveState awb; }; struct IPAFrameContext : public FrameContext { @@ -67,10 +68,7 @@ struct IPAFrameContext : public FrameContext { UQ<5, 8> ispGain; } agc; - struct { - UQ<4, 8> rGain; - UQ<4, 8> bGain; - } awb; + ipa::awb::FrameContext awb; }; struct IPAContext { @@ -85,6 +83,9 @@ struct IPAContext { FCQueue<IPAFrameContext> frameContexts; ControlInfoMap::Map ctrlMap; + + /* Interface to the Camera Helper */ + std::unique_ptr<CameraSensorHelper> camHelper; }; } /* namespace ipa::mali_c55 */ diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp index 1d3af0627fdb..c35cc10bea01 100644 --- a/src/ipa/mali-c55/mali-c55.cpp +++ b/src/ipa/mali-c55/mali-c55.cpp @@ -77,9 +77,6 @@ private: ControlInfoMap sensorControls_; - /* Interface to the Camera Helper */ - std::unique_ptr<CameraSensorHelper> camHelper_; - /* Local parameter storage */ struct IPAContext context_; }; @@ -101,8 +98,8 @@ std::string IPAMaliC55::logPrefix() const int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig, ControlInfoMap *ipaControls) { - camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); - if (!camHelper_) { + context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel); + if (!context_.camHelper) { LOG(IPAMaliC55, Error) << "Failed to create camera sensor helper for " << settings.sensorModel; @@ -145,10 +142,10 @@ void IPAMaliC55::setControls() if (activeState.agc.autoEnabled) { exposure = activeState.agc.automatic.exposure; - gain = camHelper_->gainCode(activeState.agc.automatic.sensorGain); + gain = context_.camHelper->gainCode(activeState.agc.automatic.sensorGain); } else { exposure = activeState.agc.manual.exposure; - gain = camHelper_->gainCode(activeState.agc.manual.sensorGain); + gain = context_.camHelper->gainCode(activeState.agc.manual.sensorGain); } ControlList ctrls(sensorControls_); @@ -194,17 +191,17 @@ void IPAMaliC55::updateSessionConfiguration(const IPACameraSensorInfo &info, 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); + context_.configuration.agc.minAnalogueGain = context_.camHelper->gain(minGain); + context_.configuration.agc.maxAnalogueGain = context_.camHelper->gain(maxGain); - if (camHelper_->blackLevel().has_value()) { + if (context_.camHelper->blackLevel().has_value()) { /* * The black level from CameraSensorHelper is a 16-bit value. * The Mali-C55 ISP expects 20-bit settings, so we shift it to * the appropriate width */ context_.configuration.sensor.blackLevel = - camHelper_->blackLevel().value() << 4; + context_.camHelper->blackLevel().value() << 4; } } @@ -255,9 +252,9 @@ void IPAMaliC55::updateControls(const IPACameraSensorInfo &sensorInfo, /* Compute the analogue gain limits. */ const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; - float minGain = camHelper_->gain(v4l2Gain.min().get<int32_t>()); - float maxGain = camHelper_->gain(v4l2Gain.max().get<int32_t>()); - float defGain = camHelper_->gain(v4l2Gain.def().get<int32_t>()); + float minGain = context_.camHelper->gain(v4l2Gain.min().get<int32_t>()); + float maxGain = context_.camHelper->gain(v4l2Gain.max().get<int32_t>()); + float defGain = context_.camHelper->gain(v4l2Gain.def().get<int32_t>()); ctrlMap[&controls::AnalogueGain] = ControlInfo(minGain, maxGain, defGain); /* @@ -355,7 +352,7 @@ void IPAMaliC55::processStats(unsigned int request, unsigned int bufferId, frameContext.agc.exposure = sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>(); frameContext.agc.sensorGain = - camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>()); + context_.camHelper->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>()); ControlList metadata(controls::controls);
Port the Mali-C55 Awb algorithm to use the new libipa implementation of AwbAlgorithm. The awbAlgo_ class member is initialized with the Q<4, 8> type that represents the register format and the MaliC55AwbStats type handles the WB statistics format as produced by the Mali-C55 which already calculates the mean values as R/G and B/G values. Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> --- src/ipa/mali-c55/algorithms/awb.cpp | 198 ++++++++++++++++++------------------ src/ipa/mali-c55/algorithms/awb.h | 30 ++++-- src/ipa/mali-c55/ipa_context.cpp | 6 ++ src/ipa/mali-c55/ipa_context.h | 17 ++-- src/ipa/mali-c55/mali-c55.cpp | 27 +++-- 5 files changed, 152 insertions(+), 126 deletions(-)